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    runnables::RunnableTasks,
    9    scroll::scroll_amount::ScrollAmount,
   10    test::{
   11        assert_text_with_selections, build_editor, editor_content_with_blocks,
   12        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   13        editor_test_context::EditorTestContext,
   14        select_ranges,
   15    },
   16};
   17use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   18use collections::HashMap;
   19use futures::{StreamExt, channel::oneshot};
   20use gpui::{
   21    BackgroundExecutor, DismissEvent, TestAppContext, UpdateGlobal, VisualTestContext,
   22    WindowBounds, WindowOptions, div,
   23};
   24use indoc::indoc;
   25use language::{
   26    BracketPair, BracketPairConfig,
   27    Capability::ReadWrite,
   28    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   29    LanguageConfigOverride, LanguageMatcher, LanguageName, LanguageQueries, Override, Point,
   30    language_settings::{
   31        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   32    },
   33    tree_sitter_python,
   34};
   35use language_settings::Formatter;
   36use languages::markdown_lang;
   37use languages::rust_lang;
   38use lsp::{CompletionParams, DEFAULT_LSP_REQUEST_TIMEOUT};
   39use multi_buffer::{IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
   40use parking_lot::Mutex;
   41use pretty_assertions::{assert_eq, assert_ne};
   42use project::{
   43    FakeFs, Project,
   44    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   45    project_settings::LspSettings,
   46    trusted_worktrees::{PathTrust, TrustedWorktrees},
   47};
   48use serde_json::{self, json};
   49use settings::{
   50    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, GlobalLspSettingsContent,
   51    IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
   52    ProjectSettingsContent, ScrollBeyondLastLine, SearchSettingsContent, SettingsContent,
   53    SettingsStore,
   54};
   55use std::borrow::Cow;
   56use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   57use std::{
   58    iter,
   59    sync::atomic::{self, AtomicUsize},
   60};
   61use test::build_editor_with_project;
   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};
   68use workspace::{
   69    CloseActiveItem, CloseAllItems, CloseOtherItems, MultiWorkspace, NavigationEntry, OpenOptions,
   70    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#[cfg(any(test, feature = "test-support"))]
   82pub mod property_test;
   83
   84#[gpui::test]
   85fn test_edit_events(cx: &mut TestAppContext) {
   86    init_test(cx, |_| {});
   87
   88    let buffer = cx.new(|cx| {
   89        let mut buffer = language::Buffer::local("123456", cx);
   90        buffer.set_group_interval(Duration::from_secs(1));
   91        buffer
   92    });
   93
   94    let events = Rc::new(RefCell::new(Vec::new()));
   95    let editor1 = cx.add_window({
   96        let events = events.clone();
   97        |window, cx| {
   98            let entity = cx.entity();
   99            cx.subscribe_in(
  100                &entity,
  101                window,
  102                move |_, _, event: &EditorEvent, _, _| match event {
  103                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  104                    EditorEvent::BufferEdited => {
  105                        events.borrow_mut().push(("editor1", "buffer edited"))
  106                    }
  107                    _ => {}
  108                },
  109            )
  110            .detach();
  111            Editor::for_buffer(buffer.clone(), None, window, cx)
  112        }
  113    });
  114
  115    let editor2 = cx.add_window({
  116        let events = events.clone();
  117        |window, cx| {
  118            cx.subscribe_in(
  119                &cx.entity(),
  120                window,
  121                move |_, _, event: &EditorEvent, _, _| match event {
  122                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  123                    EditorEvent::BufferEdited => {
  124                        events.borrow_mut().push(("editor2", "buffer edited"))
  125                    }
  126                    _ => {}
  127                },
  128            )
  129            .detach();
  130            Editor::for_buffer(buffer.clone(), None, window, cx)
  131        }
  132    });
  133
  134    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  135
  136    // Mutating editor 1 will emit an `Edited` event only for that editor.
  137    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  138    assert_eq!(
  139        mem::take(&mut *events.borrow_mut()),
  140        [
  141            ("editor1", "edited"),
  142            ("editor1", "buffer edited"),
  143            ("editor2", "buffer edited"),
  144        ]
  145    );
  146
  147    // Mutating editor 2 will emit an `Edited` event only for that editor.
  148    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  149    assert_eq!(
  150        mem::take(&mut *events.borrow_mut()),
  151        [
  152            ("editor2", "edited"),
  153            ("editor1", "buffer edited"),
  154            ("editor2", "buffer edited"),
  155        ]
  156    );
  157
  158    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  159    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  160    assert_eq!(
  161        mem::take(&mut *events.borrow_mut()),
  162        [
  163            ("editor1", "edited"),
  164            ("editor1", "buffer edited"),
  165            ("editor2", "buffer edited"),
  166        ]
  167    );
  168
  169    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  170    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  171    assert_eq!(
  172        mem::take(&mut *events.borrow_mut()),
  173        [
  174            ("editor1", "edited"),
  175            ("editor1", "buffer edited"),
  176            ("editor2", "buffer edited"),
  177        ]
  178    );
  179
  180    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  181    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  182    assert_eq!(
  183        mem::take(&mut *events.borrow_mut()),
  184        [
  185            ("editor2", "edited"),
  186            ("editor1", "buffer edited"),
  187            ("editor2", "buffer edited"),
  188        ]
  189    );
  190
  191    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  192    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  193    assert_eq!(
  194        mem::take(&mut *events.borrow_mut()),
  195        [
  196            ("editor2", "edited"),
  197            ("editor1", "buffer edited"),
  198            ("editor2", "buffer edited"),
  199        ]
  200    );
  201
  202    // No event is emitted when the mutation is a no-op.
  203    _ = editor2.update(cx, |editor, window, cx| {
  204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  205            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  206        });
  207
  208        editor.backspace(&Backspace, window, cx);
  209    });
  210    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  211}
  212
  213#[gpui::test]
  214fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  215    init_test(cx, |_| {});
  216
  217    let mut now = Instant::now();
  218    let group_interval = Duration::from_millis(1);
  219    let buffer = cx.new(|cx| {
  220        let mut buf = language::Buffer::local("123456", cx);
  221        buf.set_group_interval(group_interval);
  222        buf
  223    });
  224    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  225    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  226
  227    _ = editor.update(cx, |editor, window, cx| {
  228        editor.start_transaction_at(now, window, cx);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  231        });
  232
  233        editor.insert("cd", window, cx);
  234        editor.end_transaction_at(now, cx);
  235        assert_eq!(editor.text(cx), "12cd56");
  236        assert_eq!(
  237            editor.selections.ranges(&editor.display_snapshot(cx)),
  238            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  239        );
  240
  241        editor.start_transaction_at(now, window, cx);
  242        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  243            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  244        });
  245        editor.insert("e", window, cx);
  246        editor.end_transaction_at(now, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(
  249            editor.selections.ranges(&editor.display_snapshot(cx)),
  250            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  251        );
  252
  253        now += group_interval + Duration::from_millis(1);
  254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  255            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  256        });
  257
  258        // Simulate an edit in another editor
  259        buffer.update(cx, |buffer, cx| {
  260            buffer.start_transaction_at(now, cx);
  261            buffer.edit(
  262                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  263                None,
  264                cx,
  265            );
  266            buffer.edit(
  267                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  268                None,
  269                cx,
  270            );
  271            buffer.end_transaction_at(now, cx);
  272        });
  273
  274        assert_eq!(editor.text(cx), "ab2cde6");
  275        assert_eq!(
  276            editor.selections.ranges(&editor.display_snapshot(cx)),
  277            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  278        );
  279
  280        // Last transaction happened past the group interval in a different editor.
  281        // Undo it individually and don't restore selections.
  282        editor.undo(&Undo, window, cx);
  283        assert_eq!(editor.text(cx), "12cde6");
  284        assert_eq!(
  285            editor.selections.ranges(&editor.display_snapshot(cx)),
  286            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  287        );
  288
  289        // First two transactions happened within the group interval in this editor.
  290        // Undo them together and restore selections.
  291        editor.undo(&Undo, window, cx);
  292        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  293        assert_eq!(editor.text(cx), "123456");
  294        assert_eq!(
  295            editor.selections.ranges(&editor.display_snapshot(cx)),
  296            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  297        );
  298
  299        // Redo the first two transactions together.
  300        editor.redo(&Redo, window, cx);
  301        assert_eq!(editor.text(cx), "12cde6");
  302        assert_eq!(
  303            editor.selections.ranges(&editor.display_snapshot(cx)),
  304            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  305        );
  306
  307        // Redo the last transaction on its own.
  308        editor.redo(&Redo, window, cx);
  309        assert_eq!(editor.text(cx), "ab2cde6");
  310        assert_eq!(
  311            editor.selections.ranges(&editor.display_snapshot(cx)),
  312            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  313        );
  314
  315        // Test empty transactions.
  316        editor.start_transaction_at(now, window, cx);
  317        editor.end_transaction_at(now, cx);
  318        editor.undo(&Undo, window, cx);
  319        assert_eq!(editor.text(cx), "12cde6");
  320    });
  321}
  322
  323#[gpui::test]
  324fn test_accessibility_keyboard_word_completion(cx: &mut TestAppContext) {
  325    init_test(cx, |_| {});
  326
  327    // Simulates the macOS Accessibility Keyboard word completion panel, which calls
  328    // insertText:replacementRange: to commit a completion. macOS sends two calls per
  329    // completion: one with a non-empty range replacing the typed prefix, and one with
  330    // an empty replacement range (cursor..cursor) to append a trailing space.
  331
  332    cx.add_window(|window, cx| {
  333        let buffer = MultiBuffer::build_simple("ab", cx);
  334        let mut editor = build_editor(buffer, window, cx);
  335
  336        // Cursor is after the 2-char prefix "ab" at offset 2.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  339        });
  340
  341        // macOS completes "about" by replacing the prefix via range 0..2.
  342        editor.replace_text_in_range(Some(0..2), "about", window, cx);
  343        assert_eq!(editor.text(cx), "about");
  344
  345        // macOS sends a trailing space as an empty replacement range (cursor..cursor).
  346        // Must insert at the cursor position, not call backspace first (which would
  347        // delete the preceding character).
  348        editor.replace_text_in_range(Some(5..5), " ", window, cx);
  349        assert_eq!(editor.text(cx), "about ");
  350
  351        editor
  352    });
  353
  354    // Multi-cursor: the replacement must fan out to all cursors, and the trailing
  355    // space must land at each cursor's actual current position. After the first
  356    // completion, macOS's reported cursor offset is stale (it doesn't account for
  357    // the offset shift caused by the other cursor's insertion), so the empty
  358    // replacement range must be ignored and the space inserted at each real cursor.
  359    cx.add_window(|window, cx| {
  360        // Two cursors, each after a 2-char prefix "ab" at the end of each line:
  361        //   "ab\nab" — cursors at offsets 2 and 5.
  362        let buffer = MultiBuffer::build_simple("ab\nab", cx);
  363        let mut editor = build_editor(buffer, window, cx);
  364
  365        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  366            s.select_ranges([
  367                MultiBufferOffset(2)..MultiBufferOffset(2),
  368                MultiBufferOffset(5)..MultiBufferOffset(5),
  369            ])
  370        });
  371
  372        // macOS reports the newest cursor (offset 5) and sends range 3..5 to
  373        // replace its 2-char prefix. selection_replacement_ranges applies the same
  374        // delta to fan out to both cursors: 0..2 and 3..5.
  375        editor.replace_text_in_range(Some(3..5), "about", window, cx);
  376        assert_eq!(editor.text(cx), "about\nabout");
  377
  378        // Trailing space via empty range. macOS thinks the cursor is at offset 10
  379        // (5 - 2 + 7 = 10), but the actual cursors are at 5 and 11. The stale
  380        // offset must be ignored and the space inserted at each real cursor position.
  381        editor.replace_text_in_range(Some(10..10), " ", window, cx);
  382        assert_eq!(editor.text(cx), "about \nabout ");
  383
  384        editor
  385    });
  386}
  387
  388#[gpui::test]
  389fn test_ime_composition(cx: &mut TestAppContext) {
  390    init_test(cx, |_| {});
  391
  392    let buffer = cx.new(|cx| {
  393        let mut buffer = language::Buffer::local("abcde", cx);
  394        // Ensure automatic grouping doesn't occur.
  395        buffer.set_group_interval(Duration::ZERO);
  396        buffer
  397    });
  398
  399    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  400    cx.add_window(|window, cx| {
  401        let mut editor = build_editor(buffer.clone(), window, cx);
  402
  403        // Start a new IME composition.
  404        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  405        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  406        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  407        assert_eq!(editor.text(cx), "äbcde");
  408        assert_eq!(
  409            editor.marked_text_ranges(cx),
  410            Some(vec![
  411                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  412            ])
  413        );
  414
  415        // Finalize IME composition.
  416        editor.replace_text_in_range(None, "ā", window, cx);
  417        assert_eq!(editor.text(cx), "ābcde");
  418        assert_eq!(editor.marked_text_ranges(cx), None);
  419
  420        // IME composition edits are grouped and are undone/redone at once.
  421        editor.undo(&Default::default(), window, cx);
  422        assert_eq!(editor.text(cx), "abcde");
  423        assert_eq!(editor.marked_text_ranges(cx), None);
  424        editor.redo(&Default::default(), window, cx);
  425        assert_eq!(editor.text(cx), "ābcde");
  426        assert_eq!(editor.marked_text_ranges(cx), None);
  427
  428        // Start a new IME composition.
  429        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  430        assert_eq!(
  431            editor.marked_text_ranges(cx),
  432            Some(vec![
  433                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  434            ])
  435        );
  436
  437        // Undoing during an IME composition cancels it.
  438        editor.undo(&Default::default(), window, cx);
  439        assert_eq!(editor.text(cx), "ābcde");
  440        assert_eq!(editor.marked_text_ranges(cx), None);
  441
  442        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  443        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  444        assert_eq!(editor.text(cx), "ābcdè");
  445        assert_eq!(
  446            editor.marked_text_ranges(cx),
  447            Some(vec![
  448                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  449            ])
  450        );
  451
  452        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  453        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  454        assert_eq!(editor.text(cx), "ābcdę");
  455        assert_eq!(editor.marked_text_ranges(cx), None);
  456
  457        // Start a new IME composition with multiple cursors.
  458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  459            s.select_ranges([
  460                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  461                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  462                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  463            ])
  464        });
  465        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  466        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  467        assert_eq!(
  468            editor.marked_text_ranges(cx),
  469            Some(vec![
  470                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  471                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  472                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  473            ])
  474        );
  475
  476        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  477        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  478        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  479        assert_eq!(
  480            editor.marked_text_ranges(cx),
  481            Some(vec![
  482                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  483                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  484                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  485            ])
  486        );
  487
  488        // Finalize IME composition with multiple cursors.
  489        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  490        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  491        assert_eq!(editor.marked_text_ranges(cx), None);
  492
  493        editor
  494    });
  495}
  496
  497#[gpui::test]
  498fn test_selection_with_mouse(cx: &mut TestAppContext) {
  499    init_test(cx, |_| {});
  500
  501    let editor = cx.add_window(|window, cx| {
  502        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  503        build_editor(buffer, window, cx)
  504    });
  505
  506    _ = editor.update(cx, |editor, window, cx| {
  507        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  508    });
  509    assert_eq!(
  510        editor
  511            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  512            .unwrap(),
  513        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  514    );
  515
  516    _ = editor.update(cx, |editor, window, cx| {
  517        editor.update_selection(
  518            DisplayPoint::new(DisplayRow(3), 3),
  519            0,
  520            gpui::Point::<f32>::default(),
  521            window,
  522            cx,
  523        );
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  531    );
  532
  533    _ = editor.update(cx, |editor, window, cx| {
  534        editor.update_selection(
  535            DisplayPoint::new(DisplayRow(1), 1),
  536            0,
  537            gpui::Point::<f32>::default(),
  538            window,
  539            cx,
  540        );
  541    });
  542
  543    assert_eq!(
  544        editor
  545            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  546            .unwrap(),
  547        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  548    );
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.end_selection(window, cx);
  552        editor.update_selection(
  553            DisplayPoint::new(DisplayRow(3), 3),
  554            0,
  555            gpui::Point::<f32>::default(),
  556            window,
  557            cx,
  558        );
  559    });
  560
  561    assert_eq!(
  562        editor
  563            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  564            .unwrap(),
  565        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  566    );
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  570        editor.update_selection(
  571            DisplayPoint::new(DisplayRow(0), 0),
  572            0,
  573            gpui::Point::<f32>::default(),
  574            window,
  575            cx,
  576        );
  577    });
  578
  579    assert_eq!(
  580        editor
  581            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  582            .unwrap(),
  583        [
  584            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  585            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  586        ]
  587    );
  588
  589    _ = editor.update(cx, |editor, window, cx| {
  590        editor.end_selection(window, cx);
  591    });
  592
  593    assert_eq!(
  594        editor
  595            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  596            .unwrap(),
  597        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  598    );
  599}
  600
  601#[gpui::test]
  602fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  603    init_test(cx, |_| {});
  604
  605    let editor = cx.add_window(|window, cx| {
  606        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  607        build_editor(buffer, window, cx)
  608    });
  609
  610    _ = editor.update(cx, |editor, window, cx| {
  611        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  612    });
  613
  614    _ = editor.update(cx, |editor, window, cx| {
  615        editor.end_selection(window, cx);
  616    });
  617
  618    _ = editor.update(cx, |editor, window, cx| {
  619        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  620    });
  621
  622    _ = editor.update(cx, |editor, window, cx| {
  623        editor.end_selection(window, cx);
  624    });
  625
  626    assert_eq!(
  627        editor
  628            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  629            .unwrap(),
  630        [
  631            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  632            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  633        ]
  634    );
  635
  636    _ = editor.update(cx, |editor, window, cx| {
  637        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.end_selection(window, cx);
  642    });
  643
  644    assert_eq!(
  645        editor
  646            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  647            .unwrap(),
  648        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  649    );
  650}
  651
  652#[gpui::test]
  653fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  654    init_test(cx, |_| {});
  655
  656    let editor = cx.add_window(|window, cx| {
  657        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  658        build_editor(buffer, window, cx)
  659    });
  660
  661    _ = editor.update(cx, |editor, window, cx| {
  662        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  663        assert_eq!(
  664            display_ranges(editor, cx),
  665            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  666        );
  667    });
  668
  669    _ = editor.update(cx, |editor, window, cx| {
  670        editor.update_selection(
  671            DisplayPoint::new(DisplayRow(3), 3),
  672            0,
  673            gpui::Point::<f32>::default(),
  674            window,
  675            cx,
  676        );
  677        assert_eq!(
  678            display_ranges(editor, cx),
  679            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  680        );
  681    });
  682
  683    _ = editor.update(cx, |editor, window, cx| {
  684        editor.cancel(&Cancel, window, cx);
  685        editor.update_selection(
  686            DisplayPoint::new(DisplayRow(1), 1),
  687            0,
  688            gpui::Point::<f32>::default(),
  689            window,
  690            cx,
  691        );
  692        assert_eq!(
  693            display_ranges(editor, cx),
  694            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  695        );
  696    });
  697}
  698
  699#[gpui::test]
  700fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  701    init_test(cx, |_| {});
  702
  703    let editor = cx.add_window(|window, cx| {
  704        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  705        build_editor(buffer, window, cx)
  706    });
  707
  708    _ = editor.update(cx, |editor, window, cx| {
  709        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  710        assert_eq!(
  711            display_ranges(editor, cx),
  712            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  713        );
  714
  715        editor.move_down(&Default::default(), window, cx);
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  719        );
  720
  721        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  722        assert_eq!(
  723            display_ranges(editor, cx),
  724            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  725        );
  726
  727        editor.move_up(&Default::default(), window, cx);
  728        assert_eq!(
  729            display_ranges(editor, cx),
  730            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  731        );
  732    });
  733}
  734
  735#[gpui::test]
  736fn test_extending_selection(cx: &mut TestAppContext) {
  737    init_test(cx, |_| {});
  738
  739    let editor = cx.add_window(|window, cx| {
  740        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  741        build_editor(buffer, window, cx)
  742    });
  743
  744    _ = editor.update(cx, |editor, window, cx| {
  745        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  746        editor.end_selection(window, cx);
  747        assert_eq!(
  748            display_ranges(editor, cx),
  749            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  750        );
  751
  752        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  753        editor.end_selection(window, cx);
  754        assert_eq!(
  755            display_ranges(editor, cx),
  756            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  757        );
  758
  759        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  760        editor.end_selection(window, cx);
  761        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  762        assert_eq!(
  763            display_ranges(editor, cx),
  764            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  765        );
  766
  767        editor.update_selection(
  768            DisplayPoint::new(DisplayRow(0), 1),
  769            0,
  770            gpui::Point::<f32>::default(),
  771            window,
  772            cx,
  773        );
  774        editor.end_selection(window, cx);
  775        assert_eq!(
  776            display_ranges(editor, cx),
  777            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  778        );
  779
  780        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  781        editor.end_selection(window, cx);
  782        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  783        editor.end_selection(window, cx);
  784        assert_eq!(
  785            display_ranges(editor, cx),
  786            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  787        );
  788
  789        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  790        assert_eq!(
  791            display_ranges(editor, cx),
  792            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  793        );
  794
  795        editor.update_selection(
  796            DisplayPoint::new(DisplayRow(0), 6),
  797            0,
  798            gpui::Point::<f32>::default(),
  799            window,
  800            cx,
  801        );
  802        assert_eq!(
  803            display_ranges(editor, cx),
  804            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  805        );
  806
  807        editor.update_selection(
  808            DisplayPoint::new(DisplayRow(0), 1),
  809            0,
  810            gpui::Point::<f32>::default(),
  811            window,
  812            cx,
  813        );
  814        editor.end_selection(window, cx);
  815        assert_eq!(
  816            display_ranges(editor, cx),
  817            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  818        );
  819    });
  820}
  821
  822#[gpui::test]
  823fn test_clone(cx: &mut TestAppContext) {
  824    init_test(cx, |_| {});
  825
  826    let (text, selection_ranges) = marked_text_ranges(
  827        indoc! {"
  828            one
  829            two
  830            threeˇ
  831            four
  832            fiveˇ
  833        "},
  834        true,
  835    );
  836
  837    let editor = cx.add_window(|window, cx| {
  838        let buffer = MultiBuffer::build_simple(&text, cx);
  839        build_editor(buffer, window, cx)
  840    });
  841
  842    _ = editor.update(cx, |editor, window, cx| {
  843        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  844            s.select_ranges(
  845                selection_ranges
  846                    .iter()
  847                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  848            )
  849        });
  850        editor.fold_creases(
  851            vec![
  852                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  853                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  854            ],
  855            true,
  856            window,
  857            cx,
  858        );
  859    });
  860
  861    let cloned_editor = editor
  862        .update(cx, |editor, _, cx| {
  863            cx.open_window(Default::default(), |window, cx| {
  864                cx.new(|cx| editor.clone(window, cx))
  865            })
  866        })
  867        .unwrap()
  868        .unwrap();
  869
  870    let snapshot = editor
  871        .update(cx, |e, window, cx| e.snapshot(window, cx))
  872        .unwrap();
  873    let cloned_snapshot = cloned_editor
  874        .update(cx, |e, window, cx| e.snapshot(window, cx))
  875        .unwrap();
  876
  877    assert_eq!(
  878        cloned_editor
  879            .update(cx, |e, _, cx| e.display_text(cx))
  880            .unwrap(),
  881        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  882    );
  883    assert_eq!(
  884        cloned_snapshot
  885            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  886            .collect::<Vec<_>>(),
  887        snapshot
  888            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  889            .collect::<Vec<_>>(),
  890    );
  891    assert_set_eq!(
  892        cloned_editor
  893            .update(cx, |editor, _, cx| editor
  894                .selections
  895                .ranges::<Point>(&editor.display_snapshot(cx)))
  896            .unwrap(),
  897        editor
  898            .update(cx, |editor, _, cx| editor
  899                .selections
  900                .ranges(&editor.display_snapshot(cx)))
  901            .unwrap()
  902    );
  903    assert_set_eq!(
  904        cloned_editor
  905            .update(cx, |e, _window, cx| e
  906                .selections
  907                .display_ranges(&e.display_snapshot(cx)))
  908            .unwrap(),
  909        editor
  910            .update(cx, |e, _, cx| e
  911                .selections
  912                .display_ranges(&e.display_snapshot(cx)))
  913            .unwrap()
  914    );
  915}
  916
  917#[gpui::test]
  918async fn test_navigation_history(cx: &mut TestAppContext) {
  919    init_test(cx, |_| {});
  920
  921    use workspace::item::Item;
  922
  923    let fs = FakeFs::new(cx.executor());
  924    let project = Project::test(fs, [], cx).await;
  925    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
  926    let workspace = window
  927        .read_with(cx, |mw, _| mw.workspace().clone())
  928        .unwrap();
  929    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
  930
  931    _ = window.update(cx, |_mw, window, cx| {
  932        cx.new(|cx| {
  933            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  934            let mut editor = build_editor(buffer, window, cx);
  935            let handle = cx.entity();
  936            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  937
  938            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  939                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  940            }
  941
  942            // Move the cursor a small distance.
  943            // Nothing is added to the navigation history.
  944            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  945                s.select_display_ranges([
  946                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  947                ])
  948            });
  949            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  950                s.select_display_ranges([
  951                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  952                ])
  953            });
  954            assert!(pop_history(&mut editor, cx).is_none());
  955
  956            // Move the cursor a large distance.
  957            // The history can jump back to the previous position.
  958            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  959                s.select_display_ranges([
  960                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  961                ])
  962            });
  963            let nav_entry = pop_history(&mut editor, cx).unwrap();
  964            editor.navigate(nav_entry.data.unwrap(), window, cx);
  965            assert_eq!(nav_entry.item.id(), cx.entity_id());
  966            assert_eq!(
  967                editor
  968                    .selections
  969                    .display_ranges(&editor.display_snapshot(cx)),
  970                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  971            );
  972            assert!(pop_history(&mut editor, cx).is_none());
  973
  974            // Move the cursor a small distance via the mouse.
  975            // Nothing is added to the navigation history.
  976            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  977            editor.end_selection(window, cx);
  978            assert_eq!(
  979                editor
  980                    .selections
  981                    .display_ranges(&editor.display_snapshot(cx)),
  982                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  983            );
  984            assert!(pop_history(&mut editor, cx).is_none());
  985
  986            // Move the cursor a large distance via the mouse.
  987            // The history can jump back to the previous position.
  988            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  989            editor.end_selection(window, cx);
  990            assert_eq!(
  991                editor
  992                    .selections
  993                    .display_ranges(&editor.display_snapshot(cx)),
  994                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  995            );
  996            let nav_entry = pop_history(&mut editor, cx).unwrap();
  997            editor.navigate(nav_entry.data.unwrap(), window, cx);
  998            assert_eq!(nav_entry.item.id(), cx.entity_id());
  999            assert_eq!(
 1000                editor
 1001                    .selections
 1002                    .display_ranges(&editor.display_snapshot(cx)),
 1003                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
 1004            );
 1005            assert!(pop_history(&mut editor, cx).is_none());
 1006
 1007            // Set scroll position to check later
 1008            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
 1009            let original_scroll_position = editor
 1010                .scroll_manager
 1011                .native_anchor(&editor.display_snapshot(cx), cx);
 1012
 1013            // Jump to the end of the document and adjust scroll
 1014            editor.move_to_end(&MoveToEnd, window, cx);
 1015            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
 1016            assert_ne!(
 1017                editor
 1018                    .scroll_manager
 1019                    .native_anchor(&editor.display_snapshot(cx), cx),
 1020                original_scroll_position
 1021            );
 1022
 1023            let nav_entry = pop_history(&mut editor, cx).unwrap();
 1024            editor.navigate(nav_entry.data.unwrap(), window, cx);
 1025            assert_eq!(
 1026                editor
 1027                    .scroll_manager
 1028                    .native_anchor(&editor.display_snapshot(cx), cx),
 1029                original_scroll_position
 1030            );
 1031
 1032            let other_buffer =
 1033                cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local("test", cx)), cx));
 1034
 1035            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
 1036            let invalid_anchor = other_buffer.update(cx, |buffer, cx| {
 1037                buffer.snapshot(cx).anchor_after(MultiBufferOffset(3))
 1038            });
 1039            let invalid_point = Point::new(9999, 0);
 1040            editor.navigate(
 1041                Arc::new(NavigationData {
 1042                    cursor_anchor: invalid_anchor,
 1043                    cursor_position: invalid_point,
 1044                    scroll_anchor: ScrollAnchor {
 1045                        anchor: invalid_anchor,
 1046                        offset: Default::default(),
 1047                    },
 1048                    scroll_top_row: invalid_point.row,
 1049                }),
 1050                window,
 1051                cx,
 1052            );
 1053            assert_eq!(
 1054                editor
 1055                    .selections
 1056                    .display_ranges(&editor.display_snapshot(cx)),
 1057                &[editor.max_point(cx)..editor.max_point(cx)]
 1058            );
 1059            assert_eq!(
 1060                editor.scroll_position(cx),
 1061                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
 1062            );
 1063
 1064            editor
 1065        })
 1066    });
 1067}
 1068
 1069#[gpui::test]
 1070fn test_cancel(cx: &mut TestAppContext) {
 1071    init_test(cx, |_| {});
 1072
 1073    let editor = cx.add_window(|window, cx| {
 1074        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 1075        build_editor(buffer, window, cx)
 1076    });
 1077
 1078    _ = editor.update(cx, |editor, window, cx| {
 1079        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
 1080        editor.update_selection(
 1081            DisplayPoint::new(DisplayRow(1), 1),
 1082            0,
 1083            gpui::Point::<f32>::default(),
 1084            window,
 1085            cx,
 1086        );
 1087        editor.end_selection(window, cx);
 1088
 1089        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1090        editor.update_selection(
 1091            DisplayPoint::new(DisplayRow(0), 3),
 1092            0,
 1093            gpui::Point::<f32>::default(),
 1094            window,
 1095            cx,
 1096        );
 1097        editor.end_selection(window, cx);
 1098        assert_eq!(
 1099            display_ranges(editor, cx),
 1100            [
 1101                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1102                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1103            ]
 1104        );
 1105    });
 1106
 1107    _ = editor.update(cx, |editor, window, cx| {
 1108        editor.cancel(&Cancel, window, cx);
 1109        assert_eq!(
 1110            display_ranges(editor, cx),
 1111            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1112        );
 1113    });
 1114
 1115    _ = editor.update(cx, |editor, window, cx| {
 1116        editor.cancel(&Cancel, window, cx);
 1117        assert_eq!(
 1118            display_ranges(editor, cx),
 1119            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1120        );
 1121    });
 1122}
 1123
 1124#[gpui::test]
 1125async fn test_fold_action(cx: &mut TestAppContext) {
 1126    init_test(cx, |_| {});
 1127
 1128    let mut cx = EditorTestContext::new(cx).await;
 1129
 1130    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 1131    cx.set_state(indoc! {"
 1132        impl Foo {
 1133            // Hello!
 1134
 1135            fn a() {
 1136                1
 1137            }
 1138
 1139            fn b() {
 1140                2
 1141            }
 1142
 1143            fn c() {
 1144                3
 1145            }
 1146 1147    "});
 1148
 1149    cx.update_editor(|editor, window, cx| {
 1150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1151            s.select_display_ranges([
 1152                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1153            ]);
 1154        });
 1155        editor.fold(&Fold, window, cx);
 1156        assert_eq!(
 1157            editor.display_text(cx),
 1158            "
 1159                impl Foo {
 1160                    // Hello!
 1161
 1162                    fn a() {
 1163                        1
 1164                    }
 1165
 1166                    fn b() {⋯}
 1167
 1168                    fn c() {⋯}
 1169                }
 1170            "
 1171            .unindent(),
 1172        );
 1173
 1174        editor.fold(&Fold, window, cx);
 1175        assert_eq!(
 1176            editor.display_text(cx),
 1177            "
 1178                impl Foo {⋯}
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.unfold_lines(&UnfoldLines, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                impl Foo {
 1188                    // Hello!
 1189
 1190                    fn a() {
 1191                        1
 1192                    }
 1193
 1194                    fn b() {⋯}
 1195
 1196                    fn c() {⋯}
 1197                }
 1198            "
 1199            .unindent(),
 1200        );
 1201
 1202        editor.unfold_lines(&UnfoldLines, window, cx);
 1203        assert_eq!(
 1204            editor.display_text(cx),
 1205            editor.buffer.read(cx).read(cx).text()
 1206        );
 1207    });
 1208}
 1209
 1210#[gpui::test]
 1211fn test_fold_action_without_language(cx: &mut TestAppContext) {
 1212    init_test(cx, |_| {});
 1213
 1214    let editor = cx.add_window(|window, cx| {
 1215        let buffer = MultiBuffer::build_simple(
 1216            &"
 1217                impl Foo {
 1218                    // Hello!
 1219
 1220                    fn a() {
 1221                        1
 1222                    }
 1223
 1224                    fn b() {
 1225                        2
 1226                    }
 1227
 1228                    fn c() {
 1229                        3
 1230                    }
 1231                }
 1232            "
 1233            .unindent(),
 1234            cx,
 1235        );
 1236        build_editor(buffer, window, cx)
 1237    });
 1238
 1239    _ = editor.update(cx, |editor, window, cx| {
 1240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1241            s.select_display_ranges([
 1242                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1243            ]);
 1244        });
 1245        editor.fold(&Fold, window, cx);
 1246        assert_eq!(
 1247            editor.display_text(cx),
 1248            "
 1249                impl Foo {
 1250                    // Hello!
 1251
 1252                    fn a() {
 1253                        1
 1254                    }
 1255
 1256                    fn b() {⋯
 1257                    }
 1258
 1259                    fn c() {⋯
 1260                    }
 1261                }
 1262            "
 1263            .unindent(),
 1264        );
 1265
 1266        editor.fold(&Fold, window, cx);
 1267        assert_eq!(
 1268            editor.display_text(cx),
 1269            "
 1270                impl Foo {⋯
 1271                }
 1272            "
 1273            .unindent(),
 1274        );
 1275
 1276        editor.unfold_lines(&UnfoldLines, window, cx);
 1277        assert_eq!(
 1278            editor.display_text(cx),
 1279            "
 1280                impl Foo {
 1281                    // Hello!
 1282
 1283                    fn a() {
 1284                        1
 1285                    }
 1286
 1287                    fn b() {⋯
 1288                    }
 1289
 1290                    fn c() {⋯
 1291                    }
 1292                }
 1293            "
 1294            .unindent(),
 1295        );
 1296
 1297        editor.unfold_lines(&UnfoldLines, window, cx);
 1298        assert_eq!(
 1299            editor.display_text(cx),
 1300            editor.buffer.read(cx).read(cx).text()
 1301        );
 1302    });
 1303}
 1304
 1305#[gpui::test]
 1306fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1307    init_test(cx, |_| {});
 1308
 1309    let editor = cx.add_window(|window, cx| {
 1310        let buffer = MultiBuffer::build_simple(
 1311            &"
 1312                class Foo:
 1313                    # Hello!
 1314
 1315                    def a():
 1316                        print(1)
 1317
 1318                    def b():
 1319                        print(2)
 1320
 1321                    def c():
 1322                        print(3)
 1323            "
 1324            .unindent(),
 1325            cx,
 1326        );
 1327        build_editor(buffer, window, cx)
 1328    });
 1329
 1330    _ = editor.update(cx, |editor, window, cx| {
 1331        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1332            s.select_display_ranges([
 1333                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1334            ]);
 1335        });
 1336        editor.fold(&Fold, window, cx);
 1337        assert_eq!(
 1338            editor.display_text(cx),
 1339            "
 1340                class Foo:
 1341                    # Hello!
 1342
 1343                    def a():
 1344                        print(1)
 1345
 1346                    def b():⋯
 1347
 1348                    def c():⋯
 1349            "
 1350            .unindent(),
 1351        );
 1352
 1353        editor.fold(&Fold, window, cx);
 1354        assert_eq!(
 1355            editor.display_text(cx),
 1356            "
 1357                class Foo:⋯
 1358            "
 1359            .unindent(),
 1360        );
 1361
 1362        editor.unfold_lines(&UnfoldLines, window, cx);
 1363        assert_eq!(
 1364            editor.display_text(cx),
 1365            "
 1366                class Foo:
 1367                    # Hello!
 1368
 1369                    def a():
 1370                        print(1)
 1371
 1372                    def b():⋯
 1373
 1374                    def c():⋯
 1375            "
 1376            .unindent(),
 1377        );
 1378
 1379        editor.unfold_lines(&UnfoldLines, window, cx);
 1380        assert_eq!(
 1381            editor.display_text(cx),
 1382            editor.buffer.read(cx).read(cx).text()
 1383        );
 1384    });
 1385}
 1386
 1387#[gpui::test]
 1388fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1389    init_test(cx, |_| {});
 1390
 1391    let editor = cx.add_window(|window, cx| {
 1392        let buffer = MultiBuffer::build_simple(
 1393            &"
 1394                class Foo:
 1395                    # Hello!
 1396
 1397                    def a():
 1398                        print(1)
 1399
 1400                    def b():
 1401                        print(2)
 1402
 1403
 1404                    def c():
 1405                        print(3)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410            cx,
 1411        );
 1412        build_editor(buffer, window, cx)
 1413    });
 1414
 1415    _ = editor.update(cx, |editor, window, cx| {
 1416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1417            s.select_display_ranges([
 1418                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1419            ]);
 1420        });
 1421        editor.fold(&Fold, window, cx);
 1422        assert_eq!(
 1423            editor.display_text(cx),
 1424            "
 1425                class Foo:
 1426                    # Hello!
 1427
 1428                    def a():
 1429                        print(1)
 1430
 1431                    def b():⋯
 1432
 1433
 1434                    def c():⋯
 1435
 1436
 1437            "
 1438            .unindent(),
 1439        );
 1440
 1441        editor.fold(&Fold, window, cx);
 1442        assert_eq!(
 1443            editor.display_text(cx),
 1444            "
 1445                class Foo:⋯
 1446
 1447
 1448            "
 1449            .unindent(),
 1450        );
 1451
 1452        editor.unfold_lines(&UnfoldLines, window, cx);
 1453        assert_eq!(
 1454            editor.display_text(cx),
 1455            "
 1456                class Foo:
 1457                    # Hello!
 1458
 1459                    def a():
 1460                        print(1)
 1461
 1462                    def b():⋯
 1463
 1464
 1465                    def c():⋯
 1466
 1467
 1468            "
 1469            .unindent(),
 1470        );
 1471
 1472        editor.unfold_lines(&UnfoldLines, window, cx);
 1473        assert_eq!(
 1474            editor.display_text(cx),
 1475            editor.buffer.read(cx).read(cx).text()
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481async fn test_fold_with_unindented_multiline_raw_string(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let mut cx = EditorTestContext::new(cx).await;
 1485
 1486    let language = Arc::new(
 1487        Language::new(
 1488            LanguageConfig::default(),
 1489            Some(tree_sitter_rust::LANGUAGE.into()),
 1490        )
 1491        .with_queries(LanguageQueries {
 1492            overrides: Some(Cow::from(indoc! {"
 1493                [
 1494                  (string_literal)
 1495                  (raw_string_literal)
 1496                ] @string
 1497                [
 1498                  (line_comment)
 1499                  (block_comment)
 1500                ] @comment.inclusive
 1501            "})),
 1502            ..Default::default()
 1503        })
 1504        .expect("Could not parse queries"),
 1505    );
 1506
 1507    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 1508    cx.set_state(indoc! {"
 1509        fn main() {
 1510            let s = r#\"
 1511        a
 1512        b
 1513        c
 1514        \"#;
 1515 1516    "});
 1517
 1518    cx.update_editor(|editor, window, cx| {
 1519        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1520        assert_eq!(
 1521            editor.display_text(cx),
 1522            indoc! {"
 1523                fn main() {⋯
 1524                }
 1525            "},
 1526        );
 1527    });
 1528}
 1529
 1530#[gpui::test]
 1531async fn test_fold_with_unindented_multiline_raw_string_includes_closing_bracket(
 1532    cx: &mut TestAppContext,
 1533) {
 1534    init_test(cx, |_| {});
 1535
 1536    let mut cx = EditorTestContext::new(cx).await;
 1537
 1538    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 1539    cx.set_state(indoc! {"
 1540        ˇfn main() {
 1541            let s = r#\"
 1542        a
 1543        b
 1544        c
 1545        \"#;
 1546        }
 1547    "});
 1548
 1549    cx.update_editor(|editor, window, cx| {
 1550        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1551        assert_eq!(
 1552            editor.display_text(cx),
 1553            indoc! {"
 1554                fn main() {⋯}
 1555            "},
 1556        );
 1557    });
 1558}
 1559
 1560#[gpui::test]
 1561async fn test_fold_with_unindented_multiline_block_comment(cx: &mut TestAppContext) {
 1562    init_test(cx, |_| {});
 1563
 1564    let mut cx = EditorTestContext::new(cx).await;
 1565
 1566    let language = Arc::new(
 1567        Language::new(
 1568            LanguageConfig::default(),
 1569            Some(tree_sitter_rust::LANGUAGE.into()),
 1570        )
 1571        .with_queries(LanguageQueries {
 1572            overrides: Some(Cow::from(indoc! {"
 1573                [
 1574                  (string_literal)
 1575                  (raw_string_literal)
 1576                ] @string
 1577                [
 1578                  (line_comment)
 1579                  (block_comment)
 1580                ] @comment.inclusive
 1581            "})),
 1582            ..Default::default()
 1583        })
 1584        .expect("Could not parse queries"),
 1585    );
 1586
 1587    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 1588    cx.set_state(indoc! {"
 1589        fn main() {
 1590            let x = 1;
 1591            /*
 1592        unindented comment line
 1593            */
 1594 1595    "});
 1596
 1597    cx.update_editor(|editor, window, cx| {
 1598        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1599        assert_eq!(
 1600            editor.display_text(cx),
 1601            indoc! {"
 1602                fn main() {⋯
 1603                }
 1604            "},
 1605        );
 1606    });
 1607}
 1608
 1609#[gpui::test]
 1610async fn test_fold_with_unindented_multiline_block_comment_includes_closing_bracket(
 1611    cx: &mut TestAppContext,
 1612) {
 1613    init_test(cx, |_| {});
 1614
 1615    let mut cx = EditorTestContext::new(cx).await;
 1616
 1617    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 1618    cx.set_state(indoc! {"
 1619        ˇfn main() {
 1620            let x = 1;
 1621            /*
 1622        unindented comment line
 1623            */
 1624        }
 1625    "});
 1626
 1627    cx.update_editor(|editor, window, cx| {
 1628        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1629        assert_eq!(
 1630            editor.display_text(cx),
 1631            indoc! {"
 1632                fn main() {⋯}
 1633            "},
 1634        );
 1635    });
 1636}
 1637
 1638#[gpui::test]
 1639fn test_fold_at_level(cx: &mut TestAppContext) {
 1640    init_test(cx, |_| {});
 1641
 1642    let editor = cx.add_window(|window, cx| {
 1643        let buffer = MultiBuffer::build_simple(
 1644            &"
 1645                class Foo:
 1646                    # Hello!
 1647
 1648                    def a():
 1649                        print(1)
 1650
 1651                    def b():
 1652                        print(2)
 1653
 1654
 1655                class Bar:
 1656                    # World!
 1657
 1658                    def a():
 1659                        print(1)
 1660
 1661                    def b():
 1662                        print(2)
 1663
 1664
 1665            "
 1666            .unindent(),
 1667            cx,
 1668        );
 1669        build_editor(buffer, window, cx)
 1670    });
 1671
 1672    _ = editor.update(cx, |editor, window, cx| {
 1673        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1674        assert_eq!(
 1675            editor.display_text(cx),
 1676            "
 1677                class Foo:
 1678                    # Hello!
 1679
 1680                    def a():⋯
 1681
 1682                    def b():⋯
 1683
 1684
 1685                class Bar:
 1686                    # World!
 1687
 1688                    def a():⋯
 1689
 1690                    def b():⋯
 1691
 1692
 1693            "
 1694            .unindent(),
 1695        );
 1696
 1697        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1698        assert_eq!(
 1699            editor.display_text(cx),
 1700            "
 1701                class Foo:⋯
 1702
 1703
 1704                class Bar:⋯
 1705
 1706
 1707            "
 1708            .unindent(),
 1709        );
 1710
 1711        editor.unfold_all(&UnfoldAll, window, cx);
 1712        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1713        assert_eq!(
 1714            editor.display_text(cx),
 1715            "
 1716                class Foo:
 1717                    # Hello!
 1718
 1719                    def a():
 1720                        print(1)
 1721
 1722                    def b():
 1723                        print(2)
 1724
 1725
 1726                class Bar:
 1727                    # World!
 1728
 1729                    def a():
 1730                        print(1)
 1731
 1732                    def b():
 1733                        print(2)
 1734
 1735
 1736            "
 1737            .unindent(),
 1738        );
 1739
 1740        assert_eq!(
 1741            editor.display_text(cx),
 1742            editor.buffer.read(cx).read(cx).text()
 1743        );
 1744        let (_, positions) = marked_text_ranges(
 1745            &"
 1746                       class Foo:
 1747                           # Hello!
 1748
 1749                           def a():
 1750                              print(1)
 1751
 1752                           def b():
 1753                               p«riˇ»nt(2)
 1754
 1755
 1756                       class Bar:
 1757                           # World!
 1758
 1759                           def a():
 1760                               «ˇprint(1)
 1761
 1762                           def b():
 1763                               print(2)»
 1764
 1765
 1766                   "
 1767            .unindent(),
 1768            true,
 1769        );
 1770
 1771        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1772            s.select_ranges(
 1773                positions
 1774                    .iter()
 1775                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1776            )
 1777        });
 1778
 1779        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1780        assert_eq!(
 1781            editor.display_text(cx),
 1782            "
 1783                class Foo:
 1784                    # Hello!
 1785
 1786                    def a():⋯
 1787
 1788                    def b():
 1789                        print(2)
 1790
 1791
 1792                class Bar:
 1793                    # World!
 1794
 1795                    def a():
 1796                        print(1)
 1797
 1798                    def b():
 1799                        print(2)
 1800
 1801
 1802            "
 1803            .unindent(),
 1804        );
 1805    });
 1806}
 1807
 1808#[gpui::test]
 1809fn test_move_cursor(cx: &mut TestAppContext) {
 1810    init_test(cx, |_| {});
 1811
 1812    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1813    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1814
 1815    buffer.update(cx, |buffer, cx| {
 1816        buffer.edit(
 1817            vec![
 1818                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1819                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1820            ],
 1821            None,
 1822            cx,
 1823        );
 1824    });
 1825    _ = editor.update(cx, |editor, window, cx| {
 1826        assert_eq!(
 1827            display_ranges(editor, cx),
 1828            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1829        );
 1830
 1831        editor.move_down(&MoveDown, window, cx);
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1835        );
 1836
 1837        editor.move_right(&MoveRight, window, cx);
 1838        assert_eq!(
 1839            display_ranges(editor, cx),
 1840            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1841        );
 1842
 1843        editor.move_left(&MoveLeft, window, cx);
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1847        );
 1848
 1849        editor.move_up(&MoveUp, window, cx);
 1850        assert_eq!(
 1851            display_ranges(editor, cx),
 1852            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1853        );
 1854
 1855        editor.move_to_end(&MoveToEnd, window, cx);
 1856        assert_eq!(
 1857            display_ranges(editor, cx),
 1858            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1859        );
 1860
 1861        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1862        assert_eq!(
 1863            display_ranges(editor, cx),
 1864            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1865        );
 1866
 1867        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1868            s.select_display_ranges([
 1869                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1870            ]);
 1871        });
 1872        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1873        assert_eq!(
 1874            display_ranges(editor, cx),
 1875            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1876        );
 1877
 1878        editor.select_to_end(&SelectToEnd, window, cx);
 1879        assert_eq!(
 1880            display_ranges(editor, cx),
 1881            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1882        );
 1883    });
 1884}
 1885
 1886#[gpui::test]
 1887fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1888    init_test(cx, |_| {});
 1889
 1890    let editor = cx.add_window(|window, cx| {
 1891        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1892        build_editor(buffer, window, cx)
 1893    });
 1894
 1895    assert_eq!('🟥'.len_utf8(), 4);
 1896    assert_eq!('α'.len_utf8(), 2);
 1897
 1898    _ = editor.update(cx, |editor, window, cx| {
 1899        editor.fold_creases(
 1900            vec![
 1901                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1902                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1903                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1904            ],
 1905            true,
 1906            window,
 1907            cx,
 1908        );
 1909        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1910
 1911        editor.move_right(&MoveRight, window, cx);
 1912        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1913        editor.move_right(&MoveRight, window, cx);
 1914        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1915        editor.move_right(&MoveRight, window, cx);
 1916        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1917
 1918        editor.move_down(&MoveDown, window, cx);
 1919        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1920        editor.move_left(&MoveLeft, window, cx);
 1921        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1922        editor.move_left(&MoveLeft, window, cx);
 1923        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1924        editor.move_left(&MoveLeft, window, cx);
 1925        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1926
 1927        editor.move_down(&MoveDown, window, cx);
 1928        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1929        editor.move_right(&MoveRight, window, cx);
 1930        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1931        editor.move_right(&MoveRight, window, cx);
 1932        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1933        editor.move_right(&MoveRight, window, cx);
 1934        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1935
 1936        editor.move_up(&MoveUp, window, cx);
 1937        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1938        editor.move_down(&MoveDown, window, cx);
 1939        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1940        editor.move_up(&MoveUp, window, cx);
 1941        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1942
 1943        editor.move_up(&MoveUp, window, cx);
 1944        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1945        editor.move_left(&MoveLeft, window, cx);
 1946        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1947        editor.move_left(&MoveLeft, window, cx);
 1948        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1949    });
 1950}
 1951
 1952#[gpui::test]
 1953fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1954    init_test(cx, |_| {});
 1955
 1956    let editor = cx.add_window(|window, cx| {
 1957        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1958        build_editor(buffer, window, cx)
 1959    });
 1960    _ = editor.update(cx, |editor, window, cx| {
 1961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1962            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1963        });
 1964
 1965        // moving above start of document should move selection to start of document,
 1966        // but the next move down should still be at the original goal_x
 1967        editor.move_up(&MoveUp, window, cx);
 1968        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1969
 1970        editor.move_down(&MoveDown, window, cx);
 1971        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1972
 1973        editor.move_down(&MoveDown, window, cx);
 1974        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1975
 1976        editor.move_down(&MoveDown, window, cx);
 1977        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1978
 1979        editor.move_down(&MoveDown, window, cx);
 1980        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1981
 1982        // moving past end of document should not change goal_x
 1983        editor.move_down(&MoveDown, window, cx);
 1984        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1985
 1986        editor.move_down(&MoveDown, window, cx);
 1987        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1988
 1989        editor.move_up(&MoveUp, window, cx);
 1990        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1991
 1992        editor.move_up(&MoveUp, window, cx);
 1993        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1994
 1995        editor.move_up(&MoveUp, window, cx);
 1996        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1997    });
 1998}
 1999
 2000#[gpui::test]
 2001fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 2002    init_test(cx, |_| {});
 2003    let move_to_beg = MoveToBeginningOfLine {
 2004        stop_at_soft_wraps: true,
 2005        stop_at_indent: true,
 2006    };
 2007
 2008    let delete_to_beg = DeleteToBeginningOfLine {
 2009        stop_at_indent: false,
 2010    };
 2011
 2012    let move_to_end = MoveToEndOfLine {
 2013        stop_at_soft_wraps: true,
 2014    };
 2015
 2016    let editor = cx.add_window(|window, cx| {
 2017        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2018        build_editor(buffer, window, cx)
 2019    });
 2020    _ = editor.update(cx, |editor, window, cx| {
 2021        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2022            s.select_display_ranges([
 2023                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2024                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2025            ]);
 2026        });
 2027    });
 2028
 2029    _ = editor.update(cx, |editor, window, cx| {
 2030        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2031        assert_eq!(
 2032            display_ranges(editor, cx),
 2033            &[
 2034                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2035                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2036            ]
 2037        );
 2038    });
 2039
 2040    _ = editor.update(cx, |editor, window, cx| {
 2041        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2042        assert_eq!(
 2043            display_ranges(editor, cx),
 2044            &[
 2045                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2046                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2047            ]
 2048        );
 2049    });
 2050
 2051    _ = editor.update(cx, |editor, window, cx| {
 2052        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2053        assert_eq!(
 2054            display_ranges(editor, cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060    });
 2061
 2062    _ = editor.update(cx, |editor, window, cx| {
 2063        editor.move_to_end_of_line(&move_to_end, window, cx);
 2064        assert_eq!(
 2065            display_ranges(editor, cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2068                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 2069            ]
 2070        );
 2071    });
 2072
 2073    // Moving to the end of line again is a no-op.
 2074    _ = editor.update(cx, |editor, window, cx| {
 2075        editor.move_to_end_of_line(&move_to_end, window, cx);
 2076        assert_eq!(
 2077            display_ranges(editor, cx),
 2078            &[
 2079                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2080                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 2081            ]
 2082        );
 2083    });
 2084
 2085    _ = editor.update(cx, |editor, window, cx| {
 2086        editor.move_left(&MoveLeft, window, cx);
 2087        editor.select_to_beginning_of_line(
 2088            &SelectToBeginningOfLine {
 2089                stop_at_soft_wraps: true,
 2090                stop_at_indent: true,
 2091            },
 2092            window,
 2093            cx,
 2094        );
 2095        assert_eq!(
 2096            display_ranges(editor, cx),
 2097            &[
 2098                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2099                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2100            ]
 2101        );
 2102    });
 2103
 2104    _ = editor.update(cx, |editor, window, cx| {
 2105        editor.select_to_beginning_of_line(
 2106            &SelectToBeginningOfLine {
 2107                stop_at_soft_wraps: true,
 2108                stop_at_indent: true,
 2109            },
 2110            window,
 2111            cx,
 2112        );
 2113        assert_eq!(
 2114            display_ranges(editor, cx),
 2115            &[
 2116                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2117                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2118            ]
 2119        );
 2120    });
 2121
 2122    _ = editor.update(cx, |editor, window, cx| {
 2123        editor.select_to_beginning_of_line(
 2124            &SelectToBeginningOfLine {
 2125                stop_at_soft_wraps: true,
 2126                stop_at_indent: true,
 2127            },
 2128            window,
 2129            cx,
 2130        );
 2131        assert_eq!(
 2132            display_ranges(editor, cx),
 2133            &[
 2134                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2135                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2136            ]
 2137        );
 2138    });
 2139
 2140    _ = editor.update(cx, |editor, window, cx| {
 2141        editor.select_to_end_of_line(
 2142            &SelectToEndOfLine {
 2143                stop_at_soft_wraps: true,
 2144            },
 2145            window,
 2146            cx,
 2147        );
 2148        assert_eq!(
 2149            display_ranges(editor, cx),
 2150            &[
 2151                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 2152                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 2153            ]
 2154        );
 2155    });
 2156
 2157    _ = editor.update(cx, |editor, window, cx| {
 2158        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 2159        assert_eq!(editor.display_text(cx), "ab\n  de");
 2160        assert_eq!(
 2161            display_ranges(editor, cx),
 2162            &[
 2163                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2164                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2165            ]
 2166        );
 2167    });
 2168
 2169    _ = editor.update(cx, |editor, window, cx| {
 2170        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2171        assert_eq!(editor.display_text(cx), "\n");
 2172        assert_eq!(
 2173            display_ranges(editor, cx),
 2174            &[
 2175                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2176                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2177            ]
 2178        );
 2179    });
 2180}
 2181
 2182#[gpui::test]
 2183fn test_beginning_of_line_single_line_editor(cx: &mut TestAppContext) {
 2184    init_test(cx, |_| {});
 2185
 2186    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
 2187
 2188    _ = editor.update(cx, |editor, window, cx| {
 2189        editor.set_text("  indented text", window, cx);
 2190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2191            s.select_display_ranges([
 2192                DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 10)
 2193            ]);
 2194        });
 2195
 2196        editor.move_to_beginning_of_line(
 2197            &MoveToBeginningOfLine {
 2198                stop_at_soft_wraps: true,
 2199                stop_at_indent: true,
 2200            },
 2201            window,
 2202            cx,
 2203        );
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2207        );
 2208    });
 2209
 2210    _ = editor.update(cx, |editor, window, cx| {
 2211        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2212            s.select_display_ranges([
 2213                DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 10)
 2214            ]);
 2215        });
 2216
 2217        editor.select_to_beginning_of_line(
 2218            &SelectToBeginningOfLine {
 2219                stop_at_soft_wraps: true,
 2220                stop_at_indent: true,
 2221            },
 2222            window,
 2223            cx,
 2224        );
 2225        assert_eq!(
 2226            display_ranges(editor, cx),
 2227            &[DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 0)]
 2228        );
 2229    });
 2230}
 2231
 2232#[gpui::test]
 2233fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 2234    init_test(cx, |_| {});
 2235    let move_to_beg = MoveToBeginningOfLine {
 2236        stop_at_soft_wraps: false,
 2237        stop_at_indent: false,
 2238    };
 2239
 2240    let move_to_end = MoveToEndOfLine {
 2241        stop_at_soft_wraps: false,
 2242    };
 2243
 2244    let editor = cx.add_window(|window, cx| {
 2245        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 2246        build_editor(buffer, window, cx)
 2247    });
 2248
 2249    _ = editor.update(cx, |editor, window, cx| {
 2250        editor.set_wrap_width(Some(140.0.into()), cx);
 2251
 2252        // We expect the following lines after wrapping
 2253        // ```
 2254        // thequickbrownfox
 2255        // jumpedoverthelazydo
 2256        // gs
 2257        // ```
 2258        // The final `gs` was soft-wrapped onto a new line.
 2259        assert_eq!(
 2260            "thequickbrownfox\njumpedoverthelaz\nydogs",
 2261            editor.display_text(cx),
 2262        );
 2263
 2264        // First, let's assert behavior on the first line, that was not soft-wrapped.
 2265        // Start the cursor at the `k` on the first line
 2266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2267            s.select_display_ranges([
 2268                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 2269            ]);
 2270        });
 2271
 2272        // Moving to the beginning of the line should put us at the beginning of the line.
 2273        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2274        assert_eq!(
 2275            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 2276            display_ranges(editor, cx)
 2277        );
 2278
 2279        // Moving to the end of the line should put us at the end of the line.
 2280        editor.move_to_end_of_line(&move_to_end, window, cx);
 2281        assert_eq!(
 2282            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 2283            display_ranges(editor, cx)
 2284        );
 2285
 2286        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 2287        // Start the cursor at the last line (`y` that was wrapped to a new line)
 2288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2289            s.select_display_ranges([
 2290                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 2291            ]);
 2292        });
 2293
 2294        // Moving to the beginning of the line should put us at the start of the second line of
 2295        // display text, i.e., the `j`.
 2296        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2297        assert_eq!(
 2298            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 2299            display_ranges(editor, cx)
 2300        );
 2301
 2302        // Moving to the beginning of the line again should be a no-op.
 2303        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2304        assert_eq!(
 2305            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 2306            display_ranges(editor, cx)
 2307        );
 2308
 2309        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 2310        // next display line.
 2311        editor.move_to_end_of_line(&move_to_end, window, cx);
 2312        assert_eq!(
 2313            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 2314            display_ranges(editor, cx)
 2315        );
 2316
 2317        // Moving to the end of the line again should be a no-op.
 2318        editor.move_to_end_of_line(&move_to_end, window, cx);
 2319        assert_eq!(
 2320            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 2321            display_ranges(editor, cx)
 2322        );
 2323    });
 2324}
 2325
 2326#[gpui::test]
 2327fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 2328    init_test(cx, |_| {});
 2329
 2330    let move_to_beg = MoveToBeginningOfLine {
 2331        stop_at_soft_wraps: true,
 2332        stop_at_indent: true,
 2333    };
 2334
 2335    let select_to_beg = SelectToBeginningOfLine {
 2336        stop_at_soft_wraps: true,
 2337        stop_at_indent: true,
 2338    };
 2339
 2340    let delete_to_beg = DeleteToBeginningOfLine {
 2341        stop_at_indent: true,
 2342    };
 2343
 2344    let move_to_end = MoveToEndOfLine {
 2345        stop_at_soft_wraps: false,
 2346    };
 2347
 2348    let editor = cx.add_window(|window, cx| {
 2349        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2350        build_editor(buffer, window, cx)
 2351    });
 2352
 2353    _ = editor.update(cx, |editor, window, cx| {
 2354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2355            s.select_display_ranges([
 2356                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2357                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2358            ]);
 2359        });
 2360
 2361        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2362        // and the second cursor at the first non-whitespace character in the line.
 2363        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2364        assert_eq!(
 2365            display_ranges(editor, cx),
 2366            &[
 2367                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2368                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2369            ]
 2370        );
 2371
 2372        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2373        // and should move the second cursor to the beginning of the line.
 2374        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2375        assert_eq!(
 2376            display_ranges(editor, cx),
 2377            &[
 2378                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2379                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2380            ]
 2381        );
 2382
 2383        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2384        // and should move the second cursor back to the first non-whitespace character in the line.
 2385        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2386        assert_eq!(
 2387            display_ranges(editor, cx),
 2388            &[
 2389                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2390                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2391            ]
 2392        );
 2393
 2394        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2395        // and to the first non-whitespace character in the line for the second cursor.
 2396        editor.move_to_end_of_line(&move_to_end, window, cx);
 2397        editor.move_left(&MoveLeft, window, cx);
 2398        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2399        assert_eq!(
 2400            display_ranges(editor, cx),
 2401            &[
 2402                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2403                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2404            ]
 2405        );
 2406
 2407        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2408        // and should select to the beginning of the line for the second cursor.
 2409        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2410        assert_eq!(
 2411            display_ranges(editor, cx),
 2412            &[
 2413                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2414                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2415            ]
 2416        );
 2417
 2418        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2419        // and should delete to the first non-whitespace character in the line for the second cursor.
 2420        editor.move_to_end_of_line(&move_to_end, window, cx);
 2421        editor.move_left(&MoveLeft, window, cx);
 2422        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2423        assert_eq!(editor.text(cx), "c\n  f");
 2424    });
 2425}
 2426
 2427#[gpui::test]
 2428fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2429    init_test(cx, |_| {});
 2430
 2431    let move_to_beg = MoveToBeginningOfLine {
 2432        stop_at_soft_wraps: true,
 2433        stop_at_indent: true,
 2434    };
 2435
 2436    let editor = cx.add_window(|window, cx| {
 2437        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2438        build_editor(buffer, window, cx)
 2439    });
 2440
 2441    _ = editor.update(cx, |editor, window, cx| {
 2442        // test cursor between line_start and indent_start
 2443        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2444            s.select_display_ranges([
 2445                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2446            ]);
 2447        });
 2448
 2449        // cursor should move to line_start
 2450        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2451        assert_eq!(
 2452            display_ranges(editor, cx),
 2453            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2454        );
 2455
 2456        // cursor should move to indent_start
 2457        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2458        assert_eq!(
 2459            display_ranges(editor, cx),
 2460            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2461        );
 2462
 2463        // cursor should move to back to line_start
 2464        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2465        assert_eq!(
 2466            display_ranges(editor, cx),
 2467            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2468        );
 2469    });
 2470}
 2471
 2472#[gpui::test]
 2473fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2474    init_test(cx, |_| {});
 2475
 2476    let editor = cx.add_window(|window, cx| {
 2477        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2478        build_editor(buffer, window, cx)
 2479    });
 2480    _ = editor.update(cx, |editor, window, cx| {
 2481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2482            s.select_display_ranges([
 2483                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2484                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2485            ])
 2486        });
 2487        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2488        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2489
 2490        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2491        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2492
 2493        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2494        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2495
 2496        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2497        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2498
 2499        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2500        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2501
 2502        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2503        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2504
 2505        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2506        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2507
 2508        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2509        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2510
 2511        editor.move_right(&MoveRight, window, cx);
 2512        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2513        assert_selection_ranges(
 2514            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2515            editor,
 2516            cx,
 2517        );
 2518
 2519        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2520        assert_selection_ranges(
 2521            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2522            editor,
 2523            cx,
 2524        );
 2525
 2526        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2527        assert_selection_ranges(
 2528            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2529            editor,
 2530            cx,
 2531        );
 2532    });
 2533}
 2534
 2535#[gpui::test]
 2536fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2537    init_test(cx, |_| {});
 2538
 2539    let editor = cx.add_window(|window, cx| {
 2540        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2541        build_editor(buffer, window, cx)
 2542    });
 2543
 2544    _ = editor.update(cx, |editor, window, cx| {
 2545        editor.set_wrap_width(Some(140.0.into()), cx);
 2546        assert_eq!(
 2547            editor.display_text(cx),
 2548            "use one::{\n    two::three::\n    four::five\n};"
 2549        );
 2550
 2551        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2552            s.select_display_ranges([
 2553                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2554            ]);
 2555        });
 2556
 2557        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2558        assert_eq!(
 2559            display_ranges(editor, cx),
 2560            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2561        );
 2562
 2563        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2564        assert_eq!(
 2565            display_ranges(editor, cx),
 2566            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2567        );
 2568
 2569        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2570        assert_eq!(
 2571            display_ranges(editor, cx),
 2572            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2573        );
 2574
 2575        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2576        assert_eq!(
 2577            display_ranges(editor, cx),
 2578            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2579        );
 2580
 2581        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2582        assert_eq!(
 2583            display_ranges(editor, cx),
 2584            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2585        );
 2586
 2587        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2588        assert_eq!(
 2589            display_ranges(editor, cx),
 2590            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2591        );
 2592    });
 2593}
 2594
 2595#[gpui::test]
 2596async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2597    init_test(cx, |_| {});
 2598    let mut cx = EditorTestContext::new(cx).await;
 2599
 2600    let line_height = cx.update_editor(|editor, window, cx| {
 2601        editor
 2602            .style(cx)
 2603            .text
 2604            .line_height_in_pixels(window.rem_size())
 2605    });
 2606    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2607
 2608    // The third line only contains a single space so we can later assert that the
 2609    // editor's paragraph movement considers a non-blank line as a paragraph
 2610    // boundary.
 2611    cx.set_state(&"ˇone\ntwo\n \nthree\nfourˇ\nfive\n\nsix");
 2612
 2613    cx.update_editor(|editor, window, cx| {
 2614        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2615    });
 2616    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\nˇ\nsix");
 2617
 2618    cx.update_editor(|editor, window, cx| {
 2619        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2620    });
 2621    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsixˇ");
 2622
 2623    cx.update_editor(|editor, window, cx| {
 2624        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2625    });
 2626    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n\nsixˇ");
 2627
 2628    cx.update_editor(|editor, window, cx| {
 2629        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2630    });
 2631    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsix");
 2632
 2633    cx.update_editor(|editor, window, cx| {
 2634        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2635    });
 2636
 2637    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\n\nsix");
 2638
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2641    });
 2642    cx.assert_editor_state(&"ˇone\ntwo\n \nthree\nfour\nfive\n\nsix");
 2643}
 2644
 2645#[gpui::test]
 2646async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2647    init_test(cx, |_| {});
 2648    let mut cx = EditorTestContext::new(cx).await;
 2649    let line_height = cx.update_editor(|editor, window, cx| {
 2650        editor
 2651            .style(cx)
 2652            .text
 2653            .line_height_in_pixels(window.rem_size())
 2654    });
 2655    let window = cx.window;
 2656    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2657
 2658    cx.set_state(
 2659        r#"ˇone
 2660        two
 2661        three
 2662        four
 2663        five
 2664        six
 2665        seven
 2666        eight
 2667        nine
 2668        ten
 2669        "#,
 2670    );
 2671
 2672    cx.update_editor(|editor, window, cx| {
 2673        assert_eq!(
 2674            editor.snapshot(window, cx).scroll_position(),
 2675            gpui::Point::new(0., 0.)
 2676        );
 2677        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2678        assert_eq!(
 2679            editor.snapshot(window, cx).scroll_position(),
 2680            gpui::Point::new(0., 3.)
 2681        );
 2682        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2683        assert_eq!(
 2684            editor.snapshot(window, cx).scroll_position(),
 2685            gpui::Point::new(0., 6.)
 2686        );
 2687        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2688        assert_eq!(
 2689            editor.snapshot(window, cx).scroll_position(),
 2690            gpui::Point::new(0., 3.)
 2691        );
 2692
 2693        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2694        assert_eq!(
 2695            editor.snapshot(window, cx).scroll_position(),
 2696            gpui::Point::new(0., 1.)
 2697        );
 2698        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2699        assert_eq!(
 2700            editor.snapshot(window, cx).scroll_position(),
 2701            gpui::Point::new(0., 3.)
 2702        );
 2703    });
 2704}
 2705
 2706#[gpui::test]
 2707async fn test_autoscroll(cx: &mut TestAppContext) {
 2708    init_test(cx, |_| {});
 2709    let mut cx = EditorTestContext::new(cx).await;
 2710
 2711    let line_height = cx.update_editor(|editor, window, cx| {
 2712        editor.set_vertical_scroll_margin(2, cx);
 2713        editor
 2714            .style(cx)
 2715            .text
 2716            .line_height_in_pixels(window.rem_size())
 2717    });
 2718    let window = cx.window;
 2719    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2720
 2721    cx.set_state(
 2722        r#"ˇone
 2723            two
 2724            three
 2725            four
 2726            five
 2727            six
 2728            seven
 2729            eight
 2730            nine
 2731            ten
 2732        "#,
 2733    );
 2734    cx.update_editor(|editor, window, cx| {
 2735        assert_eq!(
 2736            editor.snapshot(window, cx).scroll_position(),
 2737            gpui::Point::new(0., 0.0)
 2738        );
 2739    });
 2740
 2741    // Add a cursor below the visible area. Since both cursors cannot fit
 2742    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2743    // allows the vertical scroll margin below that cursor.
 2744    cx.update_editor(|editor, window, cx| {
 2745        editor.change_selections(Default::default(), window, cx, |selections| {
 2746            selections.select_ranges([
 2747                Point::new(0, 0)..Point::new(0, 0),
 2748                Point::new(6, 0)..Point::new(6, 0),
 2749            ]);
 2750        })
 2751    });
 2752    cx.update_editor(|editor, window, cx| {
 2753        assert_eq!(
 2754            editor.snapshot(window, cx).scroll_position(),
 2755            gpui::Point::new(0., 3.0)
 2756        );
 2757    });
 2758
 2759    // Move down. The editor cursor scrolls down to track the newest cursor.
 2760    cx.update_editor(|editor, window, cx| {
 2761        editor.move_down(&Default::default(), window, cx);
 2762    });
 2763    cx.update_editor(|editor, window, cx| {
 2764        assert_eq!(
 2765            editor.snapshot(window, cx).scroll_position(),
 2766            gpui::Point::new(0., 4.0)
 2767        );
 2768    });
 2769
 2770    // Add a cursor above the visible area. Since both cursors fit on screen,
 2771    // the editor scrolls to show both.
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.change_selections(Default::default(), window, cx, |selections| {
 2774            selections.select_ranges([
 2775                Point::new(1, 0)..Point::new(1, 0),
 2776                Point::new(6, 0)..Point::new(6, 0),
 2777            ]);
 2778        })
 2779    });
 2780    cx.update_editor(|editor, window, cx| {
 2781        assert_eq!(
 2782            editor.snapshot(window, cx).scroll_position(),
 2783            gpui::Point::new(0., 1.0)
 2784        );
 2785    });
 2786}
 2787
 2788#[gpui::test]
 2789async fn test_exclude_overscroll_margin_clamps_scroll_position(cx: &mut TestAppContext) {
 2790    init_test(cx, |_| {});
 2791    update_test_editor_settings(cx, &|settings| {
 2792        settings.scroll_beyond_last_line = Some(ScrollBeyondLastLine::OnePage);
 2793    });
 2794
 2795    let mut cx = EditorTestContext::new(cx).await;
 2796
 2797    let line_height = cx.update_editor(|editor, window, cx| {
 2798        editor.set_mode(EditorMode::Full {
 2799            scale_ui_elements_with_buffer_font_size: false,
 2800            show_active_line_background: false,
 2801            sizing_behavior: SizingBehavior::ExcludeOverscrollMargin,
 2802        });
 2803        editor
 2804            .style(cx)
 2805            .text
 2806            .line_height_in_pixels(window.rem_size())
 2807    });
 2808    let window = cx.window;
 2809    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2810    cx.set_state(
 2811        &r#"
 2812        ˇone
 2813        two
 2814        three
 2815        four
 2816        five
 2817        six
 2818        seven
 2819        eight
 2820        nine
 2821        ten
 2822        eleven
 2823        "#
 2824        .unindent(),
 2825    );
 2826
 2827    cx.update_editor(|editor, window, cx| {
 2828        let snapshot = editor.snapshot(window, cx);
 2829        let max_scroll_top =
 2830            (snapshot.max_point().row().as_f64() - editor.visible_line_count().unwrap() + 1.)
 2831                .max(0.);
 2832
 2833        editor.set_scroll_position(gpui::Point::new(0., max_scroll_top + 10.), window, cx);
 2834
 2835        assert_eq!(
 2836            editor.snapshot(window, cx).scroll_position(),
 2837            gpui::Point::new(0., max_scroll_top)
 2838        );
 2839    });
 2840}
 2841
 2842#[gpui::test]
 2843async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2844    init_test(cx, |_| {});
 2845    let mut cx = EditorTestContext::new(cx).await;
 2846
 2847    let line_height = cx.update_editor(|editor, window, cx| {
 2848        editor
 2849            .style(cx)
 2850            .text
 2851            .line_height_in_pixels(window.rem_size())
 2852    });
 2853    let window = cx.window;
 2854    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2855    cx.set_state(
 2856        &r#"
 2857        ˇone
 2858        two
 2859        threeˇ
 2860        four
 2861        five
 2862        six
 2863        seven
 2864        eight
 2865        nine
 2866        ten
 2867        "#
 2868        .unindent(),
 2869    );
 2870
 2871    cx.update_editor(|editor, window, cx| {
 2872        editor.move_page_down(&MovePageDown::default(), window, cx)
 2873    });
 2874    cx.assert_editor_state(
 2875        &r#"
 2876        one
 2877        two
 2878        three
 2879        ˇfour
 2880        five
 2881        sixˇ
 2882        seven
 2883        eight
 2884        nine
 2885        ten
 2886        "#
 2887        .unindent(),
 2888    );
 2889
 2890    cx.update_editor(|editor, window, cx| {
 2891        editor.move_page_down(&MovePageDown::default(), window, cx)
 2892    });
 2893    cx.assert_editor_state(
 2894        &r#"
 2895        one
 2896        two
 2897        three
 2898        four
 2899        five
 2900        six
 2901        ˇseven
 2902        eight
 2903        nineˇ
 2904        ten
 2905        "#
 2906        .unindent(),
 2907    );
 2908
 2909    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2910    cx.assert_editor_state(
 2911        &r#"
 2912        one
 2913        two
 2914        three
 2915        ˇfour
 2916        five
 2917        sixˇ
 2918        seven
 2919        eight
 2920        nine
 2921        ten
 2922        "#
 2923        .unindent(),
 2924    );
 2925
 2926    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2927    cx.assert_editor_state(
 2928        &r#"
 2929        ˇone
 2930        two
 2931        threeˇ
 2932        four
 2933        five
 2934        six
 2935        seven
 2936        eight
 2937        nine
 2938        ten
 2939        "#
 2940        .unindent(),
 2941    );
 2942
 2943    // Test select collapsing
 2944    cx.update_editor(|editor, window, cx| {
 2945        editor.move_page_down(&MovePageDown::default(), window, cx);
 2946        editor.move_page_down(&MovePageDown::default(), window, cx);
 2947        editor.move_page_down(&MovePageDown::default(), window, cx);
 2948    });
 2949    cx.assert_editor_state(
 2950        &r#"
 2951        one
 2952        two
 2953        three
 2954        four
 2955        five
 2956        six
 2957        seven
 2958        eight
 2959        nine
 2960        ˇten
 2961        ˇ"#
 2962        .unindent(),
 2963    );
 2964}
 2965
 2966#[gpui::test]
 2967async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2968    init_test(cx, |_| {});
 2969    let mut cx = EditorTestContext::new(cx).await;
 2970    cx.set_state("one «two threeˇ» four");
 2971    cx.update_editor(|editor, window, cx| {
 2972        editor.delete_to_beginning_of_line(
 2973            &DeleteToBeginningOfLine {
 2974                stop_at_indent: false,
 2975            },
 2976            window,
 2977            cx,
 2978        );
 2979        assert_eq!(editor.text(cx), " four");
 2980    });
 2981}
 2982
 2983#[gpui::test]
 2984async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2985    init_test(cx, |_| {});
 2986
 2987    let mut cx = EditorTestContext::new(cx).await;
 2988
 2989    // For an empty selection, the preceding word fragment is deleted.
 2990    // For non-empty selections, only selected characters are deleted.
 2991    cx.set_state("onˇe two t«hreˇ»e four");
 2992    cx.update_editor(|editor, window, cx| {
 2993        editor.delete_to_previous_word_start(
 2994            &DeleteToPreviousWordStart {
 2995                ignore_newlines: false,
 2996                ignore_brackets: false,
 2997            },
 2998            window,
 2999            cx,
 3000        );
 3001    });
 3002    cx.assert_editor_state("ˇe two tˇe four");
 3003
 3004    cx.set_state("e tˇwo te «fˇ»our");
 3005    cx.update_editor(|editor, window, cx| {
 3006        editor.delete_to_next_word_end(
 3007            &DeleteToNextWordEnd {
 3008                ignore_newlines: false,
 3009                ignore_brackets: false,
 3010            },
 3011            window,
 3012            cx,
 3013        );
 3014    });
 3015    cx.assert_editor_state("e tˇ te ˇour");
 3016}
 3017
 3018#[gpui::test]
 3019async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 3020    init_test(cx, |_| {});
 3021
 3022    let mut cx = EditorTestContext::new(cx).await;
 3023
 3024    cx.set_state("here is some text    ˇwith a space");
 3025    cx.update_editor(|editor, window, cx| {
 3026        editor.delete_to_previous_word_start(
 3027            &DeleteToPreviousWordStart {
 3028                ignore_newlines: false,
 3029                ignore_brackets: true,
 3030            },
 3031            window,
 3032            cx,
 3033        );
 3034    });
 3035    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 3036    cx.assert_editor_state("here is some textˇwith a space");
 3037
 3038    cx.set_state("here is some text    ˇwith a space");
 3039    cx.update_editor(|editor, window, cx| {
 3040        editor.delete_to_previous_word_start(
 3041            &DeleteToPreviousWordStart {
 3042                ignore_newlines: false,
 3043                ignore_brackets: false,
 3044            },
 3045            window,
 3046            cx,
 3047        );
 3048    });
 3049    cx.assert_editor_state("here is some textˇwith a space");
 3050
 3051    cx.set_state("here is some textˇ    with a space");
 3052    cx.update_editor(|editor, window, cx| {
 3053        editor.delete_to_next_word_end(
 3054            &DeleteToNextWordEnd {
 3055                ignore_newlines: false,
 3056                ignore_brackets: true,
 3057            },
 3058            window,
 3059            cx,
 3060        );
 3061    });
 3062    // Same happens in the other direction.
 3063    cx.assert_editor_state("here is some textˇwith a space");
 3064
 3065    cx.set_state("here is some textˇ    with a space");
 3066    cx.update_editor(|editor, window, cx| {
 3067        editor.delete_to_next_word_end(
 3068            &DeleteToNextWordEnd {
 3069                ignore_newlines: false,
 3070                ignore_brackets: false,
 3071            },
 3072            window,
 3073            cx,
 3074        );
 3075    });
 3076    cx.assert_editor_state("here is some textˇwith a space");
 3077
 3078    cx.set_state("here is some textˇ    with a space");
 3079    cx.update_editor(|editor, window, cx| {
 3080        editor.delete_to_next_word_end(
 3081            &DeleteToNextWordEnd {
 3082                ignore_newlines: true,
 3083                ignore_brackets: false,
 3084            },
 3085            window,
 3086            cx,
 3087        );
 3088    });
 3089    cx.assert_editor_state("here is some textˇwith a space");
 3090    cx.update_editor(|editor, window, cx| {
 3091        editor.delete_to_previous_word_start(
 3092            &DeleteToPreviousWordStart {
 3093                ignore_newlines: true,
 3094                ignore_brackets: false,
 3095            },
 3096            window,
 3097            cx,
 3098        );
 3099    });
 3100    cx.assert_editor_state("here is some ˇwith a space");
 3101    cx.update_editor(|editor, window, cx| {
 3102        editor.delete_to_previous_word_start(
 3103            &DeleteToPreviousWordStart {
 3104                ignore_newlines: true,
 3105                ignore_brackets: false,
 3106            },
 3107            window,
 3108            cx,
 3109        );
 3110    });
 3111    // Single whitespaces are removed with the word behind them.
 3112    cx.assert_editor_state("here is ˇwith a space");
 3113    cx.update_editor(|editor, window, cx| {
 3114        editor.delete_to_previous_word_start(
 3115            &DeleteToPreviousWordStart {
 3116                ignore_newlines: true,
 3117                ignore_brackets: false,
 3118            },
 3119            window,
 3120            cx,
 3121        );
 3122    });
 3123    cx.assert_editor_state("here ˇwith a space");
 3124    cx.update_editor(|editor, window, cx| {
 3125        editor.delete_to_previous_word_start(
 3126            &DeleteToPreviousWordStart {
 3127                ignore_newlines: true,
 3128                ignore_brackets: false,
 3129            },
 3130            window,
 3131            cx,
 3132        );
 3133    });
 3134    cx.assert_editor_state("ˇwith a space");
 3135    cx.update_editor(|editor, window, cx| {
 3136        editor.delete_to_previous_word_start(
 3137            &DeleteToPreviousWordStart {
 3138                ignore_newlines: true,
 3139                ignore_brackets: false,
 3140            },
 3141            window,
 3142            cx,
 3143        );
 3144    });
 3145    cx.assert_editor_state("ˇwith a space");
 3146    cx.update_editor(|editor, window, cx| {
 3147        editor.delete_to_next_word_end(
 3148            &DeleteToNextWordEnd {
 3149                ignore_newlines: true,
 3150                ignore_brackets: false,
 3151            },
 3152            window,
 3153            cx,
 3154        );
 3155    });
 3156    // Same happens in the other direction.
 3157    cx.assert_editor_state("ˇ a space");
 3158    cx.update_editor(|editor, window, cx| {
 3159        editor.delete_to_next_word_end(
 3160            &DeleteToNextWordEnd {
 3161                ignore_newlines: true,
 3162                ignore_brackets: false,
 3163            },
 3164            window,
 3165            cx,
 3166        );
 3167    });
 3168    cx.assert_editor_state("ˇ space");
 3169    cx.update_editor(|editor, window, cx| {
 3170        editor.delete_to_next_word_end(
 3171            &DeleteToNextWordEnd {
 3172                ignore_newlines: true,
 3173                ignore_brackets: false,
 3174            },
 3175            window,
 3176            cx,
 3177        );
 3178    });
 3179    cx.assert_editor_state("ˇ");
 3180    cx.update_editor(|editor, window, cx| {
 3181        editor.delete_to_next_word_end(
 3182            &DeleteToNextWordEnd {
 3183                ignore_newlines: true,
 3184                ignore_brackets: false,
 3185            },
 3186            window,
 3187            cx,
 3188        );
 3189    });
 3190    cx.assert_editor_state("ˇ");
 3191    cx.update_editor(|editor, window, cx| {
 3192        editor.delete_to_previous_word_start(
 3193            &DeleteToPreviousWordStart {
 3194                ignore_newlines: true,
 3195                ignore_brackets: false,
 3196            },
 3197            window,
 3198            cx,
 3199        );
 3200    });
 3201    cx.assert_editor_state("ˇ");
 3202}
 3203
 3204#[gpui::test]
 3205async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 3206    init_test(cx, |_| {});
 3207
 3208    let language = Arc::new(
 3209        Language::new(
 3210            LanguageConfig {
 3211                brackets: BracketPairConfig {
 3212                    pairs: vec![
 3213                        BracketPair {
 3214                            start: "\"".to_string(),
 3215                            end: "\"".to_string(),
 3216                            close: true,
 3217                            surround: true,
 3218                            newline: false,
 3219                        },
 3220                        BracketPair {
 3221                            start: "(".to_string(),
 3222                            end: ")".to_string(),
 3223                            close: true,
 3224                            surround: true,
 3225                            newline: true,
 3226                        },
 3227                    ],
 3228                    ..BracketPairConfig::default()
 3229                },
 3230                ..LanguageConfig::default()
 3231            },
 3232            Some(tree_sitter_rust::LANGUAGE.into()),
 3233        )
 3234        .with_brackets_query(
 3235            r#"
 3236                ("(" @open ")" @close)
 3237                ("\"" @open "\"" @close)
 3238            "#,
 3239        )
 3240        .unwrap(),
 3241    );
 3242
 3243    let mut cx = EditorTestContext::new(cx).await;
 3244    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3245
 3246    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3247    cx.update_editor(|editor, window, cx| {
 3248        editor.delete_to_previous_word_start(
 3249            &DeleteToPreviousWordStart {
 3250                ignore_newlines: true,
 3251                ignore_brackets: false,
 3252            },
 3253            window,
 3254            cx,
 3255        );
 3256    });
 3257    // Deletion stops before brackets if asked to not ignore them.
 3258    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 3259    cx.update_editor(|editor, window, cx| {
 3260        editor.delete_to_previous_word_start(
 3261            &DeleteToPreviousWordStart {
 3262                ignore_newlines: true,
 3263                ignore_brackets: false,
 3264            },
 3265            window,
 3266            cx,
 3267        );
 3268    });
 3269    // Deletion has to remove a single bracket and then stop again.
 3270    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 3271
 3272    cx.update_editor(|editor, window, cx| {
 3273        editor.delete_to_previous_word_start(
 3274            &DeleteToPreviousWordStart {
 3275                ignore_newlines: true,
 3276                ignore_brackets: false,
 3277            },
 3278            window,
 3279            cx,
 3280        );
 3281    });
 3282    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 3283
 3284    cx.update_editor(|editor, window, cx| {
 3285        editor.delete_to_previous_word_start(
 3286            &DeleteToPreviousWordStart {
 3287                ignore_newlines: true,
 3288                ignore_brackets: false,
 3289            },
 3290            window,
 3291            cx,
 3292        );
 3293    });
 3294    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 3295
 3296    cx.update_editor(|editor, window, cx| {
 3297        editor.delete_to_previous_word_start(
 3298            &DeleteToPreviousWordStart {
 3299                ignore_newlines: true,
 3300                ignore_brackets: false,
 3301            },
 3302            window,
 3303            cx,
 3304        );
 3305    });
 3306    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 3307
 3308    cx.update_editor(|editor, window, cx| {
 3309        editor.delete_to_next_word_end(
 3310            &DeleteToNextWordEnd {
 3311                ignore_newlines: true,
 3312                ignore_brackets: false,
 3313            },
 3314            window,
 3315            cx,
 3316        );
 3317    });
 3318    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 3319    cx.assert_editor_state(r#"ˇ");"#);
 3320
 3321    cx.update_editor(|editor, window, cx| {
 3322        editor.delete_to_next_word_end(
 3323            &DeleteToNextWordEnd {
 3324                ignore_newlines: true,
 3325                ignore_brackets: false,
 3326            },
 3327            window,
 3328            cx,
 3329        );
 3330    });
 3331    cx.assert_editor_state(r#"ˇ"#);
 3332
 3333    cx.update_editor(|editor, window, cx| {
 3334        editor.delete_to_next_word_end(
 3335            &DeleteToNextWordEnd {
 3336                ignore_newlines: true,
 3337                ignore_brackets: false,
 3338            },
 3339            window,
 3340            cx,
 3341        );
 3342    });
 3343    cx.assert_editor_state(r#"ˇ"#);
 3344
 3345    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 3346    cx.update_editor(|editor, window, cx| {
 3347        editor.delete_to_previous_word_start(
 3348            &DeleteToPreviousWordStart {
 3349                ignore_newlines: true,
 3350                ignore_brackets: true,
 3351            },
 3352            window,
 3353            cx,
 3354        );
 3355    });
 3356    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 3357}
 3358
 3359#[gpui::test]
 3360fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 3361    init_test(cx, |_| {});
 3362
 3363    let editor = cx.add_window(|window, cx| {
 3364        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3365        build_editor(buffer, window, cx)
 3366    });
 3367    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3368        ignore_newlines: false,
 3369        ignore_brackets: false,
 3370    };
 3371    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3372        ignore_newlines: true,
 3373        ignore_brackets: false,
 3374    };
 3375
 3376    _ = editor.update(cx, |editor, window, cx| {
 3377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3378            s.select_display_ranges([
 3379                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3380            ])
 3381        });
 3382        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3383        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3384        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3385        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3386        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3387        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3388        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3389        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3390        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3391        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3392        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3393        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3394    });
 3395}
 3396
 3397#[gpui::test]
 3398fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3399    init_test(cx, |_| {});
 3400
 3401    let editor = cx.add_window(|window, cx| {
 3402        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3403        build_editor(buffer, window, cx)
 3404    });
 3405    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3406        ignore_newlines: false,
 3407        ignore_brackets: false,
 3408    };
 3409    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3410        ignore_newlines: true,
 3411        ignore_brackets: false,
 3412    };
 3413
 3414    _ = editor.update(cx, |editor, window, cx| {
 3415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3416            s.select_display_ranges([
 3417                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3418            ])
 3419        });
 3420        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3421        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3422        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3423        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3424        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3425        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3426        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3427        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3428        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3429        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3430        editor.delete_to_previous_subword_start(
 3431            &del_to_prev_sub_word_start_ignore_newlines,
 3432            window,
 3433            cx,
 3434        );
 3435        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3436    });
 3437}
 3438
 3439#[gpui::test]
 3440fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3441    init_test(cx, |_| {});
 3442
 3443    let editor = cx.add_window(|window, cx| {
 3444        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3445        build_editor(buffer, window, cx)
 3446    });
 3447    let del_to_next_word_end = DeleteToNextWordEnd {
 3448        ignore_newlines: false,
 3449        ignore_brackets: false,
 3450    };
 3451    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3452        ignore_newlines: true,
 3453        ignore_brackets: false,
 3454    };
 3455
 3456    _ = editor.update(cx, |editor, window, cx| {
 3457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3458            s.select_display_ranges([
 3459                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3460            ])
 3461        });
 3462        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3463        assert_eq!(
 3464            editor.buffer.read(cx).read(cx).text(),
 3465            "one\n   two\nthree\n   four"
 3466        );
 3467        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3468        assert_eq!(
 3469            editor.buffer.read(cx).read(cx).text(),
 3470            "\n   two\nthree\n   four"
 3471        );
 3472        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3473        assert_eq!(
 3474            editor.buffer.read(cx).read(cx).text(),
 3475            "two\nthree\n   four"
 3476        );
 3477        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3478        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3479        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3480        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3481        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3482        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3483        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3484        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3485    });
 3486}
 3487
 3488#[gpui::test]
 3489fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3490    init_test(cx, |_| {});
 3491
 3492    let editor = cx.add_window(|window, cx| {
 3493        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3494        build_editor(buffer, window, cx)
 3495    });
 3496    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3497        ignore_newlines: false,
 3498        ignore_brackets: false,
 3499    };
 3500    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3501        ignore_newlines: true,
 3502        ignore_brackets: false,
 3503    };
 3504
 3505    _ = editor.update(cx, |editor, window, cx| {
 3506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3507            s.select_display_ranges([
 3508                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3509            ])
 3510        });
 3511        // Delete "\n" (empty line)
 3512        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3513        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3514        // Delete "foo" (subword boundary)
 3515        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3516        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3517        // Delete "Bar"
 3518        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3519        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3520        // Delete "\n   " (newline + leading whitespace)
 3521        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3522        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3523        // Delete "baz" (subword boundary)
 3524        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3525        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3526        // With ignore_newlines, delete "Qux"
 3527        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3528        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3529    });
 3530}
 3531
 3532#[gpui::test]
 3533fn test_newline(cx: &mut TestAppContext) {
 3534    init_test(cx, |_| {});
 3535
 3536    let editor = cx.add_window(|window, cx| {
 3537        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3538        build_editor(buffer, window, cx)
 3539    });
 3540
 3541    _ = editor.update(cx, |editor, window, cx| {
 3542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3543            s.select_display_ranges([
 3544                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3545                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3546                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3547            ])
 3548        });
 3549
 3550        editor.newline(&Newline, window, cx);
 3551        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3552    });
 3553}
 3554
 3555#[gpui::test]
 3556async fn test_newline_yaml(cx: &mut TestAppContext) {
 3557    init_test(cx, |_| {});
 3558
 3559    let mut cx = EditorTestContext::new(cx).await;
 3560    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3562
 3563    // Object (between 2 fields)
 3564    cx.set_state(indoc! {"
 3565    test:ˇ
 3566    hello: bye"});
 3567    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3568    cx.assert_editor_state(indoc! {"
 3569    test:
 3570        ˇ
 3571    hello: bye"});
 3572
 3573    // Object (first and single line)
 3574    cx.set_state(indoc! {"
 3575    test:ˇ"});
 3576    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3577    cx.assert_editor_state(indoc! {"
 3578    test:
 3579        ˇ"});
 3580
 3581    // Array with objects (after first element)
 3582    cx.set_state(indoc! {"
 3583    test:
 3584        - foo: barˇ"});
 3585    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3586    cx.assert_editor_state(indoc! {"
 3587    test:
 3588        - foo: bar
 3589        ˇ"});
 3590
 3591    // Array with objects and comment
 3592    cx.set_state(indoc! {"
 3593    test:
 3594        - foo: bar
 3595        - bar: # testˇ"});
 3596    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3597    cx.assert_editor_state(indoc! {"
 3598    test:
 3599        - foo: bar
 3600        - bar: # test
 3601            ˇ"});
 3602
 3603    // Array with objects (after second element)
 3604    cx.set_state(indoc! {"
 3605    test:
 3606        - foo: bar
 3607        - bar: fooˇ"});
 3608    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3609    cx.assert_editor_state(indoc! {"
 3610    test:
 3611        - foo: bar
 3612        - bar: foo
 3613        ˇ"});
 3614
 3615    // Array with strings (after first element)
 3616    cx.set_state(indoc! {"
 3617    test:
 3618        - fooˇ"});
 3619    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3620    cx.assert_editor_state(indoc! {"
 3621    test:
 3622        - foo
 3623        ˇ"});
 3624}
 3625
 3626#[gpui::test]
 3627fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3628    init_test(cx, |_| {});
 3629
 3630    let editor = cx.add_window(|window, cx| {
 3631        let buffer = MultiBuffer::build_simple(
 3632            "
 3633                a
 3634                b(
 3635                    X
 3636                )
 3637                c(
 3638                    X
 3639                )
 3640            "
 3641            .unindent()
 3642            .as_str(),
 3643            cx,
 3644        );
 3645        let mut editor = build_editor(buffer, window, cx);
 3646        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3647            s.select_ranges([
 3648                Point::new(2, 4)..Point::new(2, 5),
 3649                Point::new(5, 4)..Point::new(5, 5),
 3650            ])
 3651        });
 3652        editor
 3653    });
 3654
 3655    _ = editor.update(cx, |editor, window, cx| {
 3656        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3657        editor.buffer.update(cx, |buffer, cx| {
 3658            buffer.edit(
 3659                [
 3660                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3661                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3662                ],
 3663                None,
 3664                cx,
 3665            );
 3666            assert_eq!(
 3667                buffer.read(cx).text(),
 3668                "
 3669                    a
 3670                    b()
 3671                    c()
 3672                "
 3673                .unindent()
 3674            );
 3675        });
 3676        assert_eq!(
 3677            editor.selections.ranges(&editor.display_snapshot(cx)),
 3678            &[
 3679                Point::new(1, 2)..Point::new(1, 2),
 3680                Point::new(2, 2)..Point::new(2, 2),
 3681            ],
 3682        );
 3683
 3684        editor.newline(&Newline, window, cx);
 3685        assert_eq!(
 3686            editor.text(cx),
 3687            "
 3688                a
 3689                b(
 3690                )
 3691                c(
 3692                )
 3693            "
 3694            .unindent()
 3695        );
 3696
 3697        // The selections are moved after the inserted newlines
 3698        assert_eq!(
 3699            editor.selections.ranges(&editor.display_snapshot(cx)),
 3700            &[
 3701                Point::new(2, 0)..Point::new(2, 0),
 3702                Point::new(4, 0)..Point::new(4, 0),
 3703            ],
 3704        );
 3705    });
 3706}
 3707
 3708#[gpui::test]
 3709async fn test_newline_above(cx: &mut TestAppContext) {
 3710    init_test(cx, |settings| {
 3711        settings.defaults.tab_size = NonZeroU32::new(4)
 3712    });
 3713
 3714    let language = Arc::new(
 3715        Language::new(
 3716            LanguageConfig::default(),
 3717            Some(tree_sitter_rust::LANGUAGE.into()),
 3718        )
 3719        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3720        .unwrap(),
 3721    );
 3722
 3723    let mut cx = EditorTestContext::new(cx).await;
 3724    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3725    cx.set_state(indoc! {"
 3726        const a: ˇA = (
 3727 3728                «const_functionˇ»(ˇ),
 3729                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3730 3731        ˇ);ˇ
 3732    "});
 3733
 3734    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3735    cx.assert_editor_state(indoc! {"
 3736        ˇ
 3737        const a: A = (
 3738            ˇ
 3739            (
 3740                ˇ
 3741                ˇ
 3742                const_function(),
 3743                ˇ
 3744                ˇ
 3745                ˇ
 3746                ˇ
 3747                something_else,
 3748                ˇ
 3749            )
 3750            ˇ
 3751            ˇ
 3752        );
 3753    "});
 3754}
 3755
 3756#[gpui::test]
 3757async fn test_newline_below(cx: &mut TestAppContext) {
 3758    init_test(cx, |settings| {
 3759        settings.defaults.tab_size = NonZeroU32::new(4)
 3760    });
 3761
 3762    let language = Arc::new(
 3763        Language::new(
 3764            LanguageConfig::default(),
 3765            Some(tree_sitter_rust::LANGUAGE.into()),
 3766        )
 3767        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3768        .unwrap(),
 3769    );
 3770
 3771    let mut cx = EditorTestContext::new(cx).await;
 3772    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3773    cx.set_state(indoc! {"
 3774        const a: ˇA = (
 3775 3776                «const_functionˇ»(ˇ),
 3777                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3778 3779        ˇ);ˇ
 3780    "});
 3781
 3782    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3783    cx.assert_editor_state(indoc! {"
 3784        const a: A = (
 3785            ˇ
 3786            (
 3787                ˇ
 3788                const_function(),
 3789                ˇ
 3790                ˇ
 3791                something_else,
 3792                ˇ
 3793                ˇ
 3794                ˇ
 3795                ˇ
 3796            )
 3797            ˇ
 3798        );
 3799        ˇ
 3800        ˇ
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805fn test_newline_respects_read_only(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let editor = cx.add_window(|window, cx| {
 3809        let buffer = MultiBuffer::build_simple("aaaa\nbbbb\n", cx);
 3810        build_editor(buffer, window, cx)
 3811    });
 3812
 3813    _ = editor.update(cx, |editor, window, cx| {
 3814        editor.set_read_only(true);
 3815        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3816            s.select_display_ranges([
 3817                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2)
 3818            ])
 3819        });
 3820
 3821        editor.newline(&Newline, window, cx);
 3822        assert_eq!(
 3823            editor.text(cx),
 3824            "aaaa\nbbbb\n",
 3825            "newline should not modify a read-only editor"
 3826        );
 3827
 3828        editor.newline_above(&NewlineAbove, window, cx);
 3829        assert_eq!(
 3830            editor.text(cx),
 3831            "aaaa\nbbbb\n",
 3832            "newline_above should not modify a read-only editor"
 3833        );
 3834
 3835        editor.newline_below(&NewlineBelow, window, cx);
 3836        assert_eq!(
 3837            editor.text(cx),
 3838            "aaaa\nbbbb\n",
 3839            "newline_below should not modify a read-only editor"
 3840        );
 3841    });
 3842}
 3843
 3844#[gpui::test]
 3845fn test_newline_below_multibuffer(cx: &mut TestAppContext) {
 3846    init_test(cx, |_| {});
 3847
 3848    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3849    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3850    let multibuffer = cx.new(|cx| {
 3851        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3852        multibuffer.set_excerpts_for_path(
 3853            PathKey::sorted(0),
 3854            buffer_1.clone(),
 3855            [Point::new(0, 0)..Point::new(2, 3)],
 3856            0,
 3857            cx,
 3858        );
 3859        multibuffer.set_excerpts_for_path(
 3860            PathKey::sorted(1),
 3861            buffer_2.clone(),
 3862            [Point::new(0, 0)..Point::new(2, 3)],
 3863            0,
 3864            cx,
 3865        );
 3866        multibuffer
 3867    });
 3868
 3869    cx.add_window(|window, cx| {
 3870        let mut editor = build_editor(multibuffer, window, cx);
 3871
 3872        assert_eq!(
 3873            editor.text(cx),
 3874            indoc! {"
 3875                aaa
 3876                bbb
 3877                ccc
 3878                ddd
 3879                eee
 3880                fff"}
 3881        );
 3882
 3883        // Cursor on the last line of the first excerpt.
 3884        // The newline should be inserted within the first excerpt (buffer_1),
 3885        // not in the second excerpt (buffer_2).
 3886        select_ranges(
 3887            &mut editor,
 3888            indoc! {"
 3889                aaa
 3890                bbb
 3891                cˇcc
 3892                ddd
 3893                eee
 3894                fff"},
 3895            window,
 3896            cx,
 3897        );
 3898        editor.newline_below(&NewlineBelow, window, cx);
 3899        assert_text_with_selections(
 3900            &mut editor,
 3901            indoc! {"
 3902                aaa
 3903                bbb
 3904                ccc
 3905                ˇ
 3906                ddd
 3907                eee
 3908                fff"},
 3909            cx,
 3910        );
 3911        buffer_1.read_with(cx, |buffer, _| {
 3912            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 3913        });
 3914        buffer_2.read_with(cx, |buffer, _| {
 3915            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3916        });
 3917
 3918        editor
 3919    });
 3920}
 3921
 3922#[gpui::test]
 3923fn test_newline_below_multibuffer_middle_of_excerpt(cx: &mut TestAppContext) {
 3924    init_test(cx, |_| {});
 3925
 3926    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3927    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3928    let multibuffer = cx.new(|cx| {
 3929        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3930        multibuffer.set_excerpts_for_path(
 3931            PathKey::sorted(0),
 3932            buffer_1.clone(),
 3933            [Point::new(0, 0)..Point::new(2, 3)],
 3934            0,
 3935            cx,
 3936        );
 3937        multibuffer.set_excerpts_for_path(
 3938            PathKey::sorted(1),
 3939            buffer_2.clone(),
 3940            [Point::new(0, 0)..Point::new(2, 3)],
 3941            0,
 3942            cx,
 3943        );
 3944        multibuffer
 3945    });
 3946
 3947    cx.add_window(|window, cx| {
 3948        let mut editor = build_editor(multibuffer, window, cx);
 3949
 3950        // Cursor in the middle of the first excerpt.
 3951        select_ranges(
 3952            &mut editor,
 3953            indoc! {"
 3954                aˇaa
 3955                bbb
 3956                ccc
 3957                ddd
 3958                eee
 3959                fff"},
 3960            window,
 3961            cx,
 3962        );
 3963        editor.newline_below(&NewlineBelow, window, cx);
 3964        assert_text_with_selections(
 3965            &mut editor,
 3966            indoc! {"
 3967                aaa
 3968                ˇ
 3969                bbb
 3970                ccc
 3971                ddd
 3972                eee
 3973                fff"},
 3974            cx,
 3975        );
 3976        buffer_1.read_with(cx, |buffer, _| {
 3977            assert_eq!(buffer.text(), "aaa\n\nbbb\nccc");
 3978        });
 3979        buffer_2.read_with(cx, |buffer, _| {
 3980            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3981        });
 3982
 3983        editor
 3984    });
 3985}
 3986
 3987#[gpui::test]
 3988fn test_newline_below_multibuffer_last_line_of_last_excerpt(cx: &mut TestAppContext) {
 3989    init_test(cx, |_| {});
 3990
 3991    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3992    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3993    let multibuffer = cx.new(|cx| {
 3994        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3995        multibuffer.set_excerpts_for_path(
 3996            PathKey::sorted(0),
 3997            buffer_1.clone(),
 3998            [Point::new(0, 0)..Point::new(2, 3)],
 3999            0,
 4000            cx,
 4001        );
 4002        multibuffer.set_excerpts_for_path(
 4003            PathKey::sorted(1),
 4004            buffer_2.clone(),
 4005            [Point::new(0, 0)..Point::new(2, 3)],
 4006            0,
 4007            cx,
 4008        );
 4009        multibuffer
 4010    });
 4011
 4012    cx.add_window(|window, cx| {
 4013        let mut editor = build_editor(multibuffer, window, cx);
 4014
 4015        // Cursor on the last line of the last excerpt.
 4016        select_ranges(
 4017            &mut editor,
 4018            indoc! {"
 4019                aaa
 4020                bbb
 4021                ccc
 4022                ddd
 4023                eee
 4024                fˇff"},
 4025            window,
 4026            cx,
 4027        );
 4028        editor.newline_below(&NewlineBelow, window, cx);
 4029        assert_text_with_selections(
 4030            &mut editor,
 4031            indoc! {"
 4032                aaa
 4033                bbb
 4034                ccc
 4035                ddd
 4036                eee
 4037                fff
 4038                ˇ"},
 4039            cx,
 4040        );
 4041        buffer_1.read_with(cx, |buffer, _| {
 4042            assert_eq!(buffer.text(), "aaa\nbbb\nccc");
 4043        });
 4044        buffer_2.read_with(cx, |buffer, _| {
 4045            assert_eq!(buffer.text(), "ddd\neee\nfff\n");
 4046        });
 4047
 4048        editor
 4049    });
 4050}
 4051
 4052#[gpui::test]
 4053fn test_newline_below_multibuffer_multiple_cursors(cx: &mut TestAppContext) {
 4054    init_test(cx, |_| {});
 4055
 4056    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 4057    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 4058    let multibuffer = cx.new(|cx| {
 4059        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4060        multibuffer.set_excerpts_for_path(
 4061            PathKey::sorted(0),
 4062            buffer_1.clone(),
 4063            [Point::new(0, 0)..Point::new(2, 3)],
 4064            0,
 4065            cx,
 4066        );
 4067        multibuffer.set_excerpts_for_path(
 4068            PathKey::sorted(1),
 4069            buffer_2.clone(),
 4070            [Point::new(0, 0)..Point::new(2, 3)],
 4071            0,
 4072            cx,
 4073        );
 4074        multibuffer
 4075    });
 4076
 4077    cx.add_window(|window, cx| {
 4078        let mut editor = build_editor(multibuffer, window, cx);
 4079
 4080        // Cursors on the last line of the first excerpt and the first line
 4081        // of the second excerpt. Each newline should go into its respective buffer.
 4082        select_ranges(
 4083            &mut editor,
 4084            indoc! {"
 4085                aaa
 4086                bbb
 4087                cˇcc
 4088                dˇdd
 4089                eee
 4090                fff"},
 4091            window,
 4092            cx,
 4093        );
 4094        editor.newline_below(&NewlineBelow, window, cx);
 4095        assert_text_with_selections(
 4096            &mut editor,
 4097            indoc! {"
 4098                aaa
 4099                bbb
 4100                ccc
 4101                ˇ
 4102                ddd
 4103                ˇ
 4104                eee
 4105                fff"},
 4106            cx,
 4107        );
 4108        buffer_1.read_with(cx, |buffer, _| {
 4109            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 4110        });
 4111        buffer_2.read_with(cx, |buffer, _| {
 4112            assert_eq!(buffer.text(), "ddd\n\neee\nfff");
 4113        });
 4114
 4115        editor
 4116    });
 4117}
 4118
 4119#[gpui::test]
 4120async fn test_newline_comments(cx: &mut TestAppContext) {
 4121    init_test(cx, |settings| {
 4122        settings.defaults.tab_size = NonZeroU32::new(4)
 4123    });
 4124
 4125    let language = Arc::new(Language::new(
 4126        LanguageConfig {
 4127            line_comments: vec!["// ".into()],
 4128            ..LanguageConfig::default()
 4129        },
 4130        None,
 4131    ));
 4132    {
 4133        let mut cx = EditorTestContext::new(cx).await;
 4134        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4135        cx.set_state(indoc! {"
 4136        // Fooˇ
 4137    "});
 4138
 4139        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4140        cx.assert_editor_state(indoc! {"
 4141        // Foo
 4142        // ˇ
 4143    "});
 4144        // Ensure that we add comment prefix when existing line contains space
 4145        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4146        cx.assert_editor_state(
 4147            indoc! {"
 4148        // Foo
 4149        //s
 4150        // ˇ
 4151    "}
 4152            .replace("s", " ") // s is used as space placeholder to prevent format on save
 4153            .as_str(),
 4154        );
 4155        // Ensure that we add comment prefix when existing line does not contain space
 4156        cx.set_state(indoc! {"
 4157        // Foo
 4158        //ˇ
 4159    "});
 4160        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4161        cx.assert_editor_state(indoc! {"
 4162        // Foo
 4163        //
 4164        // ˇ
 4165    "});
 4166        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 4167        cx.set_state(indoc! {"
 4168        ˇ// Foo
 4169    "});
 4170        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4171        cx.assert_editor_state(indoc! {"
 4172
 4173        ˇ// Foo
 4174    "});
 4175    }
 4176    // Ensure that comment continuations can be disabled.
 4177    update_test_language_settings(cx, &|settings| {
 4178        settings.defaults.extend_comment_on_newline = Some(false);
 4179    });
 4180    let mut cx = EditorTestContext::new(cx).await;
 4181    cx.set_state(indoc! {"
 4182        // Fooˇ
 4183    "});
 4184    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4185    cx.assert_editor_state(indoc! {"
 4186        // Foo
 4187        ˇ
 4188    "});
 4189}
 4190
 4191#[gpui::test]
 4192async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 4193    init_test(cx, |settings| {
 4194        settings.defaults.tab_size = NonZeroU32::new(4)
 4195    });
 4196
 4197    let language = Arc::new(Language::new(
 4198        LanguageConfig {
 4199            line_comments: vec!["// ".into(), "/// ".into()],
 4200            ..LanguageConfig::default()
 4201        },
 4202        None,
 4203    ));
 4204    {
 4205        let mut cx = EditorTestContext::new(cx).await;
 4206        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4207        cx.set_state(indoc! {"
 4208        //ˇ
 4209    "});
 4210        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4211        cx.assert_editor_state(indoc! {"
 4212        //
 4213        // ˇ
 4214    "});
 4215
 4216        cx.set_state(indoc! {"
 4217        ///ˇ
 4218    "});
 4219        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4220        cx.assert_editor_state(indoc! {"
 4221        ///
 4222        /// ˇ
 4223    "});
 4224    }
 4225}
 4226
 4227#[gpui::test]
 4228async fn test_newline_comments_repl_separators(cx: &mut TestAppContext) {
 4229    init_test(cx, |settings| {
 4230        settings.defaults.tab_size = NonZeroU32::new(4)
 4231    });
 4232    let language = Arc::new(Language::new(
 4233        LanguageConfig {
 4234            line_comments: vec!["# ".into()],
 4235            ..LanguageConfig::default()
 4236        },
 4237        None,
 4238    ));
 4239
 4240    {
 4241        let mut cx = EditorTestContext::new(cx).await;
 4242        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4243        cx.set_state(indoc! {"
 4244        # %%ˇ
 4245    "});
 4246        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4247        cx.assert_editor_state(indoc! {"
 4248        # %%
 4249        ˇ
 4250    "});
 4251
 4252        cx.set_state(indoc! {"
 4253            # %%%%%ˇ
 4254    "});
 4255        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4256        cx.assert_editor_state(indoc! {"
 4257            # %%%%%
 4258            ˇ
 4259    "});
 4260
 4261        cx.set_state(indoc! {"
 4262            # %ˇ%
 4263    "});
 4264        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4265        cx.assert_editor_state(indoc! {"
 4266            # %
 4267            # ˇ%
 4268    "});
 4269    }
 4270}
 4271
 4272#[gpui::test]
 4273async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 4274    init_test(cx, |settings| {
 4275        settings.defaults.tab_size = NonZeroU32::new(4)
 4276    });
 4277
 4278    let language = Arc::new(
 4279        Language::new(
 4280            LanguageConfig {
 4281                documentation_comment: Some(language::BlockCommentConfig {
 4282                    start: "/**".into(),
 4283                    end: "*/".into(),
 4284                    prefix: "* ".into(),
 4285                    tab_size: 1,
 4286                }),
 4287
 4288                ..LanguageConfig::default()
 4289            },
 4290            Some(tree_sitter_rust::LANGUAGE.into()),
 4291        )
 4292        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 4293        .unwrap(),
 4294    );
 4295
 4296    {
 4297        let mut cx = EditorTestContext::new(cx).await;
 4298        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4299        cx.set_state(indoc! {"
 4300        /**ˇ
 4301    "});
 4302
 4303        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4304        cx.assert_editor_state(indoc! {"
 4305        /**
 4306         * ˇ
 4307    "});
 4308        // Ensure that if cursor is before the comment start,
 4309        // we do not actually insert a comment prefix.
 4310        cx.set_state(indoc! {"
 4311        ˇ/**
 4312    "});
 4313        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4314        cx.assert_editor_state(indoc! {"
 4315
 4316        ˇ/**
 4317    "});
 4318        // Ensure that if cursor is between it doesn't add comment prefix.
 4319        cx.set_state(indoc! {"
 4320        /*ˇ*
 4321    "});
 4322        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4323        cx.assert_editor_state(indoc! {"
 4324        /*
 4325        ˇ*
 4326    "});
 4327        // Ensure that if suffix exists on same line after cursor it adds new line.
 4328        cx.set_state(indoc! {"
 4329        /**ˇ*/
 4330    "});
 4331        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4332        cx.assert_editor_state(indoc! {"
 4333        /**
 4334         * ˇ
 4335         */
 4336    "});
 4337        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 4338        cx.set_state(indoc! {"
 4339        /**ˇ */
 4340    "});
 4341        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4342        cx.assert_editor_state(indoc! {"
 4343        /**
 4344         * ˇ
 4345         */
 4346    "});
 4347        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 4348        cx.set_state(indoc! {"
 4349        /** ˇ*/
 4350    "});
 4351        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4352        cx.assert_editor_state(
 4353            indoc! {"
 4354        /**s
 4355         * ˇ
 4356         */
 4357    "}
 4358            .replace("s", " ") // s is used as space placeholder to prevent format on save
 4359            .as_str(),
 4360        );
 4361        // Ensure that delimiter space is preserved when newline on already
 4362        // spaced delimiter.
 4363        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4364        cx.assert_editor_state(
 4365            indoc! {"
 4366        /**s
 4367         *s
 4368         * ˇ
 4369         */
 4370    "}
 4371            .replace("s", " ") // s is used as space placeholder to prevent format on save
 4372            .as_str(),
 4373        );
 4374        // Ensure that delimiter space is preserved when space is not
 4375        // on existing delimiter.
 4376        cx.set_state(indoc! {"
 4377        /**
 4378 4379         */
 4380    "});
 4381        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4382        cx.assert_editor_state(indoc! {"
 4383        /**
 4384         *
 4385         * ˇ
 4386         */
 4387    "});
 4388        // Ensure that if suffix exists on same line after cursor it
 4389        // doesn't add extra new line if prefix is not on same line.
 4390        cx.set_state(indoc! {"
 4391        /**
 4392        ˇ*/
 4393    "});
 4394        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4395        cx.assert_editor_state(indoc! {"
 4396        /**
 4397
 4398        ˇ*/
 4399    "});
 4400        // Ensure that it detects suffix after existing prefix.
 4401        cx.set_state(indoc! {"
 4402        /**ˇ/
 4403    "});
 4404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4405        cx.assert_editor_state(indoc! {"
 4406        /**
 4407        ˇ/
 4408    "});
 4409        // Ensure that if suffix exists on same line before
 4410        // cursor it does not add comment prefix.
 4411        cx.set_state(indoc! {"
 4412        /** */ˇ
 4413    "});
 4414        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4415        cx.assert_editor_state(indoc! {"
 4416        /** */
 4417        ˇ
 4418    "});
 4419        // Ensure that if suffix exists on same line before
 4420        // cursor it does not add comment prefix.
 4421        cx.set_state(indoc! {"
 4422        /**
 4423         *
 4424         */ˇ
 4425    "});
 4426        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4427        cx.assert_editor_state(indoc! {"
 4428        /**
 4429         *
 4430         */
 4431         ˇ
 4432    "});
 4433
 4434        // Ensure that inline comment followed by code
 4435        // doesn't add comment prefix on newline
 4436        cx.set_state(indoc! {"
 4437        /** */ textˇ
 4438    "});
 4439        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4440        cx.assert_editor_state(indoc! {"
 4441        /** */ text
 4442        ˇ
 4443    "});
 4444
 4445        // Ensure that text after comment end tag
 4446        // doesn't add comment prefix on newline
 4447        cx.set_state(indoc! {"
 4448        /**
 4449         *
 4450         */ˇtext
 4451    "});
 4452        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4453        cx.assert_editor_state(indoc! {"
 4454        /**
 4455         *
 4456         */
 4457         ˇtext
 4458    "});
 4459
 4460        // Ensure if not comment block it doesn't
 4461        // add comment prefix on newline
 4462        cx.set_state(indoc! {"
 4463        * textˇ
 4464    "});
 4465        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4466        cx.assert_editor_state(indoc! {"
 4467        * text
 4468        ˇ
 4469    "});
 4470    }
 4471    // Ensure that comment continuations can be disabled.
 4472    update_test_language_settings(cx, &|settings| {
 4473        settings.defaults.extend_comment_on_newline = Some(false);
 4474    });
 4475    let mut cx = EditorTestContext::new(cx).await;
 4476    cx.set_state(indoc! {"
 4477        /**ˇ
 4478    "});
 4479    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4480    cx.assert_editor_state(indoc! {"
 4481        /**
 4482        ˇ
 4483    "});
 4484}
 4485
 4486#[gpui::test]
 4487async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 4488    init_test(cx, |settings| {
 4489        settings.defaults.tab_size = NonZeroU32::new(4)
 4490    });
 4491
 4492    let lua_language = Arc::new(Language::new(
 4493        LanguageConfig {
 4494            line_comments: vec!["--".into()],
 4495            block_comment: Some(language::BlockCommentConfig {
 4496                start: "--[[".into(),
 4497                prefix: "".into(),
 4498                end: "]]".into(),
 4499                tab_size: 0,
 4500            }),
 4501            ..LanguageConfig::default()
 4502        },
 4503        None,
 4504    ));
 4505
 4506    let mut cx = EditorTestContext::new(cx).await;
 4507    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 4508
 4509    // Line with line comment should extend
 4510    cx.set_state(indoc! {"
 4511        --ˇ
 4512    "});
 4513    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4514    cx.assert_editor_state(indoc! {"
 4515        --
 4516        --ˇ
 4517    "});
 4518
 4519    // Line with block comment that matches line comment should not extend
 4520    cx.set_state(indoc! {"
 4521        --[[ˇ
 4522    "});
 4523    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4524    cx.assert_editor_state(indoc! {"
 4525        --[[
 4526        ˇ
 4527    "});
 4528}
 4529
 4530#[gpui::test]
 4531fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 4532    init_test(cx, |_| {});
 4533
 4534    let editor = cx.add_window(|window, cx| {
 4535        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 4536        let mut editor = build_editor(buffer, window, cx);
 4537        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4538            s.select_ranges([
 4539                MultiBufferOffset(3)..MultiBufferOffset(4),
 4540                MultiBufferOffset(11)..MultiBufferOffset(12),
 4541                MultiBufferOffset(19)..MultiBufferOffset(20),
 4542            ])
 4543        });
 4544        editor
 4545    });
 4546
 4547    _ = editor.update(cx, |editor, window, cx| {
 4548        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 4549        editor.buffer.update(cx, |buffer, cx| {
 4550            buffer.edit(
 4551                [
 4552                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 4553                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 4554                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 4555                ],
 4556                None,
 4557                cx,
 4558            );
 4559            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 4560        });
 4561        assert_eq!(
 4562            editor.selections.ranges(&editor.display_snapshot(cx)),
 4563            &[
 4564                MultiBufferOffset(2)..MultiBufferOffset(2),
 4565                MultiBufferOffset(7)..MultiBufferOffset(7),
 4566                MultiBufferOffset(12)..MultiBufferOffset(12)
 4567            ],
 4568        );
 4569
 4570        editor.insert("Z", window, cx);
 4571        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 4572
 4573        // The selections are moved after the inserted characters
 4574        assert_eq!(
 4575            editor.selections.ranges(&editor.display_snapshot(cx)),
 4576            &[
 4577                MultiBufferOffset(3)..MultiBufferOffset(3),
 4578                MultiBufferOffset(9)..MultiBufferOffset(9),
 4579                MultiBufferOffset(15)..MultiBufferOffset(15)
 4580            ],
 4581        );
 4582    });
 4583}
 4584
 4585#[gpui::test]
 4586async fn test_tab(cx: &mut TestAppContext) {
 4587    init_test(cx, |settings| {
 4588        settings.defaults.tab_size = NonZeroU32::new(3)
 4589    });
 4590
 4591    let mut cx = EditorTestContext::new(cx).await;
 4592    cx.set_state(indoc! {"
 4593        ˇabˇc
 4594        ˇ🏀ˇ🏀ˇefg
 4595 4596    "});
 4597    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4598    cx.assert_editor_state(indoc! {"
 4599           ˇab ˇc
 4600           ˇ🏀  ˇ🏀  ˇefg
 4601        d  ˇ
 4602    "});
 4603
 4604    cx.set_state(indoc! {"
 4605        a
 4606        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4607    "});
 4608    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4609    cx.assert_editor_state(indoc! {"
 4610        a
 4611           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4612    "});
 4613}
 4614
 4615#[gpui::test]
 4616async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 4617    init_test(cx, |_| {});
 4618
 4619    let mut cx = EditorTestContext::new(cx).await;
 4620    let language = Arc::new(
 4621        Language::new(
 4622            LanguageConfig::default(),
 4623            Some(tree_sitter_rust::LANGUAGE.into()),
 4624        )
 4625        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 4626        .unwrap(),
 4627    );
 4628    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4629
 4630    // test when all cursors are not at suggested indent
 4631    // then simply move to their suggested indent location
 4632    cx.set_state(indoc! {"
 4633        const a: B = (
 4634            c(
 4635        ˇ
 4636        ˇ    )
 4637        );
 4638    "});
 4639    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4640    cx.assert_editor_state(indoc! {"
 4641        const a: B = (
 4642            c(
 4643                ˇ
 4644            ˇ)
 4645        );
 4646    "});
 4647
 4648    // test cursor already at suggested indent not moving when
 4649    // other cursors are yet to reach their suggested indents
 4650    cx.set_state(indoc! {"
 4651        ˇ
 4652        const a: B = (
 4653            c(
 4654                d(
 4655        ˇ
 4656                )
 4657        ˇ
 4658        ˇ    )
 4659        );
 4660    "});
 4661    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4662    cx.assert_editor_state(indoc! {"
 4663        ˇ
 4664        const a: B = (
 4665            c(
 4666                d(
 4667                    ˇ
 4668                )
 4669                ˇ
 4670            ˇ)
 4671        );
 4672    "});
 4673    // test when all cursors are at suggested indent then tab is inserted
 4674    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4675    cx.assert_editor_state(indoc! {"
 4676            ˇ
 4677        const a: B = (
 4678            c(
 4679                d(
 4680                        ˇ
 4681                )
 4682                    ˇ
 4683                ˇ)
 4684        );
 4685    "});
 4686
 4687    // test when current indent is less than suggested indent,
 4688    // we adjust line to match suggested indent and move cursor to it
 4689    //
 4690    // when no other cursor is at word boundary, all of them should move
 4691    cx.set_state(indoc! {"
 4692        const a: B = (
 4693            c(
 4694                d(
 4695        ˇ
 4696        ˇ   )
 4697        ˇ   )
 4698        );
 4699    "});
 4700    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4701    cx.assert_editor_state(indoc! {"
 4702        const a: B = (
 4703            c(
 4704                d(
 4705                    ˇ
 4706                ˇ)
 4707            ˇ)
 4708        );
 4709    "});
 4710
 4711    // test when current indent is less than suggested indent,
 4712    // we adjust line to match suggested indent and move cursor to it
 4713    //
 4714    // when some other cursor is at word boundary, it should not move
 4715    cx.set_state(indoc! {"
 4716        const a: B = (
 4717            c(
 4718                d(
 4719        ˇ
 4720        ˇ   )
 4721           ˇ)
 4722        );
 4723    "});
 4724    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4725    cx.assert_editor_state(indoc! {"
 4726        const a: B = (
 4727            c(
 4728                d(
 4729                    ˇ
 4730                ˇ)
 4731            ˇ)
 4732        );
 4733    "});
 4734
 4735    // test when current indent is more than suggested indent,
 4736    // we just move cursor to current indent instead of suggested indent
 4737    //
 4738    // when no other cursor is at word boundary, all of them should move
 4739    cx.set_state(indoc! {"
 4740        const a: B = (
 4741            c(
 4742                d(
 4743        ˇ
 4744        ˇ                )
 4745        ˇ   )
 4746        );
 4747    "});
 4748    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4749    cx.assert_editor_state(indoc! {"
 4750        const a: B = (
 4751            c(
 4752                d(
 4753                    ˇ
 4754                        ˇ)
 4755            ˇ)
 4756        );
 4757    "});
 4758    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4759    cx.assert_editor_state(indoc! {"
 4760        const a: B = (
 4761            c(
 4762                d(
 4763                        ˇ
 4764                            ˇ)
 4765                ˇ)
 4766        );
 4767    "});
 4768
 4769    // test when current indent is more than suggested indent,
 4770    // we just move cursor to current indent instead of suggested indent
 4771    //
 4772    // when some other cursor is at word boundary, it doesn't move
 4773    cx.set_state(indoc! {"
 4774        const a: B = (
 4775            c(
 4776                d(
 4777        ˇ
 4778        ˇ                )
 4779            ˇ)
 4780        );
 4781    "});
 4782    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4783    cx.assert_editor_state(indoc! {"
 4784        const a: B = (
 4785            c(
 4786                d(
 4787                    ˇ
 4788                        ˇ)
 4789            ˇ)
 4790        );
 4791    "});
 4792
 4793    // handle auto-indent when there are multiple cursors on the same line
 4794    cx.set_state(indoc! {"
 4795        const a: B = (
 4796            c(
 4797        ˇ    ˇ
 4798        ˇ    )
 4799        );
 4800    "});
 4801    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4802    cx.assert_editor_state(indoc! {"
 4803        const a: B = (
 4804            c(
 4805                ˇ
 4806            ˇ)
 4807        );
 4808    "});
 4809}
 4810
 4811#[gpui::test]
 4812async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4813    init_test(cx, |settings| {
 4814        settings.defaults.tab_size = NonZeroU32::new(3)
 4815    });
 4816
 4817    let mut cx = EditorTestContext::new(cx).await;
 4818    cx.set_state(indoc! {"
 4819         ˇ
 4820        \t ˇ
 4821        \t  ˇ
 4822        \t   ˇ
 4823         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4824    "});
 4825
 4826    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4827    cx.assert_editor_state(indoc! {"
 4828           ˇ
 4829        \t   ˇ
 4830        \t   ˇ
 4831        \t      ˇ
 4832         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4833    "});
 4834}
 4835
 4836#[gpui::test]
 4837async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4838    init_test(cx, |settings| {
 4839        settings.defaults.tab_size = NonZeroU32::new(4)
 4840    });
 4841
 4842    let language = Arc::new(
 4843        Language::new(
 4844            LanguageConfig::default(),
 4845            Some(tree_sitter_rust::LANGUAGE.into()),
 4846        )
 4847        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4848        .unwrap(),
 4849    );
 4850
 4851    let mut cx = EditorTestContext::new(cx).await;
 4852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4853    cx.set_state(indoc! {"
 4854        fn a() {
 4855            if b {
 4856        \t ˇc
 4857            }
 4858        }
 4859    "});
 4860
 4861    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4862    cx.assert_editor_state(indoc! {"
 4863        fn a() {
 4864            if b {
 4865                ˇc
 4866            }
 4867        }
 4868    "});
 4869}
 4870
 4871#[gpui::test]
 4872async fn test_indent_outdent(cx: &mut TestAppContext) {
 4873    init_test(cx, |settings| {
 4874        settings.defaults.tab_size = NonZeroU32::new(4);
 4875    });
 4876
 4877    let mut cx = EditorTestContext::new(cx).await;
 4878
 4879    cx.set_state(indoc! {"
 4880          «oneˇ» «twoˇ»
 4881        three
 4882         four
 4883    "});
 4884    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4885    cx.assert_editor_state(indoc! {"
 4886            «oneˇ» «twoˇ»
 4887        three
 4888         four
 4889    "});
 4890
 4891    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4892    cx.assert_editor_state(indoc! {"
 4893        «oneˇ» «twoˇ»
 4894        three
 4895         four
 4896    "});
 4897
 4898    // select across line ending
 4899    cx.set_state(indoc! {"
 4900        one two
 4901        t«hree
 4902        ˇ» four
 4903    "});
 4904    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4905    cx.assert_editor_state(indoc! {"
 4906        one two
 4907            t«hree
 4908        ˇ» four
 4909    "});
 4910
 4911    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4912    cx.assert_editor_state(indoc! {"
 4913        one two
 4914        t«hree
 4915        ˇ» four
 4916    "});
 4917
 4918    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4919    cx.set_state(indoc! {"
 4920        one two
 4921        ˇthree
 4922            four
 4923    "});
 4924    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4925    cx.assert_editor_state(indoc! {"
 4926        one two
 4927            ˇthree
 4928            four
 4929    "});
 4930
 4931    cx.set_state(indoc! {"
 4932        one two
 4933        ˇ    three
 4934            four
 4935    "});
 4936    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4937    cx.assert_editor_state(indoc! {"
 4938        one two
 4939        ˇthree
 4940            four
 4941    "});
 4942}
 4943
 4944#[gpui::test]
 4945async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4946    // This is a regression test for issue #33761
 4947    init_test(cx, |_| {});
 4948
 4949    let mut cx = EditorTestContext::new(cx).await;
 4950    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4951    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4952
 4953    cx.set_state(
 4954        r#"ˇ#     ingress:
 4955ˇ#         api:
 4956ˇ#             enabled: false
 4957ˇ#             pathType: Prefix
 4958ˇ#           console:
 4959ˇ#               enabled: false
 4960ˇ#               pathType: Prefix
 4961"#,
 4962    );
 4963
 4964    // Press tab to indent all lines
 4965    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4966
 4967    cx.assert_editor_state(
 4968        r#"    ˇ#     ingress:
 4969    ˇ#         api:
 4970    ˇ#             enabled: false
 4971    ˇ#             pathType: Prefix
 4972    ˇ#           console:
 4973    ˇ#               enabled: false
 4974    ˇ#               pathType: Prefix
 4975"#,
 4976    );
 4977}
 4978
 4979#[gpui::test]
 4980async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4981    // This is a test to make sure our fix for issue #33761 didn't break anything
 4982    init_test(cx, |_| {});
 4983
 4984    let mut cx = EditorTestContext::new(cx).await;
 4985    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4986    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4987
 4988    cx.set_state(
 4989        r#"ˇingress:
 4990ˇ  api:
 4991ˇ    enabled: false
 4992ˇ    pathType: Prefix
 4993"#,
 4994    );
 4995
 4996    // Press tab to indent all lines
 4997    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4998
 4999    cx.assert_editor_state(
 5000        r#"ˇingress:
 5001    ˇapi:
 5002        ˇenabled: false
 5003        ˇpathType: Prefix
 5004"#,
 5005    );
 5006}
 5007
 5008#[gpui::test]
 5009async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 5010    init_test(cx, |settings| {
 5011        settings.defaults.hard_tabs = Some(true);
 5012    });
 5013
 5014    let mut cx = EditorTestContext::new(cx).await;
 5015
 5016    // select two ranges on one line
 5017    cx.set_state(indoc! {"
 5018        «oneˇ» «twoˇ»
 5019        three
 5020        four
 5021    "});
 5022    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 5023    cx.assert_editor_state(indoc! {"
 5024        \t«oneˇ» «twoˇ»
 5025        three
 5026        four
 5027    "});
 5028    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 5029    cx.assert_editor_state(indoc! {"
 5030        \t\t«oneˇ» «twoˇ»
 5031        three
 5032        four
 5033    "});
 5034    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5035    cx.assert_editor_state(indoc! {"
 5036        \t«oneˇ» «twoˇ»
 5037        three
 5038        four
 5039    "});
 5040    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5041    cx.assert_editor_state(indoc! {"
 5042        «oneˇ» «twoˇ»
 5043        three
 5044        four
 5045    "});
 5046
 5047    // select across a line ending
 5048    cx.set_state(indoc! {"
 5049        one two
 5050        t«hree
 5051        ˇ»four
 5052    "});
 5053    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 5054    cx.assert_editor_state(indoc! {"
 5055        one two
 5056        \tt«hree
 5057        ˇ»four
 5058    "});
 5059    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 5060    cx.assert_editor_state(indoc! {"
 5061        one two
 5062        \t\tt«hree
 5063        ˇ»four
 5064    "});
 5065    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5066    cx.assert_editor_state(indoc! {"
 5067        one two
 5068        \tt«hree
 5069        ˇ»four
 5070    "});
 5071    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5072    cx.assert_editor_state(indoc! {"
 5073        one two
 5074        t«hree
 5075        ˇ»four
 5076    "});
 5077
 5078    // Ensure that indenting/outdenting works when the cursor is at column 0.
 5079    cx.set_state(indoc! {"
 5080        one two
 5081        ˇthree
 5082        four
 5083    "});
 5084    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5085    cx.assert_editor_state(indoc! {"
 5086        one two
 5087        ˇthree
 5088        four
 5089    "});
 5090    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 5091    cx.assert_editor_state(indoc! {"
 5092        one two
 5093        \tˇthree
 5094        four
 5095    "});
 5096    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 5097    cx.assert_editor_state(indoc! {"
 5098        one two
 5099        ˇthree
 5100        four
 5101    "});
 5102}
 5103
 5104#[gpui::test]
 5105fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 5106    init_test(cx, |settings| {
 5107        settings.languages.0.extend([
 5108            (
 5109                "TOML".into(),
 5110                LanguageSettingsContent {
 5111                    tab_size: NonZeroU32::new(2),
 5112                    ..Default::default()
 5113                },
 5114            ),
 5115            (
 5116                "Rust".into(),
 5117                LanguageSettingsContent {
 5118                    tab_size: NonZeroU32::new(4),
 5119                    ..Default::default()
 5120                },
 5121            ),
 5122        ]);
 5123    });
 5124
 5125    let toml_language = Arc::new(Language::new(
 5126        LanguageConfig {
 5127            name: "TOML".into(),
 5128            ..Default::default()
 5129        },
 5130        None,
 5131    ));
 5132    let rust_language = Arc::new(Language::new(
 5133        LanguageConfig {
 5134            name: "Rust".into(),
 5135            ..Default::default()
 5136        },
 5137        None,
 5138    ));
 5139
 5140    let toml_buffer =
 5141        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 5142    let rust_buffer =
 5143        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 5144    let multibuffer = cx.new(|cx| {
 5145        let mut multibuffer = MultiBuffer::new(ReadWrite);
 5146        multibuffer.set_excerpts_for_path(
 5147            PathKey::sorted(0),
 5148            toml_buffer.clone(),
 5149            [Point::new(0, 0)..Point::new(2, 0)],
 5150            0,
 5151            cx,
 5152        );
 5153        multibuffer.set_excerpts_for_path(
 5154            PathKey::sorted(1),
 5155            rust_buffer.clone(),
 5156            [Point::new(0, 0)..Point::new(1, 0)],
 5157            0,
 5158            cx,
 5159        );
 5160        multibuffer
 5161    });
 5162
 5163    cx.add_window(|window, cx| {
 5164        let mut editor = build_editor(multibuffer, window, cx);
 5165
 5166        assert_eq!(
 5167            editor.text(cx),
 5168            indoc! {"
 5169                a = 1
 5170                b = 2
 5171
 5172                const c: usize = 3;
 5173            "}
 5174        );
 5175
 5176        select_ranges(
 5177            &mut editor,
 5178            indoc! {"
 5179                «aˇ» = 1
 5180                b = 2
 5181
 5182                «const c:ˇ» usize = 3;
 5183            "},
 5184            window,
 5185            cx,
 5186        );
 5187
 5188        editor.tab(&Tab, window, cx);
 5189        assert_text_with_selections(
 5190            &mut editor,
 5191            indoc! {"
 5192                  «aˇ» = 1
 5193                b = 2
 5194
 5195                    «const c:ˇ» usize = 3;
 5196            "},
 5197            cx,
 5198        );
 5199        editor.backtab(&Backtab, window, cx);
 5200        assert_text_with_selections(
 5201            &mut editor,
 5202            indoc! {"
 5203                «aˇ» = 1
 5204                b = 2
 5205
 5206                «const c:ˇ» usize = 3;
 5207            "},
 5208            cx,
 5209        );
 5210
 5211        editor
 5212    });
 5213}
 5214
 5215#[gpui::test]
 5216async fn test_backspace(cx: &mut TestAppContext) {
 5217    init_test(cx, |_| {});
 5218
 5219    let mut cx = EditorTestContext::new(cx).await;
 5220
 5221    // Basic backspace
 5222    cx.set_state(indoc! {"
 5223        onˇe two three
 5224        fou«rˇ» five six
 5225        seven «ˇeight nine
 5226        »ten
 5227    "});
 5228    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 5229    cx.assert_editor_state(indoc! {"
 5230        oˇe two three
 5231        fouˇ five six
 5232        seven ˇten
 5233    "});
 5234
 5235    // Test backspace inside and around indents
 5236    cx.set_state(indoc! {"
 5237        zero
 5238            ˇone
 5239                ˇtwo
 5240            ˇ ˇ ˇ  three
 5241        ˇ  ˇ  four
 5242    "});
 5243    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 5244    cx.assert_editor_state(indoc! {"
 5245        zero
 5246        ˇone
 5247            ˇtwo
 5248        ˇ  threeˇ  four
 5249    "});
 5250}
 5251
 5252#[gpui::test]
 5253async fn test_delete(cx: &mut TestAppContext) {
 5254    init_test(cx, |_| {});
 5255
 5256    let mut cx = EditorTestContext::new(cx).await;
 5257    cx.set_state(indoc! {"
 5258        onˇe two three
 5259        fou«rˇ» five six
 5260        seven «ˇeight nine
 5261        »ten
 5262    "});
 5263    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 5264    cx.assert_editor_state(indoc! {"
 5265        onˇ two three
 5266        fouˇ five six
 5267        seven ˇten
 5268    "});
 5269}
 5270
 5271#[gpui::test]
 5272fn test_delete_line(cx: &mut TestAppContext) {
 5273    init_test(cx, |_| {});
 5274
 5275    let editor = cx.add_window(|window, cx| {
 5276        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5277        build_editor(buffer, window, cx)
 5278    });
 5279    _ = editor.update(cx, |editor, window, cx| {
 5280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5281            s.select_display_ranges([
 5282                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5283                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5284                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5285            ])
 5286        });
 5287        editor.delete_line(&DeleteLine, window, cx);
 5288        assert_eq!(editor.display_text(cx), "ghi");
 5289        assert_eq!(
 5290            display_ranges(editor, cx),
 5291            vec![
 5292                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 5293                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5294            ]
 5295        );
 5296    });
 5297
 5298    let editor = cx.add_window(|window, cx| {
 5299        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5300        build_editor(buffer, window, cx)
 5301    });
 5302    _ = editor.update(cx, |editor, window, cx| {
 5303        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5304            s.select_display_ranges([
 5305                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5306            ])
 5307        });
 5308        editor.delete_line(&DeleteLine, window, cx);
 5309        assert_eq!(editor.display_text(cx), "ghi\n");
 5310        assert_eq!(
 5311            display_ranges(editor, cx),
 5312            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 5313        );
 5314    });
 5315
 5316    let editor = cx.add_window(|window, cx| {
 5317        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 5318        build_editor(buffer, window, cx)
 5319    });
 5320    _ = editor.update(cx, |editor, window, cx| {
 5321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5322            s.select_display_ranges([
 5323                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 5324            ])
 5325        });
 5326        editor.delete_line(&DeleteLine, window, cx);
 5327        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 5328        assert_eq!(
 5329            display_ranges(editor, cx),
 5330            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 5331        );
 5332    });
 5333}
 5334
 5335#[gpui::test]
 5336fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 5337    init_test(cx, |_| {});
 5338
 5339    cx.add_window(|window, cx| {
 5340        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 5341        let mut editor = build_editor(buffer.clone(), window, cx);
 5342        let buffer = buffer.read(cx).as_singleton().unwrap();
 5343
 5344        assert_eq!(
 5345            editor
 5346                .selections
 5347                .ranges::<Point>(&editor.display_snapshot(cx)),
 5348            &[Point::new(0, 0)..Point::new(0, 0)]
 5349        );
 5350
 5351        // When on single line, replace newline at end by space
 5352        editor.join_lines(&JoinLines, window, cx);
 5353        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 5354        assert_eq!(
 5355            editor
 5356                .selections
 5357                .ranges::<Point>(&editor.display_snapshot(cx)),
 5358            &[Point::new(0, 3)..Point::new(0, 3)]
 5359        );
 5360
 5361        editor.undo(&Undo, window, cx);
 5362        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n\n");
 5363
 5364        // Select a full line, i.e. start of the first line to the start of the second line
 5365        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5366            s.select_ranges([Point::new(0, 0)..Point::new(1, 0)])
 5367        });
 5368        editor.join_lines(&JoinLines, window, cx);
 5369        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 5370
 5371        editor.undo(&Undo, window, cx);
 5372        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n\n");
 5373
 5374        // Select two full lines
 5375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5376            s.select_ranges([Point::new(0, 0)..Point::new(2, 0)])
 5377        });
 5378        editor.join_lines(&JoinLines, window, cx);
 5379
 5380        // Only the selected lines should be joined, not the third.
 5381        assert_eq!(
 5382            buffer.read(cx).text(),
 5383            "aaa bbb\nccc\nddd\n\n",
 5384            "only the two selected lines (a and b) should be joined"
 5385        );
 5386
 5387        // When multiple lines are selected, remove newlines that are spanned by the selection
 5388        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5389            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 5390        });
 5391        editor.join_lines(&JoinLines, window, cx);
 5392        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 5393        assert_eq!(
 5394            editor
 5395                .selections
 5396                .ranges::<Point>(&editor.display_snapshot(cx)),
 5397            &[Point::new(0, 11)..Point::new(0, 11)]
 5398        );
 5399
 5400        // Undo should be transactional
 5401        editor.undo(&Undo, window, cx);
 5402        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 5403        assert_eq!(
 5404            editor
 5405                .selections
 5406                .ranges::<Point>(&editor.display_snapshot(cx)),
 5407            &[Point::new(0, 5)..Point::new(2, 2)]
 5408        );
 5409
 5410        // When joining an empty line don't insert a space
 5411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5412            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 5413        });
 5414        editor.join_lines(&JoinLines, window, cx);
 5415        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 5416        assert_eq!(
 5417            editor
 5418                .selections
 5419                .ranges::<Point>(&editor.display_snapshot(cx)),
 5420            [Point::new(2, 3)..Point::new(2, 3)]
 5421        );
 5422
 5423        // We can remove trailing newlines
 5424        editor.join_lines(&JoinLines, window, cx);
 5425        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 5426        assert_eq!(
 5427            editor
 5428                .selections
 5429                .ranges::<Point>(&editor.display_snapshot(cx)),
 5430            [Point::new(2, 3)..Point::new(2, 3)]
 5431        );
 5432
 5433        // We don't blow up on the last line
 5434        editor.join_lines(&JoinLines, window, cx);
 5435        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 5436        assert_eq!(
 5437            editor
 5438                .selections
 5439                .ranges::<Point>(&editor.display_snapshot(cx)),
 5440            [Point::new(2, 3)..Point::new(2, 3)]
 5441        );
 5442
 5443        // reset to test indentation
 5444        editor.buffer.update(cx, |buffer, cx| {
 5445            buffer.edit(
 5446                [
 5447                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 5448                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 5449                ],
 5450                None,
 5451                cx,
 5452            )
 5453        });
 5454
 5455        // We remove any leading spaces
 5456        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 5457        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5458            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 5459        });
 5460        editor.join_lines(&JoinLines, window, cx);
 5461        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 5462
 5463        // We don't insert a space for a line containing only spaces
 5464        editor.join_lines(&JoinLines, window, cx);
 5465        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 5466
 5467        // We ignore any leading tabs
 5468        editor.join_lines(&JoinLines, window, cx);
 5469        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 5470
 5471        editor
 5472    });
 5473}
 5474
 5475#[gpui::test]
 5476fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 5477    init_test(cx, |_| {});
 5478
 5479    cx.add_window(|window, cx| {
 5480        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 5481        let mut editor = build_editor(buffer.clone(), window, cx);
 5482        let buffer = buffer.read(cx).as_singleton().unwrap();
 5483
 5484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5485            s.select_ranges([
 5486                Point::new(0, 2)..Point::new(1, 1),
 5487                Point::new(1, 2)..Point::new(1, 2),
 5488                Point::new(3, 1)..Point::new(3, 2),
 5489            ])
 5490        });
 5491
 5492        editor.join_lines(&JoinLines, window, cx);
 5493        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 5494
 5495        assert_eq!(
 5496            editor
 5497                .selections
 5498                .ranges::<Point>(&editor.display_snapshot(cx)),
 5499            [
 5500                Point::new(0, 7)..Point::new(0, 7),
 5501                Point::new(1, 3)..Point::new(1, 3)
 5502            ]
 5503        );
 5504        editor
 5505    });
 5506}
 5507
 5508#[gpui::test]
 5509async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 5510    init_test(cx, |_| {});
 5511
 5512    let mut cx = EditorTestContext::new(cx).await;
 5513
 5514    let diff_base = r#"
 5515        Line 0
 5516        Line 1
 5517        Line 2
 5518        Line 3
 5519        "#
 5520    .unindent();
 5521
 5522    cx.set_state(
 5523        &r#"
 5524        ˇLine 0
 5525        Line 1
 5526        Line 2
 5527        Line 3
 5528        "#
 5529        .unindent(),
 5530    );
 5531
 5532    cx.set_head_text(&diff_base);
 5533    executor.run_until_parked();
 5534
 5535    // Join lines
 5536    cx.update_editor(|editor, window, cx| {
 5537        editor.join_lines(&JoinLines, window, cx);
 5538    });
 5539    executor.run_until_parked();
 5540
 5541    cx.assert_editor_state(
 5542        &r#"
 5543        Line 0ˇ Line 1
 5544        Line 2
 5545        Line 3
 5546        "#
 5547        .unindent(),
 5548    );
 5549    // Join again
 5550    cx.update_editor(|editor, window, cx| {
 5551        editor.join_lines(&JoinLines, window, cx);
 5552    });
 5553    executor.run_until_parked();
 5554
 5555    cx.assert_editor_state(
 5556        &r#"
 5557        Line 0 Line 1ˇ Line 2
 5558        Line 3
 5559        "#
 5560        .unindent(),
 5561    );
 5562}
 5563
 5564#[gpui::test]
 5565async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) {
 5566    init_test(cx, |_| {});
 5567
 5568    {
 5569        let language = Arc::new(Language::new(
 5570            LanguageConfig {
 5571                line_comments: vec!["// ".into(), "/// ".into()],
 5572                documentation_comment: Some(BlockCommentConfig {
 5573                    start: "/*".into(),
 5574                    end: "*/".into(),
 5575                    prefix: "* ".into(),
 5576                    tab_size: 1,
 5577                }),
 5578                ..LanguageConfig::default()
 5579            },
 5580            None,
 5581        ));
 5582
 5583        let mut cx = EditorTestContext::new(cx).await;
 5584        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5585
 5586        // Strips the comment prefix (with trailing space) from the joined-in line.
 5587        cx.set_state(indoc! {"
 5588            // ˇfoo
 5589            // bar
 5590        "});
 5591        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5592        cx.assert_editor_state(indoc! {"
 5593            // fooˇ bar
 5594        "});
 5595
 5596        // Strips the longer doc-comment prefix when both `//` and `///` match.
 5597        cx.set_state(indoc! {"
 5598            /// ˇfoo
 5599            /// bar
 5600        "});
 5601        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5602        cx.assert_editor_state(indoc! {"
 5603            /// fooˇ bar
 5604        "});
 5605
 5606        // Does not strip when the second line is a regular line (no comment prefix).
 5607        cx.set_state(indoc! {"
 5608            // ˇfoo
 5609            bar
 5610        "});
 5611        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5612        cx.assert_editor_state(indoc! {"
 5613            // fooˇ bar
 5614        "});
 5615
 5616        // No-whitespace join also strips the comment prefix.
 5617        cx.set_state(indoc! {"
 5618            // ˇfoo
 5619            // bar
 5620        "});
 5621        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5622        cx.assert_editor_state(indoc! {"
 5623            // fooˇbar
 5624        "});
 5625
 5626        // Strips even when the joined-in line is just the bare prefix (no trailing space).
 5627        cx.set_state(indoc! {"
 5628            // ˇfoo
 5629            //
 5630        "});
 5631        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5632        cx.assert_editor_state(indoc! {"
 5633            // fooˇ
 5634        "});
 5635
 5636        // Mixed line comment prefix types: the longer matching prefix is stripped.
 5637        cx.set_state(indoc! {"
 5638            // ˇfoo
 5639            /// bar
 5640        "});
 5641        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5642        cx.assert_editor_state(indoc! {"
 5643            // fooˇ bar
 5644        "});
 5645
 5646        // Strips block comment body prefix (`* `) from the joined-in line.
 5647        cx.set_state(indoc! {"
 5648             * ˇfoo
 5649             * bar
 5650        "});
 5651        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5652        cx.assert_editor_state(indoc! {"
 5653             * fooˇ bar
 5654        "});
 5655
 5656        // Strips bare block comment body prefix (`*` without trailing space).
 5657        cx.set_state(indoc! {"
 5658             * ˇfoo
 5659             *
 5660        "});
 5661        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5662        cx.assert_editor_state(indoc! {"
 5663             * fooˇ
 5664        "});
 5665    }
 5666
 5667    {
 5668        let markdown_language = Arc::new(Language::new(
 5669            LanguageConfig {
 5670                unordered_list: vec!["- ".into(), "* ".into(), "+ ".into()],
 5671                ..LanguageConfig::default()
 5672            },
 5673            None,
 5674        ));
 5675
 5676        let mut cx = EditorTestContext::new(cx).await;
 5677        cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
 5678
 5679        // Strips the `- ` list marker from the joined-in line.
 5680        cx.set_state(indoc! {"
 5681            - ˇfoo
 5682            - bar
 5683        "});
 5684        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5685        cx.assert_editor_state(indoc! {"
 5686            - fooˇ bar
 5687        "});
 5688
 5689        // Strips the `* ` list marker from the joined-in line.
 5690        cx.set_state(indoc! {"
 5691            * ˇfoo
 5692            * bar
 5693        "});
 5694        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5695        cx.assert_editor_state(indoc! {"
 5696            * fooˇ bar
 5697        "});
 5698
 5699        // Strips the `+ ` list marker from the joined-in line.
 5700        cx.set_state(indoc! {"
 5701            + ˇfoo
 5702            + bar
 5703        "});
 5704        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5705        cx.assert_editor_state(indoc! {"
 5706            + fooˇ bar
 5707        "});
 5708
 5709        // No-whitespace join also strips the list marker.
 5710        cx.set_state(indoc! {"
 5711            - ˇfoo
 5712            - bar
 5713        "});
 5714        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5715        cx.assert_editor_state(indoc! {"
 5716            - fooˇbar
 5717        "});
 5718    }
 5719}
 5720
 5721#[gpui::test]
 5722async fn test_custom_newlines_cause_no_false_positive_diffs(
 5723    executor: BackgroundExecutor,
 5724    cx: &mut TestAppContext,
 5725) {
 5726    init_test(cx, |_| {});
 5727    let mut cx = EditorTestContext::new(cx).await;
 5728    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 5729    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 5730    executor.run_until_parked();
 5731
 5732    cx.update_editor(|editor, window, cx| {
 5733        let snapshot = editor.snapshot(window, cx);
 5734        assert_eq!(
 5735            snapshot
 5736                .buffer_snapshot()
 5737                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 5738                .collect::<Vec<_>>(),
 5739            Vec::new(),
 5740            "Should not have any diffs for files with custom newlines"
 5741        );
 5742    });
 5743}
 5744
 5745#[gpui::test]
 5746async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 5747    init_test(cx, |_| {});
 5748
 5749    let mut cx = EditorTestContext::new(cx).await;
 5750
 5751    // Test sort_lines_case_insensitive()
 5752    cx.set_state(indoc! {"
 5753        «z
 5754        y
 5755        x
 5756        Z
 5757        Y
 5758        Xˇ»
 5759    "});
 5760    cx.update_editor(|e, window, cx| {
 5761        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 5762    });
 5763    cx.assert_editor_state(indoc! {"
 5764        «x
 5765        X
 5766        y
 5767        Y
 5768        z
 5769        Zˇ»
 5770    "});
 5771
 5772    // Test sort_lines_by_length()
 5773    //
 5774    // Demonstrates:
 5775    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 5776    // - sort is stable
 5777    cx.set_state(indoc! {"
 5778        «123
 5779        æ
 5780        12
 5781 5782        1
 5783        æˇ»
 5784    "});
 5785    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 5786    cx.assert_editor_state(indoc! {"
 5787        «æ
 5788 5789        1
 5790        æ
 5791        12
 5792        123ˇ»
 5793    "});
 5794
 5795    // Test reverse_lines()
 5796    cx.set_state(indoc! {"
 5797        «5
 5798        4
 5799        3
 5800        2
 5801        1ˇ»
 5802    "});
 5803    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 5804    cx.assert_editor_state(indoc! {"
 5805        «1
 5806        2
 5807        3
 5808        4
 5809        5ˇ»
 5810    "});
 5811
 5812    // Skip testing shuffle_line()
 5813
 5814    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 5815    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 5816
 5817    // Don't manipulate when cursor is on single line, but expand the selection
 5818    cx.set_state(indoc! {"
 5819        ddˇdd
 5820        ccc
 5821        bb
 5822        a
 5823    "});
 5824    cx.update_editor(|e, window, cx| {
 5825        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5826    });
 5827    cx.assert_editor_state(indoc! {"
 5828        «ddddˇ»
 5829        ccc
 5830        bb
 5831        a
 5832    "});
 5833
 5834    // Basic manipulate case
 5835    // Start selection moves to column 0
 5836    // End of selection shrinks to fit shorter line
 5837    cx.set_state(indoc! {"
 5838        dd«d
 5839        ccc
 5840        bb
 5841        aaaaaˇ»
 5842    "});
 5843    cx.update_editor(|e, window, cx| {
 5844        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5845    });
 5846    cx.assert_editor_state(indoc! {"
 5847        «aaaaa
 5848        bb
 5849        ccc
 5850        dddˇ»
 5851    "});
 5852
 5853    // Manipulate case with newlines
 5854    cx.set_state(indoc! {"
 5855        dd«d
 5856        ccc
 5857
 5858        bb
 5859        aaaaa
 5860
 5861        ˇ»
 5862    "});
 5863    cx.update_editor(|e, window, cx| {
 5864        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5865    });
 5866    cx.assert_editor_state(indoc! {"
 5867        «
 5868
 5869        aaaaa
 5870        bb
 5871        ccc
 5872        dddˇ»
 5873
 5874    "});
 5875
 5876    // Adding new line
 5877    cx.set_state(indoc! {"
 5878        aa«a
 5879        bbˇ»b
 5880    "});
 5881    cx.update_editor(|e, window, cx| {
 5882        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 5883    });
 5884    cx.assert_editor_state(indoc! {"
 5885        «aaa
 5886        bbb
 5887        added_lineˇ»
 5888    "});
 5889
 5890    // Removing line
 5891    cx.set_state(indoc! {"
 5892        aa«a
 5893        bbbˇ»
 5894    "});
 5895    cx.update_editor(|e, window, cx| {
 5896        e.manipulate_immutable_lines(window, cx, |lines| {
 5897            lines.pop();
 5898        })
 5899    });
 5900    cx.assert_editor_state(indoc! {"
 5901        «aaaˇ»
 5902    "});
 5903
 5904    // Removing all lines
 5905    cx.set_state(indoc! {"
 5906        aa«a
 5907        bbbˇ»
 5908    "});
 5909    cx.update_editor(|e, window, cx| {
 5910        e.manipulate_immutable_lines(window, cx, |lines| {
 5911            lines.drain(..);
 5912        })
 5913    });
 5914    cx.assert_editor_state(indoc! {"
 5915        ˇ
 5916    "});
 5917}
 5918
 5919#[gpui::test]
 5920async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5921    init_test(cx, |_| {});
 5922
 5923    let mut cx = EditorTestContext::new(cx).await;
 5924
 5925    // Consider continuous selection as single selection
 5926    cx.set_state(indoc! {"
 5927        Aaa«aa
 5928        cˇ»c«c
 5929        bb
 5930        aaaˇ»aa
 5931    "});
 5932    cx.update_editor(|e, window, cx| {
 5933        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5934    });
 5935    cx.assert_editor_state(indoc! {"
 5936        «Aaaaa
 5937        ccc
 5938        bb
 5939        aaaaaˇ»
 5940    "});
 5941
 5942    cx.set_state(indoc! {"
 5943        Aaa«aa
 5944        cˇ»c«c
 5945        bb
 5946        aaaˇ»aa
 5947    "});
 5948    cx.update_editor(|e, window, cx| {
 5949        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5950    });
 5951    cx.assert_editor_state(indoc! {"
 5952        «Aaaaa
 5953        ccc
 5954        bbˇ»
 5955    "});
 5956
 5957    // Consider non continuous selection as distinct dedup operations
 5958    cx.set_state(indoc! {"
 5959        «aaaaa
 5960        bb
 5961        aaaaa
 5962        aaaaaˇ»
 5963
 5964        aaa«aaˇ»
 5965    "});
 5966    cx.update_editor(|e, window, cx| {
 5967        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5968    });
 5969    cx.assert_editor_state(indoc! {"
 5970        «aaaaa
 5971        bbˇ»
 5972
 5973        «aaaaaˇ»
 5974    "});
 5975}
 5976
 5977#[gpui::test]
 5978async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5979    init_test(cx, |_| {});
 5980
 5981    let mut cx = EditorTestContext::new(cx).await;
 5982
 5983    cx.set_state(indoc! {"
 5984        «Aaa
 5985        aAa
 5986        Aaaˇ»
 5987    "});
 5988    cx.update_editor(|e, window, cx| {
 5989        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5990    });
 5991    cx.assert_editor_state(indoc! {"
 5992        «Aaa
 5993        aAaˇ»
 5994    "});
 5995
 5996    cx.set_state(indoc! {"
 5997        «Aaa
 5998        aAa
 5999        aaAˇ»
 6000    "});
 6001    cx.update_editor(|e, window, cx| {
 6002        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 6003    });
 6004    cx.assert_editor_state(indoc! {"
 6005        «Aaaˇ»
 6006    "});
 6007}
 6008
 6009#[gpui::test]
 6010async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 6011    init_test(cx, |_| {});
 6012
 6013    let mut cx = EditorTestContext::new(cx).await;
 6014
 6015    let js_language = Arc::new(Language::new(
 6016        LanguageConfig {
 6017            name: "JavaScript".into(),
 6018            wrap_characters: Some(language::WrapCharactersConfig {
 6019                start_prefix: "<".into(),
 6020                start_suffix: ">".into(),
 6021                end_prefix: "</".into(),
 6022                end_suffix: ">".into(),
 6023            }),
 6024            ..LanguageConfig::default()
 6025        },
 6026        None,
 6027    ));
 6028
 6029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 6030
 6031    cx.set_state(indoc! {"
 6032        «testˇ»
 6033    "});
 6034    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6035    cx.assert_editor_state(indoc! {"
 6036        <«ˇ»>test</«ˇ»>
 6037    "});
 6038
 6039    cx.set_state(indoc! {"
 6040        «test
 6041         testˇ»
 6042    "});
 6043    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6044    cx.assert_editor_state(indoc! {"
 6045        <«ˇ»>test
 6046         test</«ˇ»>
 6047    "});
 6048
 6049    cx.set_state(indoc! {"
 6050        teˇst
 6051    "});
 6052    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6053    cx.assert_editor_state(indoc! {"
 6054        te<«ˇ»></«ˇ»>st
 6055    "});
 6056}
 6057
 6058#[gpui::test]
 6059async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 6060    init_test(cx, |_| {});
 6061
 6062    let mut cx = EditorTestContext::new(cx).await;
 6063
 6064    let js_language = Arc::new(Language::new(
 6065        LanguageConfig {
 6066            name: "JavaScript".into(),
 6067            wrap_characters: Some(language::WrapCharactersConfig {
 6068                start_prefix: "<".into(),
 6069                start_suffix: ">".into(),
 6070                end_prefix: "</".into(),
 6071                end_suffix: ">".into(),
 6072            }),
 6073            ..LanguageConfig::default()
 6074        },
 6075        None,
 6076    ));
 6077
 6078    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 6079
 6080    cx.set_state(indoc! {"
 6081        «testˇ»
 6082        «testˇ» «testˇ»
 6083        «testˇ»
 6084    "});
 6085    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6086    cx.assert_editor_state(indoc! {"
 6087        <«ˇ»>test</«ˇ»>
 6088        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 6089        <«ˇ»>test</«ˇ»>
 6090    "});
 6091
 6092    cx.set_state(indoc! {"
 6093        «test
 6094         testˇ»
 6095        «test
 6096         testˇ»
 6097    "});
 6098    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6099    cx.assert_editor_state(indoc! {"
 6100        <«ˇ»>test
 6101         test</«ˇ»>
 6102        <«ˇ»>test
 6103         test</«ˇ»>
 6104    "});
 6105}
 6106
 6107#[gpui::test]
 6108async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 6109    init_test(cx, |_| {});
 6110
 6111    let mut cx = EditorTestContext::new(cx).await;
 6112
 6113    let plaintext_language = Arc::new(Language::new(
 6114        LanguageConfig {
 6115            name: "Plain Text".into(),
 6116            ..LanguageConfig::default()
 6117        },
 6118        None,
 6119    ));
 6120
 6121    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 6122
 6123    cx.set_state(indoc! {"
 6124        «testˇ»
 6125    "});
 6126    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 6127    cx.assert_editor_state(indoc! {"
 6128      «testˇ»
 6129    "});
 6130}
 6131
 6132#[gpui::test]
 6133async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 6134    init_test(cx, |_| {});
 6135
 6136    let mut cx = EditorTestContext::new(cx).await;
 6137
 6138    // Manipulate with multiple selections on a single line
 6139    cx.set_state(indoc! {"
 6140        dd«dd
 6141        cˇ»c«c
 6142        bb
 6143        aaaˇ»aa
 6144    "});
 6145    cx.update_editor(|e, window, cx| {
 6146        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 6147    });
 6148    cx.assert_editor_state(indoc! {"
 6149        «aaaaa
 6150        bb
 6151        ccc
 6152        ddddˇ»
 6153    "});
 6154
 6155    // Manipulate with multiple disjoin selections
 6156    cx.set_state(indoc! {"
 6157 6158        4
 6159        3
 6160        2
 6161        1ˇ»
 6162
 6163        dd«dd
 6164        ccc
 6165        bb
 6166        aaaˇ»aa
 6167    "});
 6168    cx.update_editor(|e, window, cx| {
 6169        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 6170    });
 6171    cx.assert_editor_state(indoc! {"
 6172        «1
 6173        2
 6174        3
 6175        4
 6176        5ˇ»
 6177
 6178        «aaaaa
 6179        bb
 6180        ccc
 6181        ddddˇ»
 6182    "});
 6183
 6184    // Adding lines on each selection
 6185    cx.set_state(indoc! {"
 6186 6187        1ˇ»
 6188
 6189        bb«bb
 6190        aaaˇ»aa
 6191    "});
 6192    cx.update_editor(|e, window, cx| {
 6193        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 6194    });
 6195    cx.assert_editor_state(indoc! {"
 6196        «2
 6197        1
 6198        added lineˇ»
 6199
 6200        «bbbb
 6201        aaaaa
 6202        added lineˇ»
 6203    "});
 6204
 6205    // Removing lines on each selection
 6206    cx.set_state(indoc! {"
 6207 6208        1ˇ»
 6209
 6210        bb«bb
 6211        aaaˇ»aa
 6212    "});
 6213    cx.update_editor(|e, window, cx| {
 6214        e.manipulate_immutable_lines(window, cx, |lines| {
 6215            lines.pop();
 6216        })
 6217    });
 6218    cx.assert_editor_state(indoc! {"
 6219        «2ˇ»
 6220
 6221        «bbbbˇ»
 6222    "});
 6223}
 6224
 6225#[gpui::test]
 6226async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 6227    init_test(cx, |settings| {
 6228        settings.defaults.tab_size = NonZeroU32::new(3)
 6229    });
 6230
 6231    let mut cx = EditorTestContext::new(cx).await;
 6232
 6233    // MULTI SELECTION
 6234    // Ln.1 "«" tests empty lines
 6235    // Ln.9 tests just leading whitespace
 6236    cx.set_state(indoc! {"
 6237        «
 6238        abc                 // No indentationˇ»
 6239        «\tabc              // 1 tabˇ»
 6240        \t\tabc «      ˇ»   // 2 tabs
 6241        \t ab«c             // Tab followed by space
 6242         \tabc              // Space followed by tab (3 spaces should be the result)
 6243        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 6244           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 6245        \t
 6246        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 6247    "});
 6248    cx.update_editor(|e, window, cx| {
 6249        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 6250    });
 6251    cx.assert_editor_state(
 6252        indoc! {"
 6253            «
 6254            abc                 // No indentation
 6255               abc              // 1 tab
 6256                  abc          // 2 tabs
 6257                abc             // Tab followed by space
 6258               abc              // Space followed by tab (3 spaces should be the result)
 6259                           abc   // Mixed indentation (tab conversion depends on the column)
 6260               abc         // Already space indented
 6261               ·
 6262               abc\tdef          // Only the leading tab is manipulatedˇ»
 6263        "}
 6264        .replace("·", "")
 6265        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6266    );
 6267
 6268    // Test on just a few lines, the others should remain unchanged
 6269    // Only lines (3, 5, 10, 11) should change
 6270    cx.set_state(
 6271        indoc! {"
 6272            ·
 6273            abc                 // No indentation
 6274            \tabcˇ               // 1 tab
 6275            \t\tabc             // 2 tabs
 6276            \t abcˇ              // Tab followed by space
 6277             \tabc              // Space followed by tab (3 spaces should be the result)
 6278            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 6279               abc              // Already space indented
 6280            «\t
 6281            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 6282        "}
 6283        .replace("·", "")
 6284        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6285    );
 6286    cx.update_editor(|e, window, cx| {
 6287        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 6288    });
 6289    cx.assert_editor_state(
 6290        indoc! {"
 6291            ·
 6292            abc                 // No indentation
 6293            «   abc               // 1 tabˇ»
 6294            \t\tabc             // 2 tabs
 6295            «    abc              // Tab followed by spaceˇ»
 6296             \tabc              // Space followed by tab (3 spaces should be the result)
 6297            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 6298               abc              // Already space indented
 6299            «   ·
 6300               abc\tdef          // Only the leading tab is manipulatedˇ»
 6301        "}
 6302        .replace("·", "")
 6303        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6304    );
 6305
 6306    // SINGLE SELECTION
 6307    // Ln.1 "«" tests empty lines
 6308    // Ln.9 tests just leading whitespace
 6309    cx.set_state(indoc! {"
 6310        «
 6311        abc                 // No indentation
 6312        \tabc               // 1 tab
 6313        \t\tabc             // 2 tabs
 6314        \t abc              // Tab followed by space
 6315         \tabc              // Space followed by tab (3 spaces should be the result)
 6316        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 6317           abc              // Already space indented
 6318        \t
 6319        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 6320    "});
 6321    cx.update_editor(|e, window, cx| {
 6322        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 6323    });
 6324    cx.assert_editor_state(
 6325        indoc! {"
 6326            «
 6327            abc                 // No indentation
 6328               abc               // 1 tab
 6329                  abc             // 2 tabs
 6330                abc              // Tab followed by space
 6331               abc              // Space followed by tab (3 spaces should be the result)
 6332                           abc   // Mixed indentation (tab conversion depends on the column)
 6333               abc              // Already space indented
 6334               ·
 6335               abc\tdef          // Only the leading tab is manipulatedˇ»
 6336        "}
 6337        .replace("·", "")
 6338        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6339    );
 6340}
 6341
 6342#[gpui::test]
 6343async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 6344    init_test(cx, |settings| {
 6345        settings.defaults.tab_size = NonZeroU32::new(3)
 6346    });
 6347
 6348    let mut cx = EditorTestContext::new(cx).await;
 6349
 6350    // MULTI SELECTION
 6351    // Ln.1 "«" tests empty lines
 6352    // Ln.11 tests just leading whitespace
 6353    cx.set_state(indoc! {"
 6354        «
 6355        abˇ»ˇc                 // No indentation
 6356         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 6357          abc  «             // 2 spaces (< 3 so dont convert)
 6358           abc              // 3 spaces (convert)
 6359             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 6360        «\tˇ»\t«\tˇ»abc           // Already tab indented
 6361        «\t abc              // Tab followed by space
 6362         \tabc              // Space followed by tab (should be consumed due to tab)
 6363        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6364           \tˇ»  «\t
 6365           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 6366    "});
 6367    cx.update_editor(|e, window, cx| {
 6368        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6369    });
 6370    cx.assert_editor_state(indoc! {"
 6371        «
 6372        abc                 // No indentation
 6373         abc                // 1 space (< 3 so dont convert)
 6374          abc               // 2 spaces (< 3 so dont convert)
 6375        \tabc              // 3 spaces (convert)
 6376        \t  abc            // 5 spaces (1 tab + 2 spaces)
 6377        \t\t\tabc           // Already tab indented
 6378        \t abc              // Tab followed by space
 6379        \tabc              // Space followed by tab (should be consumed due to tab)
 6380        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6381        \t\t\t
 6382        \tabc   \t         // Only the leading spaces should be convertedˇ»
 6383    "});
 6384
 6385    // Test on just a few lines, the other should remain unchanged
 6386    // Only lines (4, 8, 11, 12) should change
 6387    cx.set_state(
 6388        indoc! {"
 6389            ·
 6390            abc                 // No indentation
 6391             abc                // 1 space (< 3 so dont convert)
 6392              abc               // 2 spaces (< 3 so dont convert)
 6393            «   abc              // 3 spaces (convert)ˇ»
 6394                 abc            // 5 spaces (1 tab + 2 spaces)
 6395            \t\t\tabc           // Already tab indented
 6396            \t abc              // Tab followed by space
 6397             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 6398               \t\t  \tabc      // Mixed indentation
 6399            \t \t  \t   \tabc   // Mixed indentation
 6400               \t  \tˇ
 6401            «   abc   \t         // Only the leading spaces should be convertedˇ»
 6402        "}
 6403        .replace("·", "")
 6404        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6405    );
 6406    cx.update_editor(|e, window, cx| {
 6407        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6408    });
 6409    cx.assert_editor_state(
 6410        indoc! {"
 6411            ·
 6412            abc                 // No indentation
 6413             abc                // 1 space (< 3 so dont convert)
 6414              abc               // 2 spaces (< 3 so dont convert)
 6415            «\tabc              // 3 spaces (convert)ˇ»
 6416                 abc            // 5 spaces (1 tab + 2 spaces)
 6417            \t\t\tabc           // Already tab indented
 6418            \t abc              // Tab followed by space
 6419            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 6420               \t\t  \tabc      // Mixed indentation
 6421            \t \t  \t   \tabc   // Mixed indentation
 6422            «\t\t\t
 6423            \tabc   \t         // Only the leading spaces should be convertedˇ»
 6424        "}
 6425        .replace("·", "")
 6426        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6427    );
 6428
 6429    // SINGLE SELECTION
 6430    // Ln.1 "«" tests empty lines
 6431    // Ln.11 tests just leading whitespace
 6432    cx.set_state(indoc! {"
 6433        «
 6434        abc                 // No indentation
 6435         abc                // 1 space (< 3 so dont convert)
 6436          abc               // 2 spaces (< 3 so dont convert)
 6437           abc              // 3 spaces (convert)
 6438             abc            // 5 spaces (1 tab + 2 spaces)
 6439        \t\t\tabc           // Already tab indented
 6440        \t abc              // Tab followed by space
 6441         \tabc              // Space followed by tab (should be consumed due to tab)
 6442        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6443           \t  \t
 6444           abc   \t         // Only the leading spaces should be convertedˇ»
 6445    "});
 6446    cx.update_editor(|e, window, cx| {
 6447        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6448    });
 6449    cx.assert_editor_state(indoc! {"
 6450        «
 6451        abc                 // No indentation
 6452         abc                // 1 space (< 3 so dont convert)
 6453          abc               // 2 spaces (< 3 so dont convert)
 6454        \tabc              // 3 spaces (convert)
 6455        \t  abc            // 5 spaces (1 tab + 2 spaces)
 6456        \t\t\tabc           // Already tab indented
 6457        \t abc              // Tab followed by space
 6458        \tabc              // Space followed by tab (should be consumed due to tab)
 6459        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6460        \t\t\t
 6461        \tabc   \t         // Only the leading spaces should be convertedˇ»
 6462    "});
 6463}
 6464
 6465#[gpui::test]
 6466async fn test_toggle_case(cx: &mut TestAppContext) {
 6467    init_test(cx, |_| {});
 6468
 6469    let mut cx = EditorTestContext::new(cx).await;
 6470
 6471    // If all lower case -> upper case
 6472    cx.set_state(indoc! {"
 6473        «hello worldˇ»
 6474    "});
 6475    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6476    cx.assert_editor_state(indoc! {"
 6477        «HELLO WORLDˇ»
 6478    "});
 6479
 6480    // If all upper case -> lower case
 6481    cx.set_state(indoc! {"
 6482        «HELLO WORLDˇ»
 6483    "});
 6484    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6485    cx.assert_editor_state(indoc! {"
 6486        «hello worldˇ»
 6487    "});
 6488
 6489    // If any upper case characters are identified -> lower case
 6490    // This matches JetBrains IDEs
 6491    cx.set_state(indoc! {"
 6492        «hEllo worldˇ»
 6493    "});
 6494    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6495    cx.assert_editor_state(indoc! {"
 6496        «hello worldˇ»
 6497    "});
 6498}
 6499
 6500#[gpui::test]
 6501async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 6502    init_test(cx, |_| {});
 6503
 6504    let mut cx = EditorTestContext::new(cx).await;
 6505
 6506    cx.set_state(indoc! {"
 6507        «implement-windows-supportˇ»
 6508    "});
 6509    cx.update_editor(|e, window, cx| {
 6510        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 6511    });
 6512    cx.assert_editor_state(indoc! {"
 6513        «Implement windows supportˇ»
 6514    "});
 6515}
 6516
 6517#[gpui::test]
 6518async fn test_manipulate_text(cx: &mut TestAppContext) {
 6519    init_test(cx, |_| {});
 6520
 6521    let mut cx = EditorTestContext::new(cx).await;
 6522
 6523    // Test convert_to_upper_case()
 6524    cx.set_state(indoc! {"
 6525        «hello worldˇ»
 6526    "});
 6527    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6528    cx.assert_editor_state(indoc! {"
 6529        «HELLO WORLDˇ»
 6530    "});
 6531
 6532    // Test convert_to_lower_case()
 6533    cx.set_state(indoc! {"
 6534        «HELLO WORLDˇ»
 6535    "});
 6536    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 6537    cx.assert_editor_state(indoc! {"
 6538        «hello worldˇ»
 6539    "});
 6540
 6541    // Test multiple line, single selection case
 6542    cx.set_state(indoc! {"
 6543        «The quick brown
 6544        fox jumps over
 6545        the lazy dogˇ»
 6546    "});
 6547    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6548    cx.assert_editor_state(indoc! {"
 6549        «The Quick Brown
 6550        Fox Jumps Over
 6551        The Lazy Dogˇ»
 6552    "});
 6553
 6554    // Test multiple line, single selection case
 6555    cx.set_state(indoc! {"
 6556        «The quick brown
 6557        fox jumps over
 6558        the lazy dogˇ»
 6559    "});
 6560    cx.update_editor(|e, window, cx| {
 6561        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 6562    });
 6563    cx.assert_editor_state(indoc! {"
 6564        «TheQuickBrown
 6565        FoxJumpsOver
 6566        TheLazyDogˇ»
 6567    "});
 6568
 6569    // From here on out, test more complex cases of manipulate_text()
 6570
 6571    // Test no selection case - should affect words cursors are in
 6572    // Cursor at beginning, middle, and end of word
 6573    cx.set_state(indoc! {"
 6574        ˇhello big beauˇtiful worldˇ
 6575    "});
 6576    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6577    cx.assert_editor_state(indoc! {"
 6578        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 6579    "});
 6580
 6581    // Test multiple selections on a single line and across multiple lines
 6582    cx.set_state(indoc! {"
 6583        «Theˇ» quick «brown
 6584        foxˇ» jumps «overˇ»
 6585        the «lazyˇ» dog
 6586    "});
 6587    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6588    cx.assert_editor_state(indoc! {"
 6589        «THEˇ» quick «BROWN
 6590        FOXˇ» jumps «OVERˇ»
 6591        the «LAZYˇ» dog
 6592    "});
 6593
 6594    // Test case where text length grows
 6595    cx.set_state(indoc! {"
 6596        «tschüߡ»
 6597    "});
 6598    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6599    cx.assert_editor_state(indoc! {"
 6600        «TSCHÜSSˇ»
 6601    "});
 6602
 6603    // Test to make sure we don't crash when text shrinks
 6604    cx.set_state(indoc! {"
 6605        aaa_bbbˇ
 6606    "});
 6607    cx.update_editor(|e, window, cx| {
 6608        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6609    });
 6610    cx.assert_editor_state(indoc! {"
 6611        «aaaBbbˇ»
 6612    "});
 6613
 6614    // Test to make sure we all aware of the fact that each word can grow and shrink
 6615    // Final selections should be aware of this fact
 6616    cx.set_state(indoc! {"
 6617        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 6618    "});
 6619    cx.update_editor(|e, window, cx| {
 6620        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6621    });
 6622    cx.assert_editor_state(indoc! {"
 6623        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 6624    "});
 6625
 6626    cx.set_state(indoc! {"
 6627        «hElLo, WoRld!ˇ»
 6628    "});
 6629    cx.update_editor(|e, window, cx| {
 6630        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 6631    });
 6632    cx.assert_editor_state(indoc! {"
 6633        «HeLlO, wOrLD!ˇ»
 6634    "});
 6635
 6636    // Test that case conversions backed by `to_case` preserve leading/trailing whitespace.
 6637    cx.set_state(indoc! {"
 6638        «    hello worldˇ»
 6639    "});
 6640    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6641    cx.assert_editor_state(indoc! {"
 6642        «    Hello Worldˇ»
 6643    "});
 6644
 6645    cx.set_state(indoc! {"
 6646        «    hello worldˇ»
 6647    "});
 6648    cx.update_editor(|e, window, cx| {
 6649        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 6650    });
 6651    cx.assert_editor_state(indoc! {"
 6652        «    HelloWorldˇ»
 6653    "});
 6654
 6655    cx.set_state(indoc! {"
 6656        «    hello worldˇ»
 6657    "});
 6658    cx.update_editor(|e, window, cx| {
 6659        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6660    });
 6661    cx.assert_editor_state(indoc! {"
 6662        «    helloWorldˇ»
 6663    "});
 6664
 6665    cx.set_state(indoc! {"
 6666        «    hello worldˇ»
 6667    "});
 6668    cx.update_editor(|e, window, cx| e.convert_to_snake_case(&ConvertToSnakeCase, window, cx));
 6669    cx.assert_editor_state(indoc! {"
 6670        «    hello_worldˇ»
 6671    "});
 6672
 6673    cx.set_state(indoc! {"
 6674        «    hello worldˇ»
 6675    "});
 6676    cx.update_editor(|e, window, cx| e.convert_to_kebab_case(&ConvertToKebabCase, window, cx));
 6677    cx.assert_editor_state(indoc! {"
 6678        «    hello-worldˇ»
 6679    "});
 6680
 6681    cx.set_state(indoc! {"
 6682        «    hello worldˇ»
 6683    "});
 6684    cx.update_editor(|e, window, cx| {
 6685        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 6686    });
 6687    cx.assert_editor_state(indoc! {"
 6688        «    Hello worldˇ»
 6689    "});
 6690
 6691    cx.set_state(indoc! {"
 6692        «    hello world\t\tˇ»
 6693    "});
 6694    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6695    cx.assert_editor_state(indoc! {"
 6696        «    Hello World\t\tˇ»
 6697    "});
 6698
 6699    cx.set_state(indoc! {"
 6700        «    hello world\t\tˇ»
 6701    "});
 6702    cx.update_editor(|e, window, cx| e.convert_to_snake_case(&ConvertToSnakeCase, window, cx));
 6703    cx.assert_editor_state(indoc! {"
 6704        «    hello_world\t\tˇ»
 6705    "});
 6706
 6707    // Test selections with `line_mode() = true`.
 6708    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 6709    cx.set_state(indoc! {"
 6710        «The quick brown
 6711        fox jumps over
 6712        tˇ»he lazy dog
 6713    "});
 6714    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6715    cx.assert_editor_state(indoc! {"
 6716        «THE QUICK BROWN
 6717        FOX JUMPS OVER
 6718        THE LAZY DOGˇ»
 6719    "});
 6720}
 6721
 6722#[gpui::test]
 6723fn test_duplicate_line(cx: &mut TestAppContext) {
 6724    init_test(cx, |_| {});
 6725
 6726    let editor = cx.add_window(|window, cx| {
 6727        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6728        build_editor(buffer, window, cx)
 6729    });
 6730    _ = editor.update(cx, |editor, window, cx| {
 6731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6732            s.select_display_ranges([
 6733                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6734                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6735                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6736                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6737            ])
 6738        });
 6739        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6740        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6741        assert_eq!(
 6742            display_ranges(editor, cx),
 6743            vec![
 6744                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 6745                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 6746                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6747                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 6748            ]
 6749        );
 6750    });
 6751
 6752    let editor = cx.add_window(|window, cx| {
 6753        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6754        build_editor(buffer, window, cx)
 6755    });
 6756    _ = editor.update(cx, |editor, window, cx| {
 6757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6758            s.select_display_ranges([
 6759                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6760                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6761            ])
 6762        });
 6763        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6764        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6765        assert_eq!(
 6766            display_ranges(editor, cx),
 6767            vec![
 6768                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 6769                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 6770            ]
 6771        );
 6772    });
 6773
 6774    // With `duplicate_line_up` the selections move to the duplicated lines,
 6775    // which are inserted above the original lines
 6776    let editor = cx.add_window(|window, cx| {
 6777        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6778        build_editor(buffer, window, cx)
 6779    });
 6780    _ = editor.update(cx, |editor, window, cx| {
 6781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6782            s.select_display_ranges([
 6783                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6784                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6785                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6786                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6787            ])
 6788        });
 6789        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6790        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6791        assert_eq!(
 6792            display_ranges(editor, cx),
 6793            vec![
 6794                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6795                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6796                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6797                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6798            ]
 6799        );
 6800    });
 6801
 6802    let editor = cx.add_window(|window, cx| {
 6803        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6804        build_editor(buffer, window, cx)
 6805    });
 6806    _ = editor.update(cx, |editor, window, cx| {
 6807        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6808            s.select_display_ranges([
 6809                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6810                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6811            ])
 6812        });
 6813        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6814        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6815        assert_eq!(
 6816            display_ranges(editor, cx),
 6817            vec![
 6818                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6819                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6820            ]
 6821        );
 6822    });
 6823
 6824    let editor = cx.add_window(|window, cx| {
 6825        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6826        build_editor(buffer, window, cx)
 6827    });
 6828    _ = editor.update(cx, |editor, window, cx| {
 6829        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6830            s.select_display_ranges([
 6831                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6832                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6833            ])
 6834        });
 6835        editor.duplicate_selection(&DuplicateSelection, window, cx);
 6836        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 6837        assert_eq!(
 6838            display_ranges(editor, cx),
 6839            vec![
 6840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6841                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 6842            ]
 6843        );
 6844    });
 6845}
 6846
 6847#[gpui::test]
 6848async fn test_rotate_selections(cx: &mut TestAppContext) {
 6849    init_test(cx, |_| {});
 6850
 6851    let mut cx = EditorTestContext::new(cx).await;
 6852
 6853    // Rotate text selections (horizontal)
 6854    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6855    cx.update_editor(|e, window, cx| {
 6856        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6857    });
 6858    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 6859    cx.update_editor(|e, window, cx| {
 6860        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6861    });
 6862    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6863
 6864    // Rotate text selections (vertical)
 6865    cx.set_state(indoc! {"
 6866        x=«1ˇ»
 6867        y=«2ˇ»
 6868        z=«3ˇ»
 6869    "});
 6870    cx.update_editor(|e, window, cx| {
 6871        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6872    });
 6873    cx.assert_editor_state(indoc! {"
 6874        x=«3ˇ»
 6875        y=«1ˇ»
 6876        z=«2ˇ»
 6877    "});
 6878    cx.update_editor(|e, window, cx| {
 6879        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6880    });
 6881    cx.assert_editor_state(indoc! {"
 6882        x=«1ˇ»
 6883        y=«2ˇ»
 6884        z=«3ˇ»
 6885    "});
 6886
 6887    // Rotate text selections (vertical, different lengths)
 6888    cx.set_state(indoc! {"
 6889        x=\"«ˇ»\"
 6890        y=\"«aˇ»\"
 6891        z=\"«aaˇ»\"
 6892    "});
 6893    cx.update_editor(|e, window, cx| {
 6894        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6895    });
 6896    cx.assert_editor_state(indoc! {"
 6897        x=\"«aaˇ»\"
 6898        y=\"«ˇ»\"
 6899        z=\"«aˇ»\"
 6900    "});
 6901    cx.update_editor(|e, window, cx| {
 6902        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6903    });
 6904    cx.assert_editor_state(indoc! {"
 6905        x=\"«ˇ»\"
 6906        y=\"«aˇ»\"
 6907        z=\"«aaˇ»\"
 6908    "});
 6909
 6910    // Rotate whole lines (cursor positions preserved)
 6911    cx.set_state(indoc! {"
 6912        ˇline123
 6913        liˇne23
 6914        line3ˇ
 6915    "});
 6916    cx.update_editor(|e, window, cx| {
 6917        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6918    });
 6919    cx.assert_editor_state(indoc! {"
 6920        line3ˇ
 6921        ˇline123
 6922        liˇne23
 6923    "});
 6924    cx.update_editor(|e, window, cx| {
 6925        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6926    });
 6927    cx.assert_editor_state(indoc! {"
 6928        ˇline123
 6929        liˇne23
 6930        line3ˇ
 6931    "});
 6932
 6933    // Rotate whole lines, multiple cursors per line (positions preserved)
 6934    cx.set_state(indoc! {"
 6935        ˇliˇne123
 6936        ˇline23
 6937        ˇline3
 6938    "});
 6939    cx.update_editor(|e, window, cx| {
 6940        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6941    });
 6942    cx.assert_editor_state(indoc! {"
 6943        ˇline3
 6944        ˇliˇne123
 6945        ˇline23
 6946    "});
 6947    cx.update_editor(|e, window, cx| {
 6948        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6949    });
 6950    cx.assert_editor_state(indoc! {"
 6951        ˇliˇne123
 6952        ˇline23
 6953        ˇline3
 6954    "});
 6955}
 6956
 6957#[gpui::test]
 6958fn test_move_line_up_down(cx: &mut TestAppContext) {
 6959    init_test(cx, |_| {});
 6960
 6961    let editor = cx.add_window(|window, cx| {
 6962        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6963        build_editor(buffer, window, cx)
 6964    });
 6965    _ = editor.update(cx, |editor, window, cx| {
 6966        editor.fold_creases(
 6967            vec![
 6968                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6969                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6970                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6971            ],
 6972            true,
 6973            window,
 6974            cx,
 6975        );
 6976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6977            s.select_display_ranges([
 6978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6979                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6980                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6981                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 6982            ])
 6983        });
 6984        assert_eq!(
 6985            editor.display_text(cx),
 6986            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6987        );
 6988
 6989        editor.move_line_up(&MoveLineUp, window, cx);
 6990        assert_eq!(
 6991            editor.display_text(cx),
 6992            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6993        );
 6994        assert_eq!(
 6995            display_ranges(editor, cx),
 6996            vec![
 6997                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6998                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6999                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 7000                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 7001            ]
 7002        );
 7003    });
 7004
 7005    _ = editor.update(cx, |editor, window, cx| {
 7006        editor.move_line_down(&MoveLineDown, window, cx);
 7007        assert_eq!(
 7008            editor.display_text(cx),
 7009            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 7010        );
 7011        assert_eq!(
 7012            display_ranges(editor, cx),
 7013            vec![
 7014                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7015                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 7016                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 7017                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 7018            ]
 7019        );
 7020    });
 7021
 7022    _ = editor.update(cx, |editor, window, cx| {
 7023        editor.move_line_down(&MoveLineDown, window, cx);
 7024        assert_eq!(
 7025            editor.display_text(cx),
 7026            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 7027        );
 7028        assert_eq!(
 7029            display_ranges(editor, cx),
 7030            vec![
 7031                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 7032                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 7033                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 7034                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 7035            ]
 7036        );
 7037    });
 7038
 7039    _ = editor.update(cx, |editor, window, cx| {
 7040        editor.move_line_up(&MoveLineUp, window, cx);
 7041        assert_eq!(
 7042            editor.display_text(cx),
 7043            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 7044        );
 7045        assert_eq!(
 7046            display_ranges(editor, cx),
 7047            vec![
 7048                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7049                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 7050                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 7051                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 7052            ]
 7053        );
 7054    });
 7055}
 7056
 7057#[gpui::test]
 7058fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 7059    init_test(cx, |_| {});
 7060    let editor = cx.add_window(|window, cx| {
 7061        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 7062        build_editor(buffer, window, cx)
 7063    });
 7064    _ = editor.update(cx, |editor, window, cx| {
 7065        editor.fold_creases(
 7066            vec![Crease::simple(
 7067                Point::new(6, 4)..Point::new(7, 4),
 7068                FoldPlaceholder::test(),
 7069            )],
 7070            true,
 7071            window,
 7072            cx,
 7073        );
 7074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7075            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 7076        });
 7077        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 7078        editor.move_line_up(&MoveLineUp, window, cx);
 7079        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 7080        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 7081    });
 7082}
 7083
 7084#[gpui::test]
 7085fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 7086    init_test(cx, |_| {});
 7087
 7088    let editor = cx.add_window(|window, cx| {
 7089        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 7090        build_editor(buffer, window, cx)
 7091    });
 7092    _ = editor.update(cx, |editor, window, cx| {
 7093        let snapshot = editor.buffer.read(cx).snapshot(cx);
 7094        editor.insert_blocks(
 7095            [BlockProperties {
 7096                style: BlockStyle::Fixed,
 7097                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 7098                height: Some(1),
 7099                render: Arc::new(|_| div().into_any()),
 7100                priority: 0,
 7101            }],
 7102            Some(Autoscroll::fit()),
 7103            cx,
 7104        );
 7105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7106            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 7107        });
 7108        editor.move_line_down(&MoveLineDown, window, cx);
 7109    });
 7110}
 7111
 7112#[gpui::test]
 7113async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 7114    init_test(cx, |_| {});
 7115
 7116    let mut cx = EditorTestContext::new(cx).await;
 7117    cx.set_state(
 7118        &"
 7119            ˇzero
 7120            one
 7121            two
 7122            three
 7123            four
 7124            five
 7125        "
 7126        .unindent(),
 7127    );
 7128
 7129    // Create a four-line block that replaces three lines of text.
 7130    cx.update_editor(|editor, window, cx| {
 7131        let snapshot = editor.snapshot(window, cx);
 7132        let snapshot = &snapshot.buffer_snapshot();
 7133        let placement = BlockPlacement::Replace(
 7134            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 7135        );
 7136        editor.insert_blocks(
 7137            [BlockProperties {
 7138                placement,
 7139                height: Some(4),
 7140                style: BlockStyle::Sticky,
 7141                render: Arc::new(|_| gpui::div().into_any_element()),
 7142                priority: 0,
 7143            }],
 7144            None,
 7145            cx,
 7146        );
 7147    });
 7148
 7149    // Move down so that the cursor touches the block.
 7150    cx.update_editor(|editor, window, cx| {
 7151        editor.move_down(&Default::default(), window, cx);
 7152    });
 7153    cx.assert_editor_state(
 7154        &"
 7155            zero
 7156            «one
 7157            two
 7158            threeˇ»
 7159            four
 7160            five
 7161        "
 7162        .unindent(),
 7163    );
 7164
 7165    // Move down past the block.
 7166    cx.update_editor(|editor, window, cx| {
 7167        editor.move_down(&Default::default(), window, cx);
 7168    });
 7169    cx.assert_editor_state(
 7170        &"
 7171            zero
 7172            one
 7173            two
 7174            three
 7175            ˇfour
 7176            five
 7177        "
 7178        .unindent(),
 7179    );
 7180}
 7181
 7182#[gpui::test]
 7183fn test_transpose(cx: &mut TestAppContext) {
 7184    init_test(cx, |_| {});
 7185
 7186    _ = cx.add_window(|window, cx| {
 7187        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 7188        editor.set_style(EditorStyle::default(), window, cx);
 7189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7190            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 7191        });
 7192        editor.transpose(&Default::default(), window, cx);
 7193        assert_eq!(editor.text(cx), "bac");
 7194        assert_eq!(
 7195            editor.selections.ranges(&editor.display_snapshot(cx)),
 7196            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 7197        );
 7198
 7199        editor.transpose(&Default::default(), window, cx);
 7200        assert_eq!(editor.text(cx), "bca");
 7201        assert_eq!(
 7202            editor.selections.ranges(&editor.display_snapshot(cx)),
 7203            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 7204        );
 7205
 7206        editor.transpose(&Default::default(), window, cx);
 7207        assert_eq!(editor.text(cx), "bac");
 7208        assert_eq!(
 7209            editor.selections.ranges(&editor.display_snapshot(cx)),
 7210            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 7211        );
 7212
 7213        editor
 7214    });
 7215
 7216    _ = cx.add_window(|window, cx| {
 7217        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 7218        editor.set_style(EditorStyle::default(), window, cx);
 7219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7220            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 7221        });
 7222        editor.transpose(&Default::default(), window, cx);
 7223        assert_eq!(editor.text(cx), "acb\nde");
 7224        assert_eq!(
 7225            editor.selections.ranges(&editor.display_snapshot(cx)),
 7226            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 7227        );
 7228
 7229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7230            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 7231        });
 7232        editor.transpose(&Default::default(), window, cx);
 7233        assert_eq!(editor.text(cx), "acbd\ne");
 7234        assert_eq!(
 7235            editor.selections.ranges(&editor.display_snapshot(cx)),
 7236            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 7237        );
 7238
 7239        editor.transpose(&Default::default(), window, cx);
 7240        assert_eq!(editor.text(cx), "acbde\n");
 7241        assert_eq!(
 7242            editor.selections.ranges(&editor.display_snapshot(cx)),
 7243            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 7244        );
 7245
 7246        editor.transpose(&Default::default(), window, cx);
 7247        assert_eq!(editor.text(cx), "acbd\ne");
 7248        assert_eq!(
 7249            editor.selections.ranges(&editor.display_snapshot(cx)),
 7250            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 7251        );
 7252
 7253        editor
 7254    });
 7255
 7256    _ = cx.add_window(|window, cx| {
 7257        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 7258        editor.set_style(EditorStyle::default(), window, cx);
 7259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7260            s.select_ranges([
 7261                MultiBufferOffset(1)..MultiBufferOffset(1),
 7262                MultiBufferOffset(2)..MultiBufferOffset(2),
 7263                MultiBufferOffset(4)..MultiBufferOffset(4),
 7264            ])
 7265        });
 7266        editor.transpose(&Default::default(), window, cx);
 7267        assert_eq!(editor.text(cx), "bacd\ne");
 7268        assert_eq!(
 7269            editor.selections.ranges(&editor.display_snapshot(cx)),
 7270            [
 7271                MultiBufferOffset(2)..MultiBufferOffset(2),
 7272                MultiBufferOffset(3)..MultiBufferOffset(3),
 7273                MultiBufferOffset(5)..MultiBufferOffset(5)
 7274            ]
 7275        );
 7276
 7277        editor.transpose(&Default::default(), window, cx);
 7278        assert_eq!(editor.text(cx), "bcade\n");
 7279        assert_eq!(
 7280            editor.selections.ranges(&editor.display_snapshot(cx)),
 7281            [
 7282                MultiBufferOffset(3)..MultiBufferOffset(3),
 7283                MultiBufferOffset(4)..MultiBufferOffset(4),
 7284                MultiBufferOffset(6)..MultiBufferOffset(6)
 7285            ]
 7286        );
 7287
 7288        editor.transpose(&Default::default(), window, cx);
 7289        assert_eq!(editor.text(cx), "bcda\ne");
 7290        assert_eq!(
 7291            editor.selections.ranges(&editor.display_snapshot(cx)),
 7292            [
 7293                MultiBufferOffset(4)..MultiBufferOffset(4),
 7294                MultiBufferOffset(6)..MultiBufferOffset(6)
 7295            ]
 7296        );
 7297
 7298        editor.transpose(&Default::default(), window, cx);
 7299        assert_eq!(editor.text(cx), "bcade\n");
 7300        assert_eq!(
 7301            editor.selections.ranges(&editor.display_snapshot(cx)),
 7302            [
 7303                MultiBufferOffset(4)..MultiBufferOffset(4),
 7304                MultiBufferOffset(6)..MultiBufferOffset(6)
 7305            ]
 7306        );
 7307
 7308        editor.transpose(&Default::default(), window, cx);
 7309        assert_eq!(editor.text(cx), "bcaed\n");
 7310        assert_eq!(
 7311            editor.selections.ranges(&editor.display_snapshot(cx)),
 7312            [
 7313                MultiBufferOffset(5)..MultiBufferOffset(5),
 7314                MultiBufferOffset(6)..MultiBufferOffset(6)
 7315            ]
 7316        );
 7317
 7318        editor
 7319    });
 7320
 7321    _ = cx.add_window(|window, cx| {
 7322        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 7323        editor.set_style(EditorStyle::default(), window, cx);
 7324        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7325            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 7326        });
 7327        editor.transpose(&Default::default(), window, cx);
 7328        assert_eq!(editor.text(cx), "🏀🍐✋");
 7329        assert_eq!(
 7330            editor.selections.ranges(&editor.display_snapshot(cx)),
 7331            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 7332        );
 7333
 7334        editor.transpose(&Default::default(), window, cx);
 7335        assert_eq!(editor.text(cx), "🏀✋🍐");
 7336        assert_eq!(
 7337            editor.selections.ranges(&editor.display_snapshot(cx)),
 7338            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 7339        );
 7340
 7341        editor.transpose(&Default::default(), window, cx);
 7342        assert_eq!(editor.text(cx), "🏀🍐✋");
 7343        assert_eq!(
 7344            editor.selections.ranges(&editor.display_snapshot(cx)),
 7345            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 7346        );
 7347
 7348        editor
 7349    });
 7350}
 7351
 7352#[gpui::test]
 7353async fn test_rewrap(cx: &mut TestAppContext) {
 7354    init_test(cx, |settings| {
 7355        settings.languages.0.extend([
 7356            (
 7357                "Markdown".into(),
 7358                LanguageSettingsContent {
 7359                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 7360                    preferred_line_length: Some(40),
 7361                    ..Default::default()
 7362                },
 7363            ),
 7364            (
 7365                "Plain Text".into(),
 7366                LanguageSettingsContent {
 7367                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 7368                    preferred_line_length: Some(40),
 7369                    ..Default::default()
 7370                },
 7371            ),
 7372            (
 7373                "C++".into(),
 7374                LanguageSettingsContent {
 7375                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7376                    preferred_line_length: Some(40),
 7377                    ..Default::default()
 7378                },
 7379            ),
 7380            (
 7381                "Python".into(),
 7382                LanguageSettingsContent {
 7383                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7384                    preferred_line_length: Some(40),
 7385                    ..Default::default()
 7386                },
 7387            ),
 7388            (
 7389                "Rust".into(),
 7390                LanguageSettingsContent {
 7391                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7392                    preferred_line_length: Some(40),
 7393                    ..Default::default()
 7394                },
 7395            ),
 7396        ])
 7397    });
 7398
 7399    let mut cx = EditorTestContext::new(cx).await;
 7400
 7401    let cpp_language = Arc::new(Language::new(
 7402        LanguageConfig {
 7403            name: "C++".into(),
 7404            line_comments: vec!["// ".into()],
 7405            ..LanguageConfig::default()
 7406        },
 7407        None,
 7408    ));
 7409    let python_language = Arc::new(Language::new(
 7410        LanguageConfig {
 7411            name: "Python".into(),
 7412            line_comments: vec!["# ".into()],
 7413            ..LanguageConfig::default()
 7414        },
 7415        None,
 7416    ));
 7417    let markdown_language = Arc::new(Language::new(
 7418        LanguageConfig {
 7419            name: "Markdown".into(),
 7420            rewrap_prefixes: vec![
 7421                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 7422                regex::Regex::new("[-*+]\\s+").unwrap(),
 7423            ],
 7424            ..LanguageConfig::default()
 7425        },
 7426        None,
 7427    ));
 7428    let rust_language = Arc::new(
 7429        Language::new(
 7430            LanguageConfig {
 7431                name: "Rust".into(),
 7432                line_comments: vec!["// ".into(), "/// ".into()],
 7433                ..LanguageConfig::default()
 7434            },
 7435            Some(tree_sitter_rust::LANGUAGE.into()),
 7436        )
 7437        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 7438        .unwrap(),
 7439    );
 7440
 7441    let plaintext_language = Arc::new(Language::new(
 7442        LanguageConfig {
 7443            name: "Plain Text".into(),
 7444            ..LanguageConfig::default()
 7445        },
 7446        None,
 7447    ));
 7448
 7449    // Test basic rewrapping of a long line with a cursor
 7450    assert_rewrap(
 7451        indoc! {"
 7452            // ˇThis is a long comment that needs to be wrapped.
 7453        "},
 7454        indoc! {"
 7455            // ˇThis is a long comment that needs to
 7456            // be wrapped.
 7457        "},
 7458        cpp_language.clone(),
 7459        &mut cx,
 7460    );
 7461
 7462    // Test rewrapping a full selection
 7463    assert_rewrap(
 7464        indoc! {"
 7465            «// This selected long comment needs to be wrapped.ˇ»"
 7466        },
 7467        indoc! {"
 7468            «// This selected long comment needs to
 7469            // be wrapped.ˇ»"
 7470        },
 7471        cpp_language.clone(),
 7472        &mut cx,
 7473    );
 7474
 7475    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 7476    assert_rewrap(
 7477        indoc! {"
 7478            // ˇThis is the first line.
 7479            // Thisˇ is the second line.
 7480            // This is the thirdˇ line, all part of one paragraph.
 7481         "},
 7482        indoc! {"
 7483            // ˇThis is the first line. Thisˇ is the
 7484            // second line. This is the thirdˇ line,
 7485            // all part of one paragraph.
 7486         "},
 7487        cpp_language.clone(),
 7488        &mut cx,
 7489    );
 7490
 7491    // Test multiple cursors in different paragraphs trigger separate rewraps
 7492    assert_rewrap(
 7493        indoc! {"
 7494            // ˇThis is the first paragraph, first line.
 7495            // ˇThis is the first paragraph, second line.
 7496
 7497            // ˇThis is the second paragraph, first line.
 7498            // ˇThis is the second paragraph, second line.
 7499        "},
 7500        indoc! {"
 7501            // ˇThis is the first paragraph, first
 7502            // line. ˇThis is the first paragraph,
 7503            // second line.
 7504
 7505            // ˇThis is the second paragraph, first
 7506            // line. ˇThis is the second paragraph,
 7507            // second line.
 7508        "},
 7509        cpp_language.clone(),
 7510        &mut cx,
 7511    );
 7512
 7513    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 7514    assert_rewrap(
 7515        indoc! {"
 7516            «// A regular long long comment to be wrapped.
 7517            /// A documentation long comment to be wrapped.ˇ»
 7518          "},
 7519        indoc! {"
 7520            «// A regular long long comment to be
 7521            // wrapped.
 7522            /// A documentation long comment to be
 7523            /// wrapped.ˇ»
 7524          "},
 7525        rust_language.clone(),
 7526        &mut cx,
 7527    );
 7528
 7529    // Test that change in indentation level trigger seperate rewraps
 7530    assert_rewrap(
 7531        indoc! {"
 7532            fn foo() {
 7533                «// This is a long comment at the base indent.
 7534                    // This is a long comment at the next indent.ˇ»
 7535            }
 7536        "},
 7537        indoc! {"
 7538            fn foo() {
 7539                «// This is a long comment at the
 7540                // base indent.
 7541                    // This is a long comment at the
 7542                    // next indent.ˇ»
 7543            }
 7544        "},
 7545        rust_language.clone(),
 7546        &mut cx,
 7547    );
 7548
 7549    // Test that different comment prefix characters (e.g., '#') are handled correctly
 7550    assert_rewrap(
 7551        indoc! {"
 7552            # ˇThis is a long comment using a pound sign.
 7553        "},
 7554        indoc! {"
 7555            # ˇThis is a long comment using a pound
 7556            # sign.
 7557        "},
 7558        python_language,
 7559        &mut cx,
 7560    );
 7561
 7562    // Test rewrapping only affects comments, not code even when selected
 7563    assert_rewrap(
 7564        indoc! {"
 7565            «/// This doc comment is long and should be wrapped.
 7566            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 7567        "},
 7568        indoc! {"
 7569            «/// This doc comment is long and should
 7570            /// be wrapped.
 7571            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 7572        "},
 7573        rust_language.clone(),
 7574        &mut cx,
 7575    );
 7576
 7577    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 7578    assert_rewrap(
 7579        indoc! {"
 7580            # Header
 7581
 7582            A long long long line of markdown text to wrap.ˇ
 7583         "},
 7584        indoc! {"
 7585            # Header
 7586
 7587            A long long long line of markdown text
 7588            to wrap.ˇ
 7589         "},
 7590        markdown_language.clone(),
 7591        &mut cx,
 7592    );
 7593
 7594    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 7595    assert_rewrap(
 7596        indoc! {"
 7597            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 7598            2. This is a numbered list item that is very long and needs to be wrapped properly.
 7599            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 7600        "},
 7601        indoc! {"
 7602            «1. This is a numbered list item that is
 7603               very long and needs to be wrapped
 7604               properly.
 7605            2. This is a numbered list item that is
 7606               very long and needs to be wrapped
 7607               properly.
 7608            - This is an unordered list item that is
 7609              also very long and should not merge
 7610              with the numbered item.ˇ»
 7611        "},
 7612        markdown_language.clone(),
 7613        &mut cx,
 7614    );
 7615
 7616    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 7617    assert_rewrap(
 7618        indoc! {"
 7619            «1. This is a numbered list item that is
 7620            very long and needs to be wrapped
 7621            properly.
 7622            2. This is a numbered list item that is
 7623            very long and needs to be wrapped
 7624            properly.
 7625            - This is an unordered list item that is
 7626            also very long and should not merge with
 7627            the numbered item.ˇ»
 7628        "},
 7629        indoc! {"
 7630            «1. This is a numbered list item that is
 7631               very long and needs to be wrapped
 7632               properly.
 7633            2. This is a numbered list item that is
 7634               very long and needs to be wrapped
 7635               properly.
 7636            - This is an unordered list item that is
 7637              also very long and should not merge
 7638              with the numbered item.ˇ»
 7639        "},
 7640        markdown_language.clone(),
 7641        &mut cx,
 7642    );
 7643
 7644    // Test that rewrapping maintain indents even when they already exists.
 7645    assert_rewrap(
 7646        indoc! {"
 7647            «1. This is a numbered list
 7648               item that is very long and needs to be wrapped properly.
 7649            2. This is a numbered list
 7650               item that is very long and needs to be wrapped properly.
 7651            - This is an unordered list item that is also very long and
 7652              should not merge with the numbered item.ˇ»
 7653        "},
 7654        indoc! {"
 7655            «1. This is a numbered list item that is
 7656               very long and needs to be wrapped
 7657               properly.
 7658            2. This is a numbered list item that is
 7659               very long and needs to be wrapped
 7660               properly.
 7661            - This is an unordered list item that is
 7662              also very long and should not merge
 7663              with the numbered item.ˇ»
 7664        "},
 7665        markdown_language.clone(),
 7666        &mut cx,
 7667    );
 7668
 7669    // Test that empty selection rewrap on a numbered list item does not merge adjacent items
 7670    assert_rewrap(
 7671        indoc! {"
 7672            1. This is the first numbered list item that is very long and needs to be wrapped properly.
 7673            2. ˇThis is the second numbered list item that is also very long and needs to be wrapped.
 7674            3. This is the third numbered list item, shorter.
 7675        "},
 7676        indoc! {"
 7677            1. This is the first numbered list item
 7678               that is very long and needs to be
 7679               wrapped properly.
 7680            2. ˇThis is the second numbered list item
 7681               that is also very long and needs to
 7682               be wrapped.
 7683            3. This is the third numbered list item,
 7684               shorter.
 7685        "},
 7686        markdown_language.clone(),
 7687        &mut cx,
 7688    );
 7689
 7690    // Test that empty selection rewrap on a bullet list item does not merge adjacent items
 7691    assert_rewrap(
 7692        indoc! {"
 7693            - This is the first bullet item that is very long and needs wrapping properly here.
 7694            - ˇThis is the second bullet item that is also very long and needs to be wrapped.
 7695            - This is the third bullet item, shorter.
 7696        "},
 7697        indoc! {"
 7698            - This is the first bullet item that is
 7699              very long and needs wrapping properly
 7700              here.
 7701            - ˇThis is the second bullet item that is
 7702              also very long and needs to be
 7703              wrapped.
 7704            - This is the third bullet item,
 7705              shorter.
 7706        "},
 7707        markdown_language,
 7708        &mut cx,
 7709    );
 7710
 7711    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 7712    assert_rewrap(
 7713        indoc! {"
 7714            ˇThis is a very long line of plain text that will be wrapped.
 7715        "},
 7716        indoc! {"
 7717            ˇThis is a very long line of plain text
 7718            that will be wrapped.
 7719        "},
 7720        plaintext_language.clone(),
 7721        &mut cx,
 7722    );
 7723
 7724    // Test that non-commented code acts as a paragraph boundary within a selection
 7725    assert_rewrap(
 7726        indoc! {"
 7727               «// This is the first long comment block to be wrapped.
 7728               fn my_func(a: u32);
 7729               // This is the second long comment block to be wrapped.ˇ»
 7730           "},
 7731        indoc! {"
 7732               «// This is the first long comment block
 7733               // to be wrapped.
 7734               fn my_func(a: u32);
 7735               // This is the second long comment block
 7736               // to be wrapped.ˇ»
 7737           "},
 7738        rust_language,
 7739        &mut cx,
 7740    );
 7741
 7742    // Test rewrapping multiple selections, including ones with blank lines or tabs
 7743    assert_rewrap(
 7744        indoc! {"
 7745            «ˇThis is a very long line that will be wrapped.
 7746
 7747            This is another paragraph in the same selection.»
 7748
 7749            «\tThis is a very long indented line that will be wrapped.ˇ»
 7750         "},
 7751        indoc! {"
 7752            «ˇThis is a very long line that will be
 7753            wrapped.
 7754
 7755            This is another paragraph in the same
 7756            selection.»
 7757
 7758            «\tThis is a very long indented line
 7759            \tthat will be wrapped.ˇ»
 7760         "},
 7761        plaintext_language,
 7762        &mut cx,
 7763    );
 7764
 7765    // Test that an empty comment line acts as a paragraph boundary
 7766    assert_rewrap(
 7767        indoc! {"
 7768            // ˇThis is a long comment that will be wrapped.
 7769            //
 7770            // And this is another long comment that will also be wrapped.ˇ
 7771         "},
 7772        indoc! {"
 7773            // ˇThis is a long comment that will be
 7774            // wrapped.
 7775            //
 7776            // And this is another long comment that
 7777            // will also be wrapped.ˇ
 7778         "},
 7779        cpp_language,
 7780        &mut cx,
 7781    );
 7782
 7783    #[track_caller]
 7784    fn assert_rewrap(
 7785        unwrapped_text: &str,
 7786        wrapped_text: &str,
 7787        language: Arc<Language>,
 7788        cx: &mut EditorTestContext,
 7789    ) {
 7790        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7791        cx.set_state(unwrapped_text);
 7792        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7793        cx.assert_editor_state(wrapped_text);
 7794    }
 7795}
 7796
 7797#[gpui::test]
 7798async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 7799    init_test(cx, |settings| {
 7800        settings.languages.0.extend([(
 7801            "Rust".into(),
 7802            LanguageSettingsContent {
 7803                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7804                preferred_line_length: Some(40),
 7805                ..Default::default()
 7806            },
 7807        )])
 7808    });
 7809
 7810    let mut cx = EditorTestContext::new(cx).await;
 7811
 7812    let rust_lang = Arc::new(
 7813        Language::new(
 7814            LanguageConfig {
 7815                name: "Rust".into(),
 7816                line_comments: vec!["// ".into()],
 7817                block_comment: Some(BlockCommentConfig {
 7818                    start: "/*".into(),
 7819                    end: "*/".into(),
 7820                    prefix: "* ".into(),
 7821                    tab_size: 1,
 7822                }),
 7823                documentation_comment: Some(BlockCommentConfig {
 7824                    start: "/**".into(),
 7825                    end: "*/".into(),
 7826                    prefix: "* ".into(),
 7827                    tab_size: 1,
 7828                }),
 7829
 7830                ..LanguageConfig::default()
 7831            },
 7832            Some(tree_sitter_rust::LANGUAGE.into()),
 7833        )
 7834        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 7835        .unwrap(),
 7836    );
 7837
 7838    // regular block comment
 7839    assert_rewrap(
 7840        indoc! {"
 7841            /*
 7842             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7843             */
 7844            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7845        "},
 7846        indoc! {"
 7847            /*
 7848             *ˇ Lorem ipsum dolor sit amet,
 7849             * consectetur adipiscing elit.
 7850             */
 7851            /*
 7852             *ˇ Lorem ipsum dolor sit amet,
 7853             * consectetur adipiscing elit.
 7854             */
 7855        "},
 7856        rust_lang.clone(),
 7857        &mut cx,
 7858    );
 7859
 7860    // indent is respected
 7861    assert_rewrap(
 7862        indoc! {"
 7863            {}
 7864                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7865        "},
 7866        indoc! {"
 7867            {}
 7868                /*
 7869                 *ˇ Lorem ipsum dolor sit amet,
 7870                 * consectetur adipiscing elit.
 7871                 */
 7872        "},
 7873        rust_lang.clone(),
 7874        &mut cx,
 7875    );
 7876
 7877    // short block comments with inline delimiters
 7878    assert_rewrap(
 7879        indoc! {"
 7880            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7881            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7882             */
 7883            /*
 7884             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7885        "},
 7886        indoc! {"
 7887            /*
 7888             *ˇ Lorem ipsum dolor sit amet,
 7889             * consectetur adipiscing elit.
 7890             */
 7891            /*
 7892             *ˇ Lorem ipsum dolor sit amet,
 7893             * consectetur adipiscing elit.
 7894             */
 7895            /*
 7896             *ˇ Lorem ipsum dolor sit amet,
 7897             * consectetur adipiscing elit.
 7898             */
 7899        "},
 7900        rust_lang.clone(),
 7901        &mut cx,
 7902    );
 7903
 7904    // multiline block comment with inline start/end delimiters
 7905    assert_rewrap(
 7906        indoc! {"
 7907            /*ˇ Lorem ipsum dolor sit amet,
 7908             * consectetur adipiscing elit. */
 7909        "},
 7910        indoc! {"
 7911            /*
 7912             *ˇ Lorem ipsum dolor sit amet,
 7913             * consectetur adipiscing elit.
 7914             */
 7915        "},
 7916        rust_lang.clone(),
 7917        &mut cx,
 7918    );
 7919
 7920    // block comment rewrap still respects paragraph bounds
 7921    assert_rewrap(
 7922        indoc! {"
 7923            /*
 7924             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7925             *
 7926             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7927             */
 7928        "},
 7929        indoc! {"
 7930            /*
 7931             *ˇ Lorem ipsum dolor sit amet,
 7932             * consectetur adipiscing elit.
 7933             *
 7934             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7935             */
 7936        "},
 7937        rust_lang.clone(),
 7938        &mut cx,
 7939    );
 7940
 7941    // documentation comments
 7942    assert_rewrap(
 7943        indoc! {"
 7944            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7945            /**
 7946             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7947             */
 7948        "},
 7949        indoc! {"
 7950            /**
 7951             *ˇ Lorem ipsum dolor sit amet,
 7952             * consectetur adipiscing elit.
 7953             */
 7954            /**
 7955             *ˇ Lorem ipsum dolor sit amet,
 7956             * consectetur adipiscing elit.
 7957             */
 7958        "},
 7959        rust_lang.clone(),
 7960        &mut cx,
 7961    );
 7962
 7963    // different, adjacent comments
 7964    assert_rewrap(
 7965        indoc! {"
 7966            /**
 7967             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7968             */
 7969            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7970            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7971        "},
 7972        indoc! {"
 7973            /**
 7974             *ˇ Lorem ipsum dolor sit amet,
 7975             * consectetur adipiscing elit.
 7976             */
 7977            /*
 7978             *ˇ Lorem ipsum dolor sit amet,
 7979             * consectetur adipiscing elit.
 7980             */
 7981            //ˇ Lorem ipsum dolor sit amet,
 7982            // consectetur adipiscing elit.
 7983        "},
 7984        rust_lang.clone(),
 7985        &mut cx,
 7986    );
 7987
 7988    // selection w/ single short block comment
 7989    assert_rewrap(
 7990        indoc! {"
 7991            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7992        "},
 7993        indoc! {"
 7994            «/*
 7995             * Lorem ipsum dolor sit amet,
 7996             * consectetur adipiscing elit.
 7997             */ˇ»
 7998        "},
 7999        rust_lang.clone(),
 8000        &mut cx,
 8001    );
 8002
 8003    // rewrapping a single comment w/ abutting comments
 8004    assert_rewrap(
 8005        indoc! {"
 8006            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8007            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8008        "},
 8009        indoc! {"
 8010            /*
 8011             * ˇLorem ipsum dolor sit amet,
 8012             * consectetur adipiscing elit.
 8013             */
 8014            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8015        "},
 8016        rust_lang.clone(),
 8017        &mut cx,
 8018    );
 8019
 8020    // selection w/ non-abutting short block comments
 8021    assert_rewrap(
 8022        indoc! {"
 8023            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8024
 8025            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 8026        "},
 8027        indoc! {"
 8028            «/*
 8029             * Lorem ipsum dolor sit amet,
 8030             * consectetur adipiscing elit.
 8031             */
 8032
 8033            /*
 8034             * Lorem ipsum dolor sit amet,
 8035             * consectetur adipiscing elit.
 8036             */ˇ»
 8037        "},
 8038        rust_lang.clone(),
 8039        &mut cx,
 8040    );
 8041
 8042    // selection of multiline block comments
 8043    assert_rewrap(
 8044        indoc! {"
 8045            «/* Lorem ipsum dolor sit amet,
 8046             * consectetur adipiscing elit. */ˇ»
 8047        "},
 8048        indoc! {"
 8049            «/*
 8050             * Lorem ipsum dolor sit amet,
 8051             * consectetur adipiscing elit.
 8052             */ˇ»
 8053        "},
 8054        rust_lang.clone(),
 8055        &mut cx,
 8056    );
 8057
 8058    // partial selection of multiline block comments
 8059    assert_rewrap(
 8060        indoc! {"
 8061            «/* Lorem ipsum dolor sit amet,ˇ»
 8062             * consectetur adipiscing elit. */
 8063            /* Lorem ipsum dolor sit amet,
 8064             «* consectetur adipiscing elit. */ˇ»
 8065        "},
 8066        indoc! {"
 8067            «/*
 8068             * Lorem ipsum dolor sit amet,ˇ»
 8069             * consectetur adipiscing elit. */
 8070            /* Lorem ipsum dolor sit amet,
 8071             «* consectetur adipiscing elit.
 8072             */ˇ»
 8073        "},
 8074        rust_lang.clone(),
 8075        &mut cx,
 8076    );
 8077
 8078    // selection w/ abutting short block comments
 8079    // TODO: should not be combined; should rewrap as 2 comments
 8080    assert_rewrap(
 8081        indoc! {"
 8082            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8083            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 8084        "},
 8085        // desired behavior:
 8086        // indoc! {"
 8087        //     «/*
 8088        //      * Lorem ipsum dolor sit amet,
 8089        //      * consectetur adipiscing elit.
 8090        //      */
 8091        //     /*
 8092        //      * Lorem ipsum dolor sit amet,
 8093        //      * consectetur adipiscing elit.
 8094        //      */ˇ»
 8095        // "},
 8096        // actual behaviour:
 8097        indoc! {"
 8098            «/*
 8099             * Lorem ipsum dolor sit amet,
 8100             * consectetur adipiscing elit. Lorem
 8101             * ipsum dolor sit amet, consectetur
 8102             * adipiscing elit.
 8103             */ˇ»
 8104        "},
 8105        rust_lang.clone(),
 8106        &mut cx,
 8107    );
 8108
 8109    // TODO: same as above, but with delimiters on separate line
 8110    // assert_rewrap(
 8111    //     indoc! {"
 8112    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 8113    //          */
 8114    //         /*
 8115    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 8116    //     "},
 8117    //     // desired:
 8118    //     // indoc! {"
 8119    //     //     «/*
 8120    //     //      * Lorem ipsum dolor sit amet,
 8121    //     //      * consectetur adipiscing elit.
 8122    //     //      */
 8123    //     //     /*
 8124    //     //      * Lorem ipsum dolor sit amet,
 8125    //     //      * consectetur adipiscing elit.
 8126    //     //      */ˇ»
 8127    //     // "},
 8128    //     // actual: (but with trailing w/s on the empty lines)
 8129    //     indoc! {"
 8130    //         «/*
 8131    //          * Lorem ipsum dolor sit amet,
 8132    //          * consectetur adipiscing elit.
 8133    //          *
 8134    //          */
 8135    //         /*
 8136    //          *
 8137    //          * Lorem ipsum dolor sit amet,
 8138    //          * consectetur adipiscing elit.
 8139    //          */ˇ»
 8140    //     "},
 8141    //     rust_lang.clone(),
 8142    //     &mut cx,
 8143    // );
 8144
 8145    // TODO these are unhandled edge cases; not correct, just documenting known issues
 8146    assert_rewrap(
 8147        indoc! {"
 8148            /*
 8149             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 8150             */
 8151            /*
 8152             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 8153            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 8154        "},
 8155        // desired:
 8156        // indoc! {"
 8157        //     /*
 8158        //      *ˇ Lorem ipsum dolor sit amet,
 8159        //      * consectetur adipiscing elit.
 8160        //      */
 8161        //     /*
 8162        //      *ˇ Lorem ipsum dolor sit amet,
 8163        //      * consectetur adipiscing elit.
 8164        //      */
 8165        //     /*
 8166        //      *ˇ Lorem ipsum dolor sit amet
 8167        //      */ /* consectetur adipiscing elit. */
 8168        // "},
 8169        // actual:
 8170        indoc! {"
 8171            /*
 8172             //ˇ Lorem ipsum dolor sit amet,
 8173             // consectetur adipiscing elit.
 8174             */
 8175            /*
 8176             * //ˇ Lorem ipsum dolor sit amet,
 8177             * consectetur adipiscing elit.
 8178             */
 8179            /*
 8180             *ˇ Lorem ipsum dolor sit amet */ /*
 8181             * consectetur adipiscing elit.
 8182             */
 8183        "},
 8184        rust_lang,
 8185        &mut cx,
 8186    );
 8187
 8188    #[track_caller]
 8189    fn assert_rewrap(
 8190        unwrapped_text: &str,
 8191        wrapped_text: &str,
 8192        language: Arc<Language>,
 8193        cx: &mut EditorTestContext,
 8194    ) {
 8195        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 8196        cx.set_state(unwrapped_text);
 8197        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 8198        cx.assert_editor_state(wrapped_text);
 8199    }
 8200}
 8201
 8202#[gpui::test]
 8203async fn test_hard_wrap(cx: &mut TestAppContext) {
 8204    init_test(cx, |_| {});
 8205    let mut cx = EditorTestContext::new(cx).await;
 8206
 8207    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 8208    cx.update_editor(|editor, _, cx| {
 8209        editor.set_hard_wrap(Some(14), cx);
 8210    });
 8211
 8212    cx.set_state(indoc!(
 8213        "
 8214        one two three ˇ
 8215        "
 8216    ));
 8217    cx.simulate_input("four");
 8218    cx.run_until_parked();
 8219
 8220    cx.assert_editor_state(indoc!(
 8221        "
 8222        one two three
 8223        fourˇ
 8224        "
 8225    ));
 8226
 8227    cx.update_editor(|editor, window, cx| {
 8228        editor.newline(&Default::default(), window, cx);
 8229    });
 8230    cx.run_until_parked();
 8231    cx.assert_editor_state(indoc!(
 8232        "
 8233        one two three
 8234        four
 8235        ˇ
 8236        "
 8237    ));
 8238
 8239    cx.simulate_input("five");
 8240    cx.run_until_parked();
 8241    cx.assert_editor_state(indoc!(
 8242        "
 8243        one two three
 8244        four
 8245        fiveˇ
 8246        "
 8247    ));
 8248
 8249    cx.update_editor(|editor, window, cx| {
 8250        editor.newline(&Default::default(), window, cx);
 8251    });
 8252    cx.run_until_parked();
 8253    cx.simulate_input("# ");
 8254    cx.run_until_parked();
 8255    cx.assert_editor_state(indoc!(
 8256        "
 8257        one two three
 8258        four
 8259        five
 8260        # ˇ
 8261        "
 8262    ));
 8263
 8264    cx.update_editor(|editor, window, cx| {
 8265        editor.newline(&Default::default(), window, cx);
 8266    });
 8267    cx.run_until_parked();
 8268    cx.assert_editor_state(indoc!(
 8269        "
 8270        one two three
 8271        four
 8272        five
 8273        #\x20
 8274 8275        "
 8276    ));
 8277
 8278    cx.simulate_input(" 6");
 8279    cx.run_until_parked();
 8280    cx.assert_editor_state(indoc!(
 8281        "
 8282        one two three
 8283        four
 8284        five
 8285        #
 8286        # 6ˇ
 8287        "
 8288    ));
 8289}
 8290
 8291#[gpui::test]
 8292async fn test_cut_line_ends(cx: &mut TestAppContext) {
 8293    init_test(cx, |_| {});
 8294
 8295    let mut cx = EditorTestContext::new(cx).await;
 8296
 8297    cx.set_state(indoc! {"The quick brownˇ"});
 8298    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 8299    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 8300
 8301    cx.set_state(indoc! {"The emacs foxˇ"});
 8302    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 8303    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 8304
 8305    cx.set_state(indoc! {"
 8306        The quick« brownˇ»
 8307        fox jumps overˇ
 8308        the lazy dog"});
 8309    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8310    cx.assert_editor_state(indoc! {"
 8311        The quickˇ
 8312        ˇthe lazy dog"});
 8313
 8314    cx.set_state(indoc! {"
 8315        The quick« brownˇ»
 8316        fox jumps overˇ
 8317        the lazy dog"});
 8318    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 8319    cx.assert_editor_state(indoc! {"
 8320        The quickˇ
 8321        fox jumps overˇthe lazy dog"});
 8322
 8323    cx.set_state(indoc! {"
 8324        The quick« brownˇ»
 8325        fox jumps overˇ
 8326        the lazy dog"});
 8327    cx.update_editor(|e, window, cx| {
 8328        e.cut_to_end_of_line(
 8329            &CutToEndOfLine {
 8330                stop_at_newlines: true,
 8331            },
 8332            window,
 8333            cx,
 8334        )
 8335    });
 8336    cx.assert_editor_state(indoc! {"
 8337        The quickˇ
 8338        fox jumps overˇ
 8339        the lazy dog"});
 8340
 8341    cx.set_state(indoc! {"
 8342        The quick« brownˇ»
 8343        fox jumps overˇ
 8344        the lazy dog"});
 8345    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 8346    cx.assert_editor_state(indoc! {"
 8347        The quickˇ
 8348        fox jumps overˇthe lazy dog"});
 8349}
 8350
 8351#[gpui::test]
 8352async fn test_clipboard(cx: &mut TestAppContext) {
 8353    init_test(cx, |_| {});
 8354
 8355    let mut cx = EditorTestContext::new(cx).await;
 8356
 8357    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 8358    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8359    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 8360
 8361    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 8362    cx.set_state("two ˇfour ˇsix ˇ");
 8363    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8364    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 8365
 8366    // Paste again but with only two cursors. Since the number of cursors doesn't
 8367    // match the number of slices in the clipboard, the entire clipboard text
 8368    // is pasted at each cursor.
 8369    cx.set_state("ˇtwo one✅ four three six five ˇ");
 8370    cx.update_editor(|e, window, cx| {
 8371        e.handle_input("( ", window, cx);
 8372        e.paste(&Paste, window, cx);
 8373        e.handle_input(") ", window, cx);
 8374    });
 8375    cx.assert_editor_state(
 8376        &([
 8377            "( one✅ ",
 8378            "three ",
 8379            "five ) ˇtwo one✅ four three six five ( one✅ ",
 8380            "three ",
 8381            "five ) ˇ",
 8382        ]
 8383        .join("\n")),
 8384    );
 8385
 8386    // Cut with three selections, one of which is full-line.
 8387    cx.set_state(indoc! {"
 8388        1«2ˇ»3
 8389        4ˇ567
 8390        «8ˇ»9"});
 8391    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8392    cx.assert_editor_state(indoc! {"
 8393        1ˇ3
 8394        ˇ9"});
 8395
 8396    // Paste with three selections, noticing how the copied selection that was full-line
 8397    // gets inserted before the second cursor.
 8398    cx.set_state(indoc! {"
 8399        1ˇ3
 8400 8401        «oˇ»ne"});
 8402    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8403    cx.assert_editor_state(indoc! {"
 8404        12ˇ3
 8405        4567
 8406 8407        8ˇne"});
 8408
 8409    // Copy with a single cursor only, which writes the whole line into the clipboard.
 8410    cx.set_state(indoc! {"
 8411        The quick brown
 8412        fox juˇmps over
 8413        the lazy dog"});
 8414    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8415    assert_eq!(
 8416        cx.read_from_clipboard()
 8417            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8418        Some("fox jumps over\n".to_string())
 8419    );
 8420
 8421    // Paste with three selections, noticing how the copied full-line selection is inserted
 8422    // before the empty selections but replaces the selection that is non-empty.
 8423    cx.set_state(indoc! {"
 8424        Tˇhe quick brown
 8425        «foˇ»x jumps over
 8426        tˇhe lazy dog"});
 8427    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8428    cx.assert_editor_state(indoc! {"
 8429        fox jumps over
 8430        Tˇhe quick brown
 8431        fox jumps over
 8432        ˇx jumps over
 8433        fox jumps over
 8434        tˇhe lazy dog"});
 8435}
 8436
 8437#[gpui::test]
 8438async fn test_copy_trim(cx: &mut TestAppContext) {
 8439    init_test(cx, |_| {});
 8440
 8441    let mut cx = EditorTestContext::new(cx).await;
 8442    cx.set_state(
 8443        r#"            «for selection in selections.iter() {
 8444            let mut start = selection.start;
 8445            let mut end = selection.end;
 8446            let is_entire_line = selection.is_empty();
 8447            if is_entire_line {
 8448                start = Point::new(start.row, 0);ˇ»
 8449                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8450            }
 8451        "#,
 8452    );
 8453    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8454    assert_eq!(
 8455        cx.read_from_clipboard()
 8456            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8457        Some(
 8458            "for selection in selections.iter() {
 8459            let mut start = selection.start;
 8460            let mut end = selection.end;
 8461            let is_entire_line = selection.is_empty();
 8462            if is_entire_line {
 8463                start = Point::new(start.row, 0);"
 8464                .to_string()
 8465        ),
 8466        "Regular copying preserves all indentation selected",
 8467    );
 8468    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8469    assert_eq!(
 8470        cx.read_from_clipboard()
 8471            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8472        Some(
 8473            "for selection in selections.iter() {
 8474let mut start = selection.start;
 8475let mut end = selection.end;
 8476let is_entire_line = selection.is_empty();
 8477if is_entire_line {
 8478    start = Point::new(start.row, 0);"
 8479                .to_string()
 8480        ),
 8481        "Copying with stripping should strip all leading whitespaces"
 8482    );
 8483
 8484    cx.set_state(
 8485        r#"       «     for selection in selections.iter() {
 8486            let mut start = selection.start;
 8487            let mut end = selection.end;
 8488            let is_entire_line = selection.is_empty();
 8489            if is_entire_line {
 8490                start = Point::new(start.row, 0);ˇ»
 8491                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8492            }
 8493        "#,
 8494    );
 8495    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8496    assert_eq!(
 8497        cx.read_from_clipboard()
 8498            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8499        Some(
 8500            "     for selection in selections.iter() {
 8501            let mut start = selection.start;
 8502            let mut end = selection.end;
 8503            let is_entire_line = selection.is_empty();
 8504            if is_entire_line {
 8505                start = Point::new(start.row, 0);"
 8506                .to_string()
 8507        ),
 8508        "Regular copying preserves all indentation selected",
 8509    );
 8510    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8511    assert_eq!(
 8512        cx.read_from_clipboard()
 8513            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8514        Some(
 8515            "for selection in selections.iter() {
 8516let mut start = selection.start;
 8517let mut end = selection.end;
 8518let is_entire_line = selection.is_empty();
 8519if is_entire_line {
 8520    start = Point::new(start.row, 0);"
 8521                .to_string()
 8522        ),
 8523        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 8524    );
 8525
 8526    cx.set_state(
 8527        r#"       «ˇ     for selection in selections.iter() {
 8528            let mut start = selection.start;
 8529            let mut end = selection.end;
 8530            let is_entire_line = selection.is_empty();
 8531            if is_entire_line {
 8532                start = Point::new(start.row, 0);»
 8533                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8534            }
 8535        "#,
 8536    );
 8537    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8538    assert_eq!(
 8539        cx.read_from_clipboard()
 8540            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8541        Some(
 8542            "     for selection in selections.iter() {
 8543            let mut start = selection.start;
 8544            let mut end = selection.end;
 8545            let is_entire_line = selection.is_empty();
 8546            if is_entire_line {
 8547                start = Point::new(start.row, 0);"
 8548                .to_string()
 8549        ),
 8550        "Regular copying for reverse selection works the same",
 8551    );
 8552    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8553    assert_eq!(
 8554        cx.read_from_clipboard()
 8555            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8556        Some(
 8557            "for selection in selections.iter() {
 8558let mut start = selection.start;
 8559let mut end = selection.end;
 8560let is_entire_line = selection.is_empty();
 8561if is_entire_line {
 8562    start = Point::new(start.row, 0);"
 8563                .to_string()
 8564        ),
 8565        "Copying with stripping for reverse selection works the same"
 8566    );
 8567
 8568    cx.set_state(
 8569        r#"            for selection «in selections.iter() {
 8570            let mut start = selection.start;
 8571            let mut end = selection.end;
 8572            let is_entire_line = selection.is_empty();
 8573            if is_entire_line {
 8574                start = Point::new(start.row, 0);ˇ»
 8575                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8576            }
 8577        "#,
 8578    );
 8579    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8580    assert_eq!(
 8581        cx.read_from_clipboard()
 8582            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8583        Some(
 8584            "in selections.iter() {
 8585            let mut start = selection.start;
 8586            let mut end = selection.end;
 8587            let is_entire_line = selection.is_empty();
 8588            if is_entire_line {
 8589                start = Point::new(start.row, 0);"
 8590                .to_string()
 8591        ),
 8592        "When selecting past the indent, the copying works as usual",
 8593    );
 8594    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8595    assert_eq!(
 8596        cx.read_from_clipboard()
 8597            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8598        Some(
 8599            "in selections.iter() {
 8600            let mut start = selection.start;
 8601            let mut end = selection.end;
 8602            let is_entire_line = selection.is_empty();
 8603            if is_entire_line {
 8604                start = Point::new(start.row, 0);"
 8605                .to_string()
 8606        ),
 8607        "When selecting past the indent, nothing is trimmed"
 8608    );
 8609
 8610    cx.set_state(
 8611        r#"            «for selection in selections.iter() {
 8612            let mut start = selection.start;
 8613
 8614            let mut end = selection.end;
 8615            let is_entire_line = selection.is_empty();
 8616            if is_entire_line {
 8617                start = Point::new(start.row, 0);
 8618ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8619            }
 8620        "#,
 8621    );
 8622    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8623    assert_eq!(
 8624        cx.read_from_clipboard()
 8625            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8626        Some(
 8627            "for selection in selections.iter() {
 8628let mut start = selection.start;
 8629
 8630let mut end = selection.end;
 8631let is_entire_line = selection.is_empty();
 8632if is_entire_line {
 8633    start = Point::new(start.row, 0);
 8634"
 8635            .to_string()
 8636        ),
 8637        "Copying with stripping should ignore empty lines"
 8638    );
 8639}
 8640
 8641#[gpui::test]
 8642async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 8643    init_test(cx, |_| {});
 8644
 8645    let mut cx = EditorTestContext::new(cx).await;
 8646
 8647    cx.set_state(indoc! {"
 8648        «    fn main() {
 8649                1
 8650            }ˇ»
 8651    "});
 8652    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 8653    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 8654
 8655    assert_eq!(
 8656        cx.read_from_clipboard().and_then(|item| item.text()),
 8657        Some("fn main() {\n    1\n}\n".to_string())
 8658    );
 8659
 8660    let clipboard_selections: Vec<ClipboardSelection> = cx
 8661        .read_from_clipboard()
 8662        .and_then(|item| item.entries().first().cloned())
 8663        .and_then(|entry| match entry {
 8664            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8665            _ => None,
 8666        })
 8667        .expect("should have clipboard selections");
 8668
 8669    assert_eq!(clipboard_selections.len(), 1);
 8670    assert!(clipboard_selections[0].is_entire_line);
 8671
 8672    cx.set_state(indoc! {"
 8673        «fn main() {
 8674            1
 8675        }ˇ»
 8676    "});
 8677    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 8678    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 8679
 8680    assert_eq!(
 8681        cx.read_from_clipboard().and_then(|item| item.text()),
 8682        Some("fn main() {\n    1\n}\n".to_string())
 8683    );
 8684
 8685    let clipboard_selections: Vec<ClipboardSelection> = cx
 8686        .read_from_clipboard()
 8687        .and_then(|item| item.entries().first().cloned())
 8688        .and_then(|entry| match entry {
 8689            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8690            _ => None,
 8691        })
 8692        .expect("should have clipboard selections");
 8693
 8694    assert_eq!(clipboard_selections.len(), 1);
 8695    assert!(clipboard_selections[0].is_entire_line);
 8696}
 8697
 8698#[gpui::test]
 8699async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 8700    init_test(cx, |_| {});
 8701
 8702    let fs = FakeFs::new(cx.executor());
 8703    fs.insert_file(
 8704        path!("/file.txt"),
 8705        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 8706    )
 8707    .await;
 8708
 8709    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 8710
 8711    let buffer = project
 8712        .update(cx, |project, cx| {
 8713            project.open_local_buffer(path!("/file.txt"), cx)
 8714        })
 8715        .await
 8716        .unwrap();
 8717
 8718    let multibuffer = cx.new(|cx| {
 8719        let mut multibuffer = MultiBuffer::new(ReadWrite);
 8720        multibuffer.set_excerpts_for_path(
 8721            PathKey::sorted(0),
 8722            buffer.clone(),
 8723            [Point::new(2, 0)..Point::new(5, 0)],
 8724            0,
 8725            cx,
 8726        );
 8727        multibuffer
 8728    });
 8729
 8730    let (editor, cx) = cx.add_window_view(|window, cx| {
 8731        build_editor_with_project(project.clone(), multibuffer, window, cx)
 8732    });
 8733
 8734    editor.update_in(cx, |editor, window, cx| {
 8735        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 8736
 8737        editor.select_all(&SelectAll, window, cx);
 8738        editor.copy(&Copy, window, cx);
 8739    });
 8740
 8741    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 8742        .read_from_clipboard()
 8743        .and_then(|item| item.entries().first().cloned())
 8744        .and_then(|entry| match entry {
 8745            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8746            _ => None,
 8747        });
 8748
 8749    let selections = clipboard_selections.expect("should have clipboard selections");
 8750    assert_eq!(selections.len(), 1);
 8751    let selection = &selections[0];
 8752    assert_eq!(
 8753        selection.line_range,
 8754        Some(2..=5),
 8755        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 8756    );
 8757}
 8758
 8759#[gpui::test]
 8760async fn test_paste_multiline(cx: &mut TestAppContext) {
 8761    init_test(cx, |_| {});
 8762
 8763    let mut cx = EditorTestContext::new(cx).await;
 8764    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8765
 8766    // Cut an indented block, without the leading whitespace.
 8767    cx.set_state(indoc! {"
 8768        const a: B = (
 8769            c(),
 8770            «d(
 8771                e,
 8772                f
 8773            )ˇ»
 8774        );
 8775    "});
 8776    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8777    cx.assert_editor_state(indoc! {"
 8778        const a: B = (
 8779            c(),
 8780            ˇ
 8781        );
 8782    "});
 8783
 8784    // Paste it at the same position.
 8785    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8786    cx.assert_editor_state(indoc! {"
 8787        const a: B = (
 8788            c(),
 8789            d(
 8790                e,
 8791                f
 8792 8793        );
 8794    "});
 8795
 8796    // Paste it at a line with a lower indent level.
 8797    cx.set_state(indoc! {"
 8798        ˇ
 8799        const a: B = (
 8800            c(),
 8801        );
 8802    "});
 8803    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8804    cx.assert_editor_state(indoc! {"
 8805        d(
 8806            e,
 8807            f
 8808 8809        const a: B = (
 8810            c(),
 8811        );
 8812    "});
 8813
 8814    // Cut an indented block, with the leading whitespace.
 8815    cx.set_state(indoc! {"
 8816        const a: B = (
 8817            c(),
 8818        «    d(
 8819                e,
 8820                f
 8821            )
 8822        ˇ»);
 8823    "});
 8824    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8825    cx.assert_editor_state(indoc! {"
 8826        const a: B = (
 8827            c(),
 8828        ˇ);
 8829    "});
 8830
 8831    // Paste it at the same position.
 8832    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8833    cx.assert_editor_state(indoc! {"
 8834        const a: B = (
 8835            c(),
 8836            d(
 8837                e,
 8838                f
 8839            )
 8840        ˇ);
 8841    "});
 8842
 8843    // Paste it at a line with a higher indent level.
 8844    cx.set_state(indoc! {"
 8845        const a: B = (
 8846            c(),
 8847            d(
 8848                e,
 8849 8850            )
 8851        );
 8852    "});
 8853    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8854    cx.assert_editor_state(indoc! {"
 8855        const a: B = (
 8856            c(),
 8857            d(
 8858                e,
 8859                f    d(
 8860                    e,
 8861                    f
 8862                )
 8863        ˇ
 8864            )
 8865        );
 8866    "});
 8867
 8868    // Copy an indented block, starting mid-line
 8869    cx.set_state(indoc! {"
 8870        const a: B = (
 8871            c(),
 8872            somethin«g(
 8873                e,
 8874                f
 8875            )ˇ»
 8876        );
 8877    "});
 8878    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8879
 8880    // Paste it on a line with a lower indent level
 8881    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 8882    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8883    cx.assert_editor_state(indoc! {"
 8884        const a: B = (
 8885            c(),
 8886            something(
 8887                e,
 8888                f
 8889            )
 8890        );
 8891        g(
 8892            e,
 8893            f
 8894"});
 8895}
 8896
 8897#[gpui::test]
 8898async fn test_paste_undo_does_not_include_preceding_edits(cx: &mut TestAppContext) {
 8899    init_test(cx, |_| {});
 8900
 8901    let mut cx = EditorTestContext::new(cx).await;
 8902
 8903    cx.update_editor(|e, _, cx| {
 8904        e.buffer().update(cx, |buffer, cx| {
 8905            buffer.set_group_interval(Duration::from_secs(10), cx)
 8906        })
 8907    });
 8908    // Type some text
 8909    cx.set_state("ˇ");
 8910    cx.update_editor(|e, window, cx| e.insert("hello", window, cx));
 8911    // cx.assert_editor_state("helloˇ");
 8912
 8913    // Paste some text immediately after typing
 8914    cx.write_to_clipboard(ClipboardItem::new_string(" world".into()));
 8915    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8916    cx.assert_editor_state("hello worldˇ");
 8917
 8918    // Undo should only undo the paste, not the preceding typing
 8919    cx.update_editor(|e, window, cx| e.undo(&Undo, window, cx));
 8920    cx.assert_editor_state("helloˇ");
 8921
 8922    // Undo again should undo the typing
 8923    cx.update_editor(|e, window, cx| e.undo(&Undo, window, cx));
 8924    cx.assert_editor_state("ˇ");
 8925}
 8926
 8927#[gpui::test]
 8928async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 8929    init_test(cx, |_| {});
 8930
 8931    cx.write_to_clipboard(ClipboardItem::new_string(
 8932        "    d(\n        e\n    );\n".into(),
 8933    ));
 8934
 8935    let mut cx = EditorTestContext::new(cx).await;
 8936    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8937    cx.run_until_parked();
 8938
 8939    cx.set_state(indoc! {"
 8940        fn a() {
 8941            b();
 8942            if c() {
 8943                ˇ
 8944            }
 8945        }
 8946    "});
 8947
 8948    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8949    cx.assert_editor_state(indoc! {"
 8950        fn a() {
 8951            b();
 8952            if c() {
 8953                d(
 8954                    e
 8955                );
 8956        ˇ
 8957            }
 8958        }
 8959    "});
 8960
 8961    cx.set_state(indoc! {"
 8962        fn a() {
 8963            b();
 8964            ˇ
 8965        }
 8966    "});
 8967
 8968    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8969    cx.assert_editor_state(indoc! {"
 8970        fn a() {
 8971            b();
 8972            d(
 8973                e
 8974            );
 8975        ˇ
 8976        }
 8977    "});
 8978}
 8979
 8980#[gpui::test]
 8981async fn test_paste_multiline_from_other_app_into_matching_cursors(cx: &mut TestAppContext) {
 8982    init_test(cx, |_| {});
 8983
 8984    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 8985
 8986    let mut cx = EditorTestContext::new(cx).await;
 8987
 8988    // Paste into 3 cursors: each cursor should receive one line.
 8989    cx.set_state("ˇ one ˇ two ˇ three");
 8990    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8991    cx.assert_editor_state("alphaˇ one betaˇ two gammaˇ three");
 8992
 8993    // Paste into 2 cursors: line count doesn't match, so paste entire text at each cursor.
 8994    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 8995    cx.set_state("ˇ one ˇ two");
 8996    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8997    cx.assert_editor_state("alpha\nbeta\ngammaˇ one alpha\nbeta\ngammaˇ two");
 8998
 8999    // Paste into a single cursor: should paste everything as-is.
 9000    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 9001    cx.set_state("ˇ one");
 9002    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 9003    cx.assert_editor_state("alpha\nbeta\ngammaˇ one");
 9004
 9005    // Paste with selections: each selection is replaced with its corresponding line.
 9006    cx.write_to_clipboard(ClipboardItem::new_string("xx\nyy\nzz".into()));
 9007    cx.set_state("«aˇ» one «bˇ» two «cˇ» three");
 9008    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 9009    cx.assert_editor_state("xxˇ one yyˇ two zzˇ three");
 9010}
 9011
 9012#[gpui::test]
 9013fn test_select_all(cx: &mut TestAppContext) {
 9014    init_test(cx, |_| {});
 9015
 9016    let editor = cx.add_window(|window, cx| {
 9017        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 9018        build_editor(buffer, window, cx)
 9019    });
 9020    _ = editor.update(cx, |editor, window, cx| {
 9021        editor.select_all(&SelectAll, window, cx);
 9022        assert_eq!(
 9023            display_ranges(editor, cx),
 9024            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 9025        );
 9026    });
 9027}
 9028
 9029#[gpui::test]
 9030fn test_select_line(cx: &mut TestAppContext) {
 9031    init_test(cx, |_| {});
 9032
 9033    let editor = cx.add_window(|window, cx| {
 9034        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 9035        build_editor(buffer, window, cx)
 9036    });
 9037    _ = editor.update(cx, |editor, window, cx| {
 9038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9039            s.select_display_ranges([
 9040                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9041                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 9042                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 9043                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 9044            ])
 9045        });
 9046        editor.select_line(&SelectLine, window, cx);
 9047        // Adjacent line selections should NOT merge (only overlapping ones do)
 9048        assert_eq!(
 9049            display_ranges(editor, cx),
 9050            vec![
 9051                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 9052                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 9053                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 9054            ]
 9055        );
 9056    });
 9057
 9058    _ = editor.update(cx, |editor, window, cx| {
 9059        editor.select_line(&SelectLine, window, cx);
 9060        assert_eq!(
 9061            display_ranges(editor, cx),
 9062            vec![
 9063                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 9064                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 9065            ]
 9066        );
 9067    });
 9068
 9069    _ = editor.update(cx, |editor, window, cx| {
 9070        editor.select_line(&SelectLine, window, cx);
 9071        // Adjacent but not overlapping, so they stay separate
 9072        assert_eq!(
 9073            display_ranges(editor, cx),
 9074            vec![
 9075                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 9076                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 9077            ]
 9078        );
 9079    });
 9080}
 9081
 9082#[gpui::test]
 9083async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 9084    init_test(cx, |_| {});
 9085    let mut cx = EditorTestContext::new(cx).await;
 9086
 9087    #[track_caller]
 9088    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 9089        cx.set_state(initial_state);
 9090        cx.update_editor(|e, window, cx| {
 9091            e.split_selection_into_lines(&Default::default(), window, cx)
 9092        });
 9093        cx.assert_editor_state(expected_state);
 9094    }
 9095
 9096    // Selection starts and ends at the middle of lines, left-to-right
 9097    test(
 9098        &mut cx,
 9099        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 9100        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 9101    );
 9102    // Same thing, right-to-left
 9103    test(
 9104        &mut cx,
 9105        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 9106        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 9107    );
 9108
 9109    // Whole buffer, left-to-right, last line *doesn't* end with newline
 9110    test(
 9111        &mut cx,
 9112        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 9113        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 9114    );
 9115    // Same thing, right-to-left
 9116    test(
 9117        &mut cx,
 9118        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 9119        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 9120    );
 9121
 9122    // Whole buffer, left-to-right, last line ends with newline
 9123    test(
 9124        &mut cx,
 9125        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 9126        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 9127    );
 9128    // Same thing, right-to-left
 9129    test(
 9130        &mut cx,
 9131        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 9132        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 9133    );
 9134
 9135    // Starts at the end of a line, ends at the start of another
 9136    test(
 9137        &mut cx,
 9138        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 9139        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 9140    );
 9141}
 9142
 9143#[gpui::test]
 9144async fn test_split_selection_into_lines_does_not_scroll(cx: &mut TestAppContext) {
 9145    init_test(cx, |_| {});
 9146    let mut cx = EditorTestContext::new(cx).await;
 9147
 9148    let large_body = "\nline".repeat(300);
 9149    cx.set_state(&format!("«ˇstart{large_body}\nend»"));
 9150    let initial_scroll_position = cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 9151
 9152    cx.update_editor(|editor, window, cx| {
 9153        editor.split_selection_into_lines(&Default::default(), window, cx);
 9154    });
 9155
 9156    let scroll_position_after_split = cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 9157    assert_eq!(
 9158        initial_scroll_position, scroll_position_after_split,
 9159        "Scroll position should not change after splitting selection into lines"
 9160    );
 9161}
 9162
 9163#[gpui::test]
 9164async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 9165    init_test(cx, |_| {});
 9166
 9167    let editor = cx.add_window(|window, cx| {
 9168        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 9169        build_editor(buffer, window, cx)
 9170    });
 9171
 9172    // setup
 9173    _ = editor.update(cx, |editor, window, cx| {
 9174        editor.fold_creases(
 9175            vec![
 9176                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 9177                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 9178                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 9179            ],
 9180            true,
 9181            window,
 9182            cx,
 9183        );
 9184        assert_eq!(
 9185            editor.display_text(cx),
 9186            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 9187        );
 9188    });
 9189
 9190    _ = editor.update(cx, |editor, window, cx| {
 9191        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9192            s.select_display_ranges([
 9193                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9194                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 9195                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 9196                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 9197            ])
 9198        });
 9199        editor.split_selection_into_lines(&Default::default(), window, cx);
 9200        assert_eq!(
 9201            editor.display_text(cx),
 9202            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 9203        );
 9204    });
 9205    EditorTestContext::for_editor(editor, cx)
 9206        .await
 9207        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 9208
 9209    _ = editor.update(cx, |editor, window, cx| {
 9210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9211            s.select_display_ranges([
 9212                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 9213            ])
 9214        });
 9215        editor.split_selection_into_lines(&Default::default(), window, cx);
 9216        assert_eq!(
 9217            editor.display_text(cx),
 9218            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 9219        );
 9220        assert_eq!(
 9221            display_ranges(editor, cx),
 9222            [
 9223                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 9224                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 9225                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 9226                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 9227                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 9228                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 9229                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 9230            ]
 9231        );
 9232    });
 9233    EditorTestContext::for_editor(editor, cx)
 9234        .await
 9235        .assert_editor_state(
 9236            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 9237        );
 9238}
 9239
 9240#[gpui::test]
 9241async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 9242    init_test(cx, |_| {});
 9243
 9244    let mut cx = EditorTestContext::new(cx).await;
 9245
 9246    cx.set_state(indoc!(
 9247        r#"abc
 9248           defˇghi
 9249
 9250           jk
 9251           nlmo
 9252           "#
 9253    ));
 9254
 9255    cx.update_editor(|editor, window, cx| {
 9256        editor.add_selection_above(&Default::default(), window, cx);
 9257    });
 9258
 9259    cx.assert_editor_state(indoc!(
 9260        r#"abcˇ
 9261           defˇghi
 9262
 9263           jk
 9264           nlmo
 9265           "#
 9266    ));
 9267
 9268    cx.update_editor(|editor, window, cx| {
 9269        editor.add_selection_above(&Default::default(), window, cx);
 9270    });
 9271
 9272    cx.assert_editor_state(indoc!(
 9273        r#"abcˇ
 9274            defˇghi
 9275
 9276            jk
 9277            nlmo
 9278            "#
 9279    ));
 9280
 9281    cx.update_editor(|editor, window, cx| {
 9282        editor.add_selection_below(&Default::default(), window, cx);
 9283    });
 9284
 9285    cx.assert_editor_state(indoc!(
 9286        r#"abc
 9287           defˇghi
 9288
 9289           jk
 9290           nlmo
 9291           "#
 9292    ));
 9293
 9294    cx.update_editor(|editor, window, cx| {
 9295        editor.undo_selection(&Default::default(), window, cx);
 9296    });
 9297
 9298    cx.assert_editor_state(indoc!(
 9299        r#"abcˇ
 9300           defˇghi
 9301
 9302           jk
 9303           nlmo
 9304           "#
 9305    ));
 9306
 9307    cx.update_editor(|editor, window, cx| {
 9308        editor.redo_selection(&Default::default(), window, cx);
 9309    });
 9310
 9311    cx.assert_editor_state(indoc!(
 9312        r#"abc
 9313           defˇghi
 9314
 9315           jk
 9316           nlmo
 9317           "#
 9318    ));
 9319
 9320    cx.update_editor(|editor, window, cx| {
 9321        editor.add_selection_below(&Default::default(), window, cx);
 9322    });
 9323
 9324    cx.assert_editor_state(indoc!(
 9325        r#"abc
 9326           defˇghi
 9327           ˇ
 9328           jk
 9329           nlmo
 9330           "#
 9331    ));
 9332
 9333    cx.update_editor(|editor, window, cx| {
 9334        editor.add_selection_below(&Default::default(), window, cx);
 9335    });
 9336
 9337    cx.assert_editor_state(indoc!(
 9338        r#"abc
 9339           defˇghi
 9340           ˇ
 9341           jkˇ
 9342           nlmo
 9343           "#
 9344    ));
 9345
 9346    cx.update_editor(|editor, window, cx| {
 9347        editor.add_selection_below(&Default::default(), window, cx);
 9348    });
 9349
 9350    cx.assert_editor_state(indoc!(
 9351        r#"abc
 9352           defˇghi
 9353           ˇ
 9354           jkˇ
 9355           nlmˇo
 9356           "#
 9357    ));
 9358
 9359    cx.update_editor(|editor, window, cx| {
 9360        editor.add_selection_below(&Default::default(), window, cx);
 9361    });
 9362
 9363    cx.assert_editor_state(indoc!(
 9364        r#"abc
 9365           defˇghi
 9366           ˇ
 9367           jkˇ
 9368           nlmˇo
 9369           ˇ"#
 9370    ));
 9371
 9372    // change selections
 9373    cx.set_state(indoc!(
 9374        r#"abc
 9375           def«ˇg»hi
 9376
 9377           jk
 9378           nlmo
 9379           "#
 9380    ));
 9381
 9382    cx.update_editor(|editor, window, cx| {
 9383        editor.add_selection_below(&Default::default(), window, cx);
 9384    });
 9385
 9386    cx.assert_editor_state(indoc!(
 9387        r#"abc
 9388           def«ˇg»hi
 9389
 9390           jk
 9391           nlm«ˇo»
 9392           "#
 9393    ));
 9394
 9395    cx.update_editor(|editor, window, cx| {
 9396        editor.add_selection_below(&Default::default(), window, cx);
 9397    });
 9398
 9399    cx.assert_editor_state(indoc!(
 9400        r#"abc
 9401           def«ˇg»hi
 9402
 9403           jk
 9404           nlm«ˇo»
 9405           "#
 9406    ));
 9407
 9408    cx.update_editor(|editor, window, cx| {
 9409        editor.add_selection_above(&Default::default(), window, cx);
 9410    });
 9411
 9412    cx.assert_editor_state(indoc!(
 9413        r#"abc
 9414           def«ˇg»hi
 9415
 9416           jk
 9417           nlmo
 9418           "#
 9419    ));
 9420
 9421    cx.update_editor(|editor, window, cx| {
 9422        editor.add_selection_above(&Default::default(), window, cx);
 9423    });
 9424
 9425    cx.assert_editor_state(indoc!(
 9426        r#"abc
 9427           def«ˇg»hi
 9428
 9429           jk
 9430           nlmo
 9431           "#
 9432    ));
 9433
 9434    // Change selections again
 9435    cx.set_state(indoc!(
 9436        r#"a«bc
 9437           defgˇ»hi
 9438
 9439           jk
 9440           nlmo
 9441           "#
 9442    ));
 9443
 9444    cx.update_editor(|editor, window, cx| {
 9445        editor.add_selection_below(&Default::default(), window, cx);
 9446    });
 9447
 9448    cx.assert_editor_state(indoc!(
 9449        r#"a«bcˇ»
 9450           d«efgˇ»hi
 9451
 9452           j«kˇ»
 9453           nlmo
 9454           "#
 9455    ));
 9456
 9457    cx.update_editor(|editor, window, cx| {
 9458        editor.add_selection_below(&Default::default(), window, cx);
 9459    });
 9460    cx.assert_editor_state(indoc!(
 9461        r#"a«bcˇ»
 9462           d«efgˇ»hi
 9463
 9464           j«kˇ»
 9465           n«lmoˇ»
 9466           "#
 9467    ));
 9468    cx.update_editor(|editor, window, cx| {
 9469        editor.add_selection_above(&Default::default(), window, cx);
 9470    });
 9471
 9472    cx.assert_editor_state(indoc!(
 9473        r#"a«bcˇ»
 9474           d«efgˇ»hi
 9475
 9476           j«kˇ»
 9477           nlmo
 9478           "#
 9479    ));
 9480
 9481    // Change selections again
 9482    cx.set_state(indoc!(
 9483        r#"abc
 9484           d«ˇefghi
 9485
 9486           jk
 9487           nlm»o
 9488           "#
 9489    ));
 9490
 9491    cx.update_editor(|editor, window, cx| {
 9492        editor.add_selection_above(&Default::default(), window, cx);
 9493    });
 9494
 9495    cx.assert_editor_state(indoc!(
 9496        r#"a«ˇbc»
 9497           d«ˇef»ghi
 9498
 9499           j«ˇk»
 9500           n«ˇlm»o
 9501           "#
 9502    ));
 9503
 9504    cx.update_editor(|editor, window, cx| {
 9505        editor.add_selection_below(&Default::default(), window, cx);
 9506    });
 9507
 9508    cx.assert_editor_state(indoc!(
 9509        r#"abc
 9510           d«ˇef»ghi
 9511
 9512           j«ˇk»
 9513           n«ˇlm»o
 9514           "#
 9515    ));
 9516
 9517    // Assert that the oldest selection's goal column is used when adding more
 9518    // selections, not the most recently added selection's actual column.
 9519    cx.set_state(indoc! {"
 9520        foo bar bazˇ
 9521        foo
 9522        foo bar
 9523    "});
 9524
 9525    cx.update_editor(|editor, window, cx| {
 9526        editor.add_selection_below(
 9527            &AddSelectionBelow {
 9528                skip_soft_wrap: true,
 9529            },
 9530            window,
 9531            cx,
 9532        );
 9533    });
 9534
 9535    cx.assert_editor_state(indoc! {"
 9536        foo bar bazˇ
 9537        fooˇ
 9538        foo bar
 9539    "});
 9540
 9541    cx.update_editor(|editor, window, cx| {
 9542        editor.add_selection_below(
 9543            &AddSelectionBelow {
 9544                skip_soft_wrap: true,
 9545            },
 9546            window,
 9547            cx,
 9548        );
 9549    });
 9550
 9551    cx.assert_editor_state(indoc! {"
 9552        foo bar bazˇ
 9553        fooˇ
 9554        foo barˇ
 9555    "});
 9556
 9557    cx.set_state(indoc! {"
 9558        foo bar baz
 9559        foo
 9560        foo barˇ
 9561    "});
 9562
 9563    cx.update_editor(|editor, window, cx| {
 9564        editor.add_selection_above(
 9565            &AddSelectionAbove {
 9566                skip_soft_wrap: true,
 9567            },
 9568            window,
 9569            cx,
 9570        );
 9571    });
 9572
 9573    cx.assert_editor_state(indoc! {"
 9574        foo bar baz
 9575        fooˇ
 9576        foo barˇ
 9577    "});
 9578
 9579    cx.update_editor(|editor, window, cx| {
 9580        editor.add_selection_above(
 9581            &AddSelectionAbove {
 9582                skip_soft_wrap: true,
 9583            },
 9584            window,
 9585            cx,
 9586        );
 9587    });
 9588
 9589    cx.assert_editor_state(indoc! {"
 9590        foo barˇ baz
 9591        fooˇ
 9592        foo barˇ
 9593    "});
 9594}
 9595
 9596#[gpui::test]
 9597async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 9598    init_test(cx, |_| {});
 9599    let mut cx = EditorTestContext::new(cx).await;
 9600
 9601    cx.set_state(indoc!(
 9602        r#"line onˇe
 9603           liˇne two
 9604           line three
 9605           line four"#
 9606    ));
 9607
 9608    cx.update_editor(|editor, window, cx| {
 9609        editor.add_selection_below(&Default::default(), window, cx);
 9610    });
 9611
 9612    // test multiple cursors expand in the same direction
 9613    cx.assert_editor_state(indoc!(
 9614        r#"line onˇe
 9615           liˇne twˇo
 9616           liˇne three
 9617           line four"#
 9618    ));
 9619
 9620    cx.update_editor(|editor, window, cx| {
 9621        editor.add_selection_below(&Default::default(), window, cx);
 9622    });
 9623
 9624    cx.update_editor(|editor, window, cx| {
 9625        editor.add_selection_below(&Default::default(), window, cx);
 9626    });
 9627
 9628    // test multiple cursors expand below overflow
 9629    cx.assert_editor_state(indoc!(
 9630        r#"line onˇe
 9631           liˇne twˇo
 9632           liˇne thˇree
 9633           liˇne foˇur"#
 9634    ));
 9635
 9636    cx.update_editor(|editor, window, cx| {
 9637        editor.add_selection_above(&Default::default(), window, cx);
 9638    });
 9639
 9640    // test multiple cursors retrieves back correctly
 9641    cx.assert_editor_state(indoc!(
 9642        r#"line onˇe
 9643           liˇne twˇo
 9644           liˇne thˇree
 9645           line four"#
 9646    ));
 9647
 9648    cx.update_editor(|editor, window, cx| {
 9649        editor.add_selection_above(&Default::default(), window, cx);
 9650    });
 9651
 9652    cx.update_editor(|editor, window, cx| {
 9653        editor.add_selection_above(&Default::default(), window, cx);
 9654    });
 9655
 9656    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 9657    cx.assert_editor_state(indoc!(
 9658        r#"liˇne onˇe
 9659           liˇne two
 9660           line three
 9661           line four"#
 9662    ));
 9663
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.undo_selection(&Default::default(), window, cx);
 9666    });
 9667
 9668    // test undo
 9669    cx.assert_editor_state(indoc!(
 9670        r#"line onˇe
 9671           liˇne twˇo
 9672           line three
 9673           line four"#
 9674    ));
 9675
 9676    cx.update_editor(|editor, window, cx| {
 9677        editor.redo_selection(&Default::default(), window, cx);
 9678    });
 9679
 9680    // test redo
 9681    cx.assert_editor_state(indoc!(
 9682        r#"liˇne onˇe
 9683           liˇne two
 9684           line three
 9685           line four"#
 9686    ));
 9687
 9688    cx.set_state(indoc!(
 9689        r#"abcd
 9690           ef«ghˇ»
 9691           ijkl
 9692           «mˇ»nop"#
 9693    ));
 9694
 9695    cx.update_editor(|editor, window, cx| {
 9696        editor.add_selection_above(&Default::default(), window, cx);
 9697    });
 9698
 9699    // test multiple selections expand in the same direction
 9700    cx.assert_editor_state(indoc!(
 9701        r#"ab«cdˇ»
 9702           ef«ghˇ»
 9703           «iˇ»jkl
 9704           «mˇ»nop"#
 9705    ));
 9706
 9707    cx.update_editor(|editor, window, cx| {
 9708        editor.add_selection_above(&Default::default(), window, cx);
 9709    });
 9710
 9711    // test multiple selection upward overflow
 9712    cx.assert_editor_state(indoc!(
 9713        r#"ab«cdˇ»
 9714           «eˇ»f«ghˇ»
 9715           «iˇ»jkl
 9716           «mˇ»nop"#
 9717    ));
 9718
 9719    cx.update_editor(|editor, window, cx| {
 9720        editor.add_selection_below(&Default::default(), window, cx);
 9721    });
 9722
 9723    // test multiple selection retrieves back correctly
 9724    cx.assert_editor_state(indoc!(
 9725        r#"abcd
 9726           ef«ghˇ»
 9727           «iˇ»jkl
 9728           «mˇ»nop"#
 9729    ));
 9730
 9731    cx.update_editor(|editor, window, cx| {
 9732        editor.add_selection_below(&Default::default(), window, cx);
 9733    });
 9734
 9735    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 9736    cx.assert_editor_state(indoc!(
 9737        r#"abcd
 9738           ef«ghˇ»
 9739           ij«klˇ»
 9740           «mˇ»nop"#
 9741    ));
 9742
 9743    cx.update_editor(|editor, window, cx| {
 9744        editor.undo_selection(&Default::default(), window, cx);
 9745    });
 9746
 9747    // test undo
 9748    cx.assert_editor_state(indoc!(
 9749        r#"abcd
 9750           ef«ghˇ»
 9751           «iˇ»jkl
 9752           «mˇ»nop"#
 9753    ));
 9754
 9755    cx.update_editor(|editor, window, cx| {
 9756        editor.redo_selection(&Default::default(), window, cx);
 9757    });
 9758
 9759    // test redo
 9760    cx.assert_editor_state(indoc!(
 9761        r#"abcd
 9762           ef«ghˇ»
 9763           ij«klˇ»
 9764           «mˇ»nop"#
 9765    ));
 9766}
 9767
 9768#[gpui::test]
 9769async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 9770    init_test(cx, |_| {});
 9771    let mut cx = EditorTestContext::new(cx).await;
 9772
 9773    cx.set_state(indoc!(
 9774        r#"line onˇe
 9775           liˇne two
 9776           line three
 9777           line four"#
 9778    ));
 9779
 9780    cx.update_editor(|editor, window, cx| {
 9781        editor.add_selection_below(&Default::default(), window, cx);
 9782        editor.add_selection_below(&Default::default(), window, cx);
 9783        editor.add_selection_below(&Default::default(), window, cx);
 9784    });
 9785
 9786    // initial state with two multi cursor groups
 9787    cx.assert_editor_state(indoc!(
 9788        r#"line onˇe
 9789           liˇne twˇo
 9790           liˇne thˇree
 9791           liˇne foˇur"#
 9792    ));
 9793
 9794    // add single cursor in middle - simulate opt click
 9795    cx.update_editor(|editor, window, cx| {
 9796        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 9797        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9798        editor.end_selection(window, cx);
 9799    });
 9800
 9801    cx.assert_editor_state(indoc!(
 9802        r#"line onˇe
 9803           liˇne twˇo
 9804           liˇneˇ thˇree
 9805           liˇne foˇur"#
 9806    ));
 9807
 9808    cx.update_editor(|editor, window, cx| {
 9809        editor.add_selection_above(&Default::default(), window, cx);
 9810    });
 9811
 9812    // test new added selection expands above and existing selection shrinks
 9813    cx.assert_editor_state(indoc!(
 9814        r#"line onˇe
 9815           liˇneˇ twˇo
 9816           liˇneˇ thˇree
 9817           line four"#
 9818    ));
 9819
 9820    cx.update_editor(|editor, window, cx| {
 9821        editor.add_selection_above(&Default::default(), window, cx);
 9822    });
 9823
 9824    // test new added selection expands above and existing selection shrinks
 9825    cx.assert_editor_state(indoc!(
 9826        r#"lineˇ onˇe
 9827           liˇneˇ twˇo
 9828           lineˇ three
 9829           line four"#
 9830    ));
 9831
 9832    // intial state with two selection groups
 9833    cx.set_state(indoc!(
 9834        r#"abcd
 9835           ef«ghˇ»
 9836           ijkl
 9837           «mˇ»nop"#
 9838    ));
 9839
 9840    cx.update_editor(|editor, window, cx| {
 9841        editor.add_selection_above(&Default::default(), window, cx);
 9842        editor.add_selection_above(&Default::default(), window, cx);
 9843    });
 9844
 9845    cx.assert_editor_state(indoc!(
 9846        r#"ab«cdˇ»
 9847           «eˇ»f«ghˇ»
 9848           «iˇ»jkl
 9849           «mˇ»nop"#
 9850    ));
 9851
 9852    // add single selection in middle - simulate opt drag
 9853    cx.update_editor(|editor, window, cx| {
 9854        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 9855        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9856        editor.update_selection(
 9857            DisplayPoint::new(DisplayRow(2), 4),
 9858            0,
 9859            gpui::Point::<f32>::default(),
 9860            window,
 9861            cx,
 9862        );
 9863        editor.end_selection(window, cx);
 9864    });
 9865
 9866    cx.assert_editor_state(indoc!(
 9867        r#"ab«cdˇ»
 9868           «eˇ»f«ghˇ»
 9869           «iˇ»jk«lˇ»
 9870           «mˇ»nop"#
 9871    ));
 9872
 9873    cx.update_editor(|editor, window, cx| {
 9874        editor.add_selection_below(&Default::default(), window, cx);
 9875    });
 9876
 9877    // test new added selection expands below, others shrinks from above
 9878    cx.assert_editor_state(indoc!(
 9879        r#"abcd
 9880           ef«ghˇ»
 9881           «iˇ»jk«lˇ»
 9882           «mˇ»no«pˇ»"#
 9883    ));
 9884}
 9885
 9886#[gpui::test]
 9887async fn test_add_selection_above_below_multibyte(cx: &mut TestAppContext) {
 9888    init_test(cx, |_| {});
 9889    let mut cx = EditorTestContext::new(cx).await;
 9890
 9891    // Cursor after "Häl" (byte column 4, char column 3) should align to
 9892    // char column 3 on the ASCII line below, not byte column 4.
 9893    cx.set_state(indoc!(
 9894        r#"Hälˇlö
 9895           Hallo"#
 9896    ));
 9897
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.add_selection_below(&Default::default(), window, cx);
 9900    });
 9901
 9902    cx.assert_editor_state(indoc!(
 9903        r#"Hälˇlö
 9904           Halˇlo"#
 9905    ));
 9906}
 9907
 9908#[gpui::test]
 9909async fn test_select_next(cx: &mut TestAppContext) {
 9910    init_test(cx, |_| {});
 9911    let mut cx = EditorTestContext::new(cx).await;
 9912
 9913    // Enable case sensitive search.
 9914    update_test_editor_settings(&mut cx, &|settings| {
 9915        let mut search_settings = SearchSettingsContent::default();
 9916        search_settings.case_sensitive = Some(true);
 9917        settings.search = Some(search_settings);
 9918    });
 9919
 9920    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9921
 9922    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9923        .unwrap();
 9924    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9925
 9926    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9927        .unwrap();
 9928    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9929
 9930    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9931    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9932
 9933    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9934    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9935
 9936    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9937        .unwrap();
 9938    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9939
 9940    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9941        .unwrap();
 9942    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9943
 9944    // Test selection direction should be preserved
 9945    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9946
 9947    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9948        .unwrap();
 9949    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 9950
 9951    // Test case sensitivity
 9952    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 9953    cx.update_editor(|e, window, cx| {
 9954        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9955    });
 9956    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9957
 9958    // Disable case sensitive search.
 9959    update_test_editor_settings(&mut cx, &|settings| {
 9960        let mut search_settings = SearchSettingsContent::default();
 9961        search_settings.case_sensitive = Some(false);
 9962        settings.search = Some(search_settings);
 9963    });
 9964
 9965    cx.set_state("«ˇfoo»\nFOO\nFoo");
 9966    cx.update_editor(|e, window, cx| {
 9967        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9968        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9969    });
 9970    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9971}
 9972
 9973#[gpui::test]
 9974async fn test_select_all_matches(cx: &mut TestAppContext) {
 9975    init_test(cx, |_| {});
 9976    let mut cx = EditorTestContext::new(cx).await;
 9977
 9978    // Enable case sensitive search.
 9979    update_test_editor_settings(&mut cx, &|settings| {
 9980        let mut search_settings = SearchSettingsContent::default();
 9981        search_settings.case_sensitive = Some(true);
 9982        settings.search = Some(search_settings);
 9983    });
 9984
 9985    // Test caret-only selections
 9986    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9987    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9988        .unwrap();
 9989    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9990
 9991    // Test left-to-right selections
 9992    cx.set_state("abc\n«abcˇ»\nabc");
 9993    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9994        .unwrap();
 9995    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 9996
 9997    // Test right-to-left selections
 9998    cx.set_state("abc\n«ˇabc»\nabc");
 9999    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
10000        .unwrap();
10001    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
10002
10003    // Test selecting whitespace with caret selection
10004    cx.set_state("abc\nˇ   abc\nabc");
10005    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
10006        .unwrap();
10007    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
10008
10009    // Test selecting whitespace with left-to-right selection
10010    cx.set_state("abc\n«ˇ  »abc\nabc");
10011    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
10012        .unwrap();
10013    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
10014
10015    // Test no matches with right-to-left selection
10016    cx.set_state("abc\n«  ˇ»abc\nabc");
10017    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
10018        .unwrap();
10019    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
10020
10021    // Test with a single word and clip_at_line_ends=true (#29823)
10022    cx.set_state("aˇbc");
10023    cx.update_editor(|e, window, cx| {
10024        e.set_clip_at_line_ends(true, cx);
10025        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
10026        e.set_clip_at_line_ends(false, cx);
10027    });
10028    cx.assert_editor_state("«abcˇ»");
10029
10030    // Test case sensitivity
10031    cx.set_state("fˇoo\nFOO\nFoo");
10032    cx.update_editor(|e, window, cx| {
10033        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
10034    });
10035    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
10036
10037    // Disable case sensitive search.
10038    update_test_editor_settings(&mut cx, &|settings| {
10039        let mut search_settings = SearchSettingsContent::default();
10040        search_settings.case_sensitive = Some(false);
10041        settings.search = Some(search_settings);
10042    });
10043
10044    cx.set_state("fˇoo\nFOO\nFoo");
10045    cx.update_editor(|e, window, cx| {
10046        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
10047    });
10048    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
10049}
10050
10051#[gpui::test]
10052async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
10053    init_test(cx, |_| {});
10054
10055    let mut cx = EditorTestContext::new(cx).await;
10056    let large_body_1 = "\nd".repeat(200);
10057    let large_body_2 = "\ne".repeat(200);
10058
10059    cx.set_state(&format!(
10060        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
10061    ));
10062    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
10063        let scroll_position = editor.scroll_position(cx);
10064        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
10065        scroll_position
10066    });
10067
10068    cx.update_editor(|editor, window, cx| editor.select_all_matches(&SelectAllMatches, window, cx))
10069        .unwrap();
10070    cx.assert_editor_state(&format!(
10071        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
10072    ));
10073    cx.update_editor(|editor, _, cx| {
10074        assert_eq!(
10075            editor.scroll_position(cx),
10076            initial_scroll_position,
10077            "Scroll position should not change after selecting all matches"
10078        )
10079    });
10080
10081    // Simulate typing while the selections are active, as that is where the
10082    // editor would attempt to actually scroll to the newest selection, which
10083    // should have been set as the original selection to avoid scrolling to the
10084    // last match.
10085    cx.simulate_keystroke("x");
10086    cx.update_editor(|editor, _, cx| {
10087        assert_eq!(
10088            editor.scroll_position(cx),
10089            initial_scroll_position,
10090            "Scroll position should not change after editing all matches"
10091        )
10092    });
10093
10094    cx.set_state(&format!(
10095        "abc\nabc{large_body_1} «aˇ»bc{large_body_2}\nefabc\nabc"
10096    ));
10097    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
10098        let scroll_position = editor.scroll_position(cx);
10099        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
10100        scroll_position
10101    });
10102
10103    cx.update_editor(|editor, window, cx| editor.select_all_matches(&SelectAllMatches, window, cx))
10104        .unwrap();
10105    cx.assert_editor_state(&format!(
10106        "«aˇ»bc\n«aˇ»bc{large_body_1} «aˇ»bc{large_body_2}\nef«aˇ»bc\n«aˇ»bc"
10107    ));
10108    cx.update_editor(|editor, _, cx| {
10109        assert_eq!(
10110            editor.scroll_position(cx),
10111            initial_scroll_position,
10112            "Scroll position should not change after selecting all matches"
10113        )
10114    });
10115
10116    cx.simulate_keystroke("x");
10117    cx.update_editor(|editor, _, cx| {
10118        assert_eq!(
10119            editor.scroll_position(cx),
10120            initial_scroll_position,
10121            "Scroll position should not change after editing all matches"
10122        )
10123    });
10124}
10125
10126#[gpui::test]
10127async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
10128    init_test(cx, |_| {});
10129
10130    let mut cx = EditorLspTestContext::new_rust(
10131        lsp::ServerCapabilities {
10132            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10133            ..Default::default()
10134        },
10135        cx,
10136    )
10137    .await;
10138
10139    cx.set_state(indoc! {"
10140        line 1
10141        line 2
10142        linˇe 3
10143        line 4
10144        line 5
10145    "});
10146
10147    // Make an edit
10148    cx.update_editor(|editor, window, cx| {
10149        editor.handle_input("X", window, cx);
10150    });
10151
10152    // Move cursor to a different position
10153    cx.update_editor(|editor, window, cx| {
10154        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10155            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
10156        });
10157    });
10158
10159    cx.assert_editor_state(indoc! {"
10160        line 1
10161        line 2
10162        linXe 3
10163        line 4
10164        liˇne 5
10165    "});
10166
10167    cx.lsp
10168        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
10169            Ok(Some(vec![lsp::TextEdit::new(
10170                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10171                "PREFIX ".to_string(),
10172            )]))
10173        });
10174
10175    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
10176        .unwrap()
10177        .await
10178        .unwrap();
10179
10180    cx.assert_editor_state(indoc! {"
10181        PREFIX line 1
10182        line 2
10183        linXe 3
10184        line 4
10185        liˇne 5
10186    "});
10187
10188    // Undo formatting
10189    cx.update_editor(|editor, window, cx| {
10190        editor.undo(&Default::default(), window, cx);
10191    });
10192
10193    // Verify cursor moved back to position after edit
10194    cx.assert_editor_state(indoc! {"
10195        line 1
10196        line 2
10197        linXˇe 3
10198        line 4
10199        line 5
10200    "});
10201}
10202
10203#[gpui::test]
10204async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
10205    init_test(cx, |_| {});
10206
10207    let mut cx = EditorTestContext::new(cx).await;
10208
10209    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
10210    cx.update_editor(|editor, window, cx| {
10211        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
10212    });
10213
10214    cx.set_state(indoc! {"
10215        line 1
10216        line 2
10217        linˇe 3
10218        line 4
10219        line 5
10220        line 6
10221        line 7
10222        line 8
10223        line 9
10224        line 10
10225    "});
10226
10227    let snapshot = cx.buffer_snapshot();
10228    let edit_position = snapshot.anchor_after(Point::new(2, 4));
10229
10230    cx.update(|_, cx| {
10231        provider.update(cx, |provider, _| {
10232            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
10233                id: None,
10234                edits: vec![(edit_position..edit_position, "X".into())],
10235                cursor_position: None,
10236                edit_preview: None,
10237            }))
10238        })
10239    });
10240
10241    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
10242    cx.update_editor(|editor, window, cx| {
10243        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
10244    });
10245
10246    cx.assert_editor_state(indoc! {"
10247        line 1
10248        line 2
10249        lineXˇ 3
10250        line 4
10251        line 5
10252        line 6
10253        line 7
10254        line 8
10255        line 9
10256        line 10
10257    "});
10258
10259    cx.update_editor(|editor, window, cx| {
10260        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10261            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
10262        });
10263    });
10264
10265    cx.assert_editor_state(indoc! {"
10266        line 1
10267        line 2
10268        lineX 3
10269        line 4
10270        line 5
10271        line 6
10272        line 7
10273        line 8
10274        line 9
10275        liˇne 10
10276    "});
10277
10278    cx.update_editor(|editor, window, cx| {
10279        editor.undo(&Default::default(), window, cx);
10280    });
10281
10282    cx.assert_editor_state(indoc! {"
10283        line 1
10284        line 2
10285        lineˇ 3
10286        line 4
10287        line 5
10288        line 6
10289        line 7
10290        line 8
10291        line 9
10292        line 10
10293    "});
10294}
10295
10296#[gpui::test]
10297async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
10298    init_test(cx, |_| {});
10299
10300    let mut cx = EditorTestContext::new(cx).await;
10301    cx.set_state(
10302        r#"let foo = 2;
10303lˇet foo = 2;
10304let fooˇ = 2;
10305let foo = 2;
10306let foo = ˇ2;"#,
10307    );
10308
10309    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
10310        .unwrap();
10311    cx.assert_editor_state(
10312        r#"let foo = 2;
10313«letˇ» foo = 2;
10314let «fooˇ» = 2;
10315let foo = 2;
10316let foo = «2ˇ»;"#,
10317    );
10318
10319    // noop for multiple selections with different contents
10320    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
10321        .unwrap();
10322    cx.assert_editor_state(
10323        r#"let foo = 2;
10324«letˇ» foo = 2;
10325let «fooˇ» = 2;
10326let foo = 2;
10327let foo = «2ˇ»;"#,
10328    );
10329
10330    // Test last selection direction should be preserved
10331    cx.set_state(
10332        r#"let foo = 2;
10333let foo = 2;
10334let «fooˇ» = 2;
10335let «ˇfoo» = 2;
10336let foo = 2;"#,
10337    );
10338
10339    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
10340        .unwrap();
10341    cx.assert_editor_state(
10342        r#"let foo = 2;
10343let foo = 2;
10344let «fooˇ» = 2;
10345let «ˇfoo» = 2;
10346let «ˇfoo» = 2;"#,
10347    );
10348}
10349
10350#[gpui::test]
10351async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
10352    init_test(cx, |_| {});
10353
10354    let mut cx =
10355        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc»\nddd", "aaa\n«bbb\nccc»\nddd"]);
10356
10357    cx.assert_editor_state(indoc! {"
10358        ˇbbb
10359        ccc
10360        bbb
10361        ccc"});
10362    cx.dispatch_action(SelectPrevious::default());
10363    cx.assert_editor_state(indoc! {"
10364                «bbbˇ»
10365                ccc
10366                bbb
10367                ccc"});
10368    cx.dispatch_action(SelectPrevious::default());
10369    cx.assert_editor_state(indoc! {"
10370                «bbbˇ»
10371                ccc
10372                «bbbˇ»
10373                ccc"});
10374}
10375
10376#[gpui::test]
10377async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
10378    init_test(cx, |_| {});
10379
10380    let mut cx = EditorTestContext::new(cx).await;
10381    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
10382
10383    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10384        .unwrap();
10385    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
10386
10387    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10388        .unwrap();
10389    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
10390
10391    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
10392    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
10393
10394    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
10395    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
10396
10397    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10398        .unwrap();
10399    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
10400
10401    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10402        .unwrap();
10403    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
10404}
10405
10406#[gpui::test]
10407async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
10408    init_test(cx, |_| {});
10409
10410    let mut cx = EditorTestContext::new(cx).await;
10411    cx.set_state("");
10412
10413    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10414        .unwrap();
10415    cx.assert_editor_state("«aˇ»");
10416    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10417        .unwrap();
10418    cx.assert_editor_state("«aˇ»");
10419}
10420
10421#[gpui::test]
10422async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
10423    init_test(cx, |_| {});
10424
10425    let mut cx = EditorTestContext::new(cx).await;
10426    cx.set_state(
10427        r#"let foo = 2;
10428lˇet foo = 2;
10429let fooˇ = 2;
10430let foo = 2;
10431let foo = ˇ2;"#,
10432    );
10433
10434    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10435        .unwrap();
10436    cx.assert_editor_state(
10437        r#"let foo = 2;
10438«letˇ» foo = 2;
10439let «fooˇ» = 2;
10440let foo = 2;
10441let foo = «2ˇ»;"#,
10442    );
10443
10444    // noop for multiple selections with different contents
10445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10446        .unwrap();
10447    cx.assert_editor_state(
10448        r#"let foo = 2;
10449«letˇ» foo = 2;
10450let «fooˇ» = 2;
10451let foo = 2;
10452let foo = «2ˇ»;"#,
10453    );
10454}
10455
10456#[gpui::test]
10457async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
10458    init_test(cx, |_| {});
10459    let mut cx = EditorTestContext::new(cx).await;
10460
10461    // Enable case sensitive search.
10462    update_test_editor_settings(&mut cx, &|settings| {
10463        let mut search_settings = SearchSettingsContent::default();
10464        search_settings.case_sensitive = Some(true);
10465        settings.search = Some(search_settings);
10466    });
10467
10468    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
10469
10470    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10471        .unwrap();
10472    // selection direction is preserved
10473    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
10474
10475    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10476        .unwrap();
10477    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
10478
10479    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
10480    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
10481
10482    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
10483    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
10484
10485    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10486        .unwrap();
10487    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
10488
10489    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
10490        .unwrap();
10491    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
10492
10493    // Test case sensitivity
10494    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
10495    cx.update_editor(|e, window, cx| {
10496        e.select_previous(&SelectPrevious::default(), window, cx)
10497            .unwrap();
10498        e.select_previous(&SelectPrevious::default(), window, cx)
10499            .unwrap();
10500    });
10501    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
10502
10503    // Disable case sensitive search.
10504    update_test_editor_settings(&mut cx, &|settings| {
10505        let mut search_settings = SearchSettingsContent::default();
10506        search_settings.case_sensitive = Some(false);
10507        settings.search = Some(search_settings);
10508    });
10509
10510    cx.set_state("foo\nFOO\n«ˇFoo»");
10511    cx.update_editor(|e, window, cx| {
10512        e.select_previous(&SelectPrevious::default(), window, cx)
10513            .unwrap();
10514        e.select_previous(&SelectPrevious::default(), window, cx)
10515            .unwrap();
10516    });
10517    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
10518}
10519
10520#[gpui::test]
10521async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
10522    init_test(cx, |_| {});
10523
10524    let language = Arc::new(Language::new(
10525        LanguageConfig::default(),
10526        Some(tree_sitter_rust::LANGUAGE.into()),
10527    ));
10528
10529    let text = r#"
10530        use mod1::mod2::{mod3, mod4};
10531
10532        fn fn_1(param1: bool, param2: &str) {
10533            let var1 = "text";
10534        }
10535    "#
10536    .unindent();
10537
10538    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10539    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10540    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10541
10542    editor
10543        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10544        .await;
10545
10546    editor.update_in(cx, |editor, window, cx| {
10547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10548            s.select_display_ranges([
10549                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
10550                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
10551                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
10552            ]);
10553        });
10554        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10555    });
10556    editor.update(cx, |editor, cx| {
10557        assert_text_with_selections(
10558            editor,
10559            indoc! {r#"
10560                use mod1::mod2::{mod3, «mod4ˇ»};
10561
10562                fn fn_1«ˇ(param1: bool, param2: &str)» {
10563                    let var1 = "«textˇ»";
10564                }
10565            "#},
10566            cx,
10567        );
10568    });
10569
10570    editor.update_in(cx, |editor, window, cx| {
10571        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10572    });
10573    editor.update(cx, |editor, cx| {
10574        assert_text_with_selections(
10575            editor,
10576            indoc! {r#"
10577                use mod1::mod2::«{mod3, mod4}ˇ»;
10578
10579                «ˇfn fn_1(param1: bool, param2: &str) {
10580                    let var1 = "text";
1058110582            "#},
10583            cx,
10584        );
10585    });
10586
10587    editor.update_in(cx, |editor, window, cx| {
10588        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10589    });
10590    assert_eq!(
10591        editor.update(cx, |editor, cx| editor
10592            .selections
10593            .display_ranges(&editor.display_snapshot(cx))),
10594        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
10595    );
10596
10597    // Trying to expand the selected syntax node one more time has no effect.
10598    editor.update_in(cx, |editor, window, cx| {
10599        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10600    });
10601    assert_eq!(
10602        editor.update(cx, |editor, cx| editor
10603            .selections
10604            .display_ranges(&editor.display_snapshot(cx))),
10605        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
10606    );
10607
10608    editor.update_in(cx, |editor, window, cx| {
10609        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10610    });
10611    editor.update(cx, |editor, cx| {
10612        assert_text_with_selections(
10613            editor,
10614            indoc! {r#"
10615                use mod1::mod2::«{mod3, mod4}ˇ»;
10616
10617                «ˇfn fn_1(param1: bool, param2: &str) {
10618                    let var1 = "text";
1061910620            "#},
10621            cx,
10622        );
10623    });
10624
10625    editor.update_in(cx, |editor, window, cx| {
10626        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10627    });
10628    editor.update(cx, |editor, cx| {
10629        assert_text_with_selections(
10630            editor,
10631            indoc! {r#"
10632                use mod1::mod2::{mod3, «mod4ˇ»};
10633
10634                fn fn_1«ˇ(param1: bool, param2: &str)» {
10635                    let var1 = "«textˇ»";
10636                }
10637            "#},
10638            cx,
10639        );
10640    });
10641
10642    editor.update_in(cx, |editor, window, cx| {
10643        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10644    });
10645    editor.update(cx, |editor, cx| {
10646        assert_text_with_selections(
10647            editor,
10648            indoc! {r#"
10649                use mod1::mod2::{mod3, moˇd4};
10650
10651                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
10652                    let var1 = "teˇxt";
10653                }
10654            "#},
10655            cx,
10656        );
10657    });
10658
10659    // Trying to shrink the selected syntax node one more time has no effect.
10660    editor.update_in(cx, |editor, window, cx| {
10661        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10662    });
10663    editor.update_in(cx, |editor, _, cx| {
10664        assert_text_with_selections(
10665            editor,
10666            indoc! {r#"
10667                use mod1::mod2::{mod3, moˇd4};
10668
10669                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
10670                    let var1 = "teˇxt";
10671                }
10672            "#},
10673            cx,
10674        );
10675    });
10676
10677    // Ensure that we keep expanding the selection if the larger selection starts or ends within
10678    // a fold.
10679    editor.update_in(cx, |editor, window, cx| {
10680        editor.fold_creases(
10681            vec![
10682                Crease::simple(
10683                    Point::new(0, 21)..Point::new(0, 24),
10684                    FoldPlaceholder::test(),
10685                ),
10686                Crease::simple(
10687                    Point::new(3, 20)..Point::new(3, 22),
10688                    FoldPlaceholder::test(),
10689                ),
10690            ],
10691            true,
10692            window,
10693            cx,
10694        );
10695        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10696    });
10697    editor.update(cx, |editor, cx| {
10698        assert_text_with_selections(
10699            editor,
10700            indoc! {r#"
10701                use mod1::mod2::«{mod3, mod4}ˇ»;
10702
10703                fn fn_1«ˇ(param1: bool, param2: &str)» {
10704                    let var1 = "«textˇ»";
10705                }
10706            "#},
10707            cx,
10708        );
10709    });
10710
10711    // Ensure multiple cursors have consistent direction after expanding
10712    editor.update_in(cx, |editor, window, cx| {
10713        editor.unfold_all(&UnfoldAll, window, cx);
10714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10715            s.select_display_ranges([
10716                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
10717                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
10718            ]);
10719        });
10720        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10721    });
10722    editor.update(cx, |editor, cx| {
10723        assert_text_with_selections(
10724            editor,
10725            indoc! {r#"
10726                use mod1::mod2::{mod3, «mod4ˇ»};
10727
10728                fn fn_1(param1: bool, param2: &str) {
10729                    let var1 = "«textˇ»";
10730                }
10731            "#},
10732            cx,
10733        );
10734    });
10735}
10736
10737#[gpui::test]
10738async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
10739    init_test(cx, |_| {});
10740
10741    let language = Arc::new(Language::new(
10742        LanguageConfig::default(),
10743        Some(tree_sitter_rust::LANGUAGE.into()),
10744    ));
10745
10746    let text = "let a = 2;";
10747
10748    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10749    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10750    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10751
10752    editor
10753        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10754        .await;
10755
10756    // Test case 1: Cursor at end of word
10757    editor.update_in(cx, |editor, window, cx| {
10758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10759            s.select_display_ranges([
10760                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
10761            ]);
10762        });
10763    });
10764    editor.update(cx, |editor, cx| {
10765        assert_text_with_selections(editor, "let aˇ = 2;", cx);
10766    });
10767    editor.update_in(cx, |editor, window, cx| {
10768        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10769    });
10770    editor.update(cx, |editor, cx| {
10771        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
10772    });
10773    editor.update_in(cx, |editor, window, cx| {
10774        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10775    });
10776    editor.update(cx, |editor, cx| {
10777        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10778    });
10779
10780    // Test case 2: Cursor at end of statement
10781    editor.update_in(cx, |editor, window, cx| {
10782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10783            s.select_display_ranges([
10784                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
10785            ]);
10786        });
10787    });
10788    editor.update(cx, |editor, cx| {
10789        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
10790    });
10791    editor.update_in(cx, |editor, window, cx| {
10792        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10793    });
10794    editor.update(cx, |editor, cx| {
10795        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10796    });
10797}
10798
10799#[gpui::test]
10800async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
10801    init_test(cx, |_| {});
10802
10803    let language = Arc::new(Language::new(
10804        LanguageConfig {
10805            name: "JavaScript".into(),
10806            ..Default::default()
10807        },
10808        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10809    ));
10810
10811    let text = r#"
10812        let a = {
10813            key: "value",
10814        };
10815    "#
10816    .unindent();
10817
10818    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10819    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10820    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10821
10822    editor
10823        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10824        .await;
10825
10826    // Test case 1: Cursor after '{'
10827    editor.update_in(cx, |editor, window, cx| {
10828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10829            s.select_display_ranges([
10830                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
10831            ]);
10832        });
10833    });
10834    editor.update(cx, |editor, cx| {
10835        assert_text_with_selections(
10836            editor,
10837            indoc! {r#"
10838                let a = {ˇ
10839                    key: "value",
10840                };
10841            "#},
10842            cx,
10843        );
10844    });
10845    editor.update_in(cx, |editor, window, cx| {
10846        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10847    });
10848    editor.update(cx, |editor, cx| {
10849        assert_text_with_selections(
10850            editor,
10851            indoc! {r#"
10852                let a = «ˇ{
10853                    key: "value",
10854                }»;
10855            "#},
10856            cx,
10857        );
10858    });
10859
10860    // Test case 2: Cursor after ':'
10861    editor.update_in(cx, |editor, window, cx| {
10862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10863            s.select_display_ranges([
10864                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
10865            ]);
10866        });
10867    });
10868    editor.update(cx, |editor, cx| {
10869        assert_text_with_selections(
10870            editor,
10871            indoc! {r#"
10872                let a = {
10873                    key:ˇ "value",
10874                };
10875            "#},
10876            cx,
10877        );
10878    });
10879    editor.update_in(cx, |editor, window, cx| {
10880        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10881    });
10882    editor.update(cx, |editor, cx| {
10883        assert_text_with_selections(
10884            editor,
10885            indoc! {r#"
10886                let a = {
10887                    «ˇkey: "value"»,
10888                };
10889            "#},
10890            cx,
10891        );
10892    });
10893    editor.update_in(cx, |editor, window, cx| {
10894        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10895    });
10896    editor.update(cx, |editor, cx| {
10897        assert_text_with_selections(
10898            editor,
10899            indoc! {r#"
10900                let a = «ˇ{
10901                    key: "value",
10902                }»;
10903            "#},
10904            cx,
10905        );
10906    });
10907
10908    // Test case 3: Cursor after ','
10909    editor.update_in(cx, |editor, window, cx| {
10910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10911            s.select_display_ranges([
10912                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
10913            ]);
10914        });
10915    });
10916    editor.update(cx, |editor, cx| {
10917        assert_text_with_selections(
10918            editor,
10919            indoc! {r#"
10920                let a = {
10921                    key: "value",ˇ
10922                };
10923            "#},
10924            cx,
10925        );
10926    });
10927    editor.update_in(cx, |editor, window, cx| {
10928        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10929    });
10930    editor.update(cx, |editor, cx| {
10931        assert_text_with_selections(
10932            editor,
10933            indoc! {r#"
10934                let a = «ˇ{
10935                    key: "value",
10936                }»;
10937            "#},
10938            cx,
10939        );
10940    });
10941
10942    // Test case 4: Cursor after ';'
10943    editor.update_in(cx, |editor, window, cx| {
10944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10945            s.select_display_ranges([
10946                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
10947            ]);
10948        });
10949    });
10950    editor.update(cx, |editor, cx| {
10951        assert_text_with_selections(
10952            editor,
10953            indoc! {r#"
10954                let a = {
10955                    key: "value",
10956                };ˇ
10957            "#},
10958            cx,
10959        );
10960    });
10961    editor.update_in(cx, |editor, window, cx| {
10962        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10963    });
10964    editor.update(cx, |editor, cx| {
10965        assert_text_with_selections(
10966            editor,
10967            indoc! {r#"
10968                «ˇlet a = {
10969                    key: "value",
10970                };
10971                »"#},
10972            cx,
10973        );
10974    });
10975}
10976
10977#[gpui::test]
10978async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
10979    init_test(cx, |_| {});
10980
10981    let language = Arc::new(Language::new(
10982        LanguageConfig::default(),
10983        Some(tree_sitter_rust::LANGUAGE.into()),
10984    ));
10985
10986    let text = r#"
10987        use mod1::mod2::{mod3, mod4};
10988
10989        fn fn_1(param1: bool, param2: &str) {
10990            let var1 = "hello world";
10991        }
10992    "#
10993    .unindent();
10994
10995    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10996    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10997    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10998
10999    editor
11000        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11001        .await;
11002
11003    // Test 1: Cursor on a letter of a string word
11004    editor.update_in(cx, |editor, window, cx| {
11005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11006            s.select_display_ranges([
11007                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
11008            ]);
11009        });
11010    });
11011    editor.update_in(cx, |editor, window, cx| {
11012        assert_text_with_selections(
11013            editor,
11014            indoc! {r#"
11015                use mod1::mod2::{mod3, mod4};
11016
11017                fn fn_1(param1: bool, param2: &str) {
11018                    let var1 = "hˇello world";
11019                }
11020            "#},
11021            cx,
11022        );
11023        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11024        assert_text_with_selections(
11025            editor,
11026            indoc! {r#"
11027                use mod1::mod2::{mod3, mod4};
11028
11029                fn fn_1(param1: bool, param2: &str) {
11030                    let var1 = "«ˇhello» world";
11031                }
11032            "#},
11033            cx,
11034        );
11035    });
11036
11037    // Test 2: Partial selection within a word
11038    editor.update_in(cx, |editor, window, cx| {
11039        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11040            s.select_display_ranges([
11041                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
11042            ]);
11043        });
11044    });
11045    editor.update_in(cx, |editor, window, cx| {
11046        assert_text_with_selections(
11047            editor,
11048            indoc! {r#"
11049                use mod1::mod2::{mod3, mod4};
11050
11051                fn fn_1(param1: bool, param2: &str) {
11052                    let var1 = "h«elˇ»lo world";
11053                }
11054            "#},
11055            cx,
11056        );
11057        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11058        assert_text_with_selections(
11059            editor,
11060            indoc! {r#"
11061                use mod1::mod2::{mod3, mod4};
11062
11063                fn fn_1(param1: bool, param2: &str) {
11064                    let var1 = "«ˇhello» world";
11065                }
11066            "#},
11067            cx,
11068        );
11069    });
11070
11071    // Test 3: Complete word already selected
11072    editor.update_in(cx, |editor, window, cx| {
11073        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11074            s.select_display_ranges([
11075                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
11076            ]);
11077        });
11078    });
11079    editor.update_in(cx, |editor, window, cx| {
11080        assert_text_with_selections(
11081            editor,
11082            indoc! {r#"
11083                use mod1::mod2::{mod3, mod4};
11084
11085                fn fn_1(param1: bool, param2: &str) {
11086                    let var1 = "«helloˇ» world";
11087                }
11088            "#},
11089            cx,
11090        );
11091        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11092        assert_text_with_selections(
11093            editor,
11094            indoc! {r#"
11095                use mod1::mod2::{mod3, mod4};
11096
11097                fn fn_1(param1: bool, param2: &str) {
11098                    let var1 = "«hello worldˇ»";
11099                }
11100            "#},
11101            cx,
11102        );
11103    });
11104
11105    // Test 4: Selection spanning across words
11106    editor.update_in(cx, |editor, window, cx| {
11107        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11108            s.select_display_ranges([
11109                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
11110            ]);
11111        });
11112    });
11113    editor.update_in(cx, |editor, window, cx| {
11114        assert_text_with_selections(
11115            editor,
11116            indoc! {r#"
11117                use mod1::mod2::{mod3, mod4};
11118
11119                fn fn_1(param1: bool, param2: &str) {
11120                    let var1 = "hel«lo woˇ»rld";
11121                }
11122            "#},
11123            cx,
11124        );
11125        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11126        assert_text_with_selections(
11127            editor,
11128            indoc! {r#"
11129                use mod1::mod2::{mod3, mod4};
11130
11131                fn fn_1(param1: bool, param2: &str) {
11132                    let var1 = "«ˇhello world»";
11133                }
11134            "#},
11135            cx,
11136        );
11137    });
11138
11139    // Test 5: Expansion beyond string
11140    editor.update_in(cx, |editor, window, cx| {
11141        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11142        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
11143        assert_text_with_selections(
11144            editor,
11145            indoc! {r#"
11146                use mod1::mod2::{mod3, mod4};
11147
11148                fn fn_1(param1: bool, param2: &str) {
11149                    «ˇlet var1 = "hello world";»
11150                }
11151            "#},
11152            cx,
11153        );
11154    });
11155}
11156
11157#[gpui::test]
11158async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
11159    init_test(cx, |_| {});
11160
11161    let mut cx = EditorTestContext::new(cx).await;
11162
11163    let language = Arc::new(Language::new(
11164        LanguageConfig::default(),
11165        Some(tree_sitter_rust::LANGUAGE.into()),
11166    ));
11167
11168    cx.update_buffer(|buffer, cx| {
11169        buffer.set_language(Some(language), cx);
11170    });
11171
11172    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
11173    cx.update_editor(|editor, window, cx| {
11174        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
11175    });
11176
11177    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
11178
11179    cx.set_state(indoc! { r#"fn a() {
11180          // what
11181          // a
11182          // ˇlong
11183          // method
11184          // I
11185          // sure
11186          // hope
11187          // it
11188          // works
11189    }"# });
11190
11191    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
11192    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
11193    cx.update(|_, cx| {
11194        multi_buffer.update(cx, |multi_buffer, cx| {
11195            multi_buffer.set_excerpts_for_path(
11196                PathKey::for_buffer(&buffer, cx),
11197                buffer,
11198                [Point::new(1, 0)..Point::new(1, 0)],
11199                3,
11200                cx,
11201            );
11202        });
11203    });
11204
11205    let editor2 = cx.new_window_entity(|window, cx| {
11206        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
11207    });
11208
11209    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
11210    cx.update_editor(|editor, window, cx| {
11211        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11212            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
11213        })
11214    });
11215
11216    cx.assert_editor_state(indoc! { "
11217        fn a() {
11218              // what
11219              // a
11220        ˇ      // long
11221              // method"});
11222
11223    cx.update_editor(|editor, window, cx| {
11224        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
11225    });
11226
11227    // Although we could potentially make the action work when the syntax node
11228    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
11229    // did. Maybe we could also expand the excerpt to contain the range?
11230    cx.assert_editor_state(indoc! { "
11231        fn a() {
11232              // what
11233              // a
11234        ˇ      // long
11235              // method"});
11236}
11237
11238#[gpui::test]
11239async fn test_fold_function_bodies(cx: &mut TestAppContext) {
11240    init_test(cx, |_| {});
11241
11242    let base_text = r#"
11243        impl A {
11244            // this is an uncommitted comment
11245
11246            fn b() {
11247                c();
11248            }
11249
11250            // this is another uncommitted comment
11251
11252            fn d() {
11253                // e
11254                // f
11255            }
11256        }
11257
11258        fn g() {
11259            // h
11260        }
11261    "#
11262    .unindent();
11263
11264    let text = r#"
11265        ˇimpl A {
11266
11267            fn b() {
11268                c();
11269            }
11270
11271            fn d() {
11272                // e
11273                // f
11274            }
11275        }
11276
11277        fn g() {
11278            // h
11279        }
11280    "#
11281    .unindent();
11282
11283    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
11284    cx.set_state(&text);
11285    cx.set_head_text(&base_text);
11286    cx.update_editor(|editor, window, cx| {
11287        editor.expand_all_diff_hunks(&Default::default(), window, cx);
11288    });
11289
11290    cx.assert_state_with_diff(
11291        "
11292        ˇimpl A {
11293      -     // this is an uncommitted comment
11294
11295            fn b() {
11296                c();
11297            }
11298
11299      -     // this is another uncommitted comment
11300      -
11301            fn d() {
11302                // e
11303                // f
11304            }
11305        }
11306
11307        fn g() {
11308            // h
11309        }
11310    "
11311        .unindent(),
11312    );
11313
11314    let expected_display_text = "
11315        impl A {
11316            // this is an uncommitted comment
11317
11318            fn b() {
1131911320            }
11321
11322            // this is another uncommitted comment
11323
11324            fn d() {
1132511326            }
11327        }
11328
11329        fn g() {
1133011331        }
11332        "
11333    .unindent();
11334
11335    cx.update_editor(|editor, window, cx| {
11336        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
11337        assert_eq!(editor.display_text(cx), expected_display_text);
11338    });
11339}
11340
11341#[gpui::test]
11342async fn test_autoindent(cx: &mut TestAppContext) {
11343    init_test(cx, |_| {});
11344
11345    let language = Arc::new(
11346        Language::new(
11347            LanguageConfig {
11348                brackets: BracketPairConfig {
11349                    pairs: vec![
11350                        BracketPair {
11351                            start: "{".to_string(),
11352                            end: "}".to_string(),
11353                            close: false,
11354                            surround: false,
11355                            newline: true,
11356                        },
11357                        BracketPair {
11358                            start: "(".to_string(),
11359                            end: ")".to_string(),
11360                            close: false,
11361                            surround: false,
11362                            newline: true,
11363                        },
11364                    ],
11365                    ..Default::default()
11366                },
11367                ..Default::default()
11368            },
11369            Some(tree_sitter_rust::LANGUAGE.into()),
11370        )
11371        .with_indents_query(
11372            r#"
11373                (_ "(" ")" @end) @indent
11374                (_ "{" "}" @end) @indent
11375            "#,
11376        )
11377        .unwrap(),
11378    );
11379
11380    let text = "fn a() {}";
11381
11382    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11383    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11384    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11385    editor
11386        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11387        .await;
11388
11389    editor.update_in(cx, |editor, window, cx| {
11390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11391            s.select_ranges([
11392                MultiBufferOffset(5)..MultiBufferOffset(5),
11393                MultiBufferOffset(8)..MultiBufferOffset(8),
11394                MultiBufferOffset(9)..MultiBufferOffset(9),
11395            ])
11396        });
11397        editor.newline(&Newline, window, cx);
11398        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
11399        assert_eq!(
11400            editor.selections.ranges(&editor.display_snapshot(cx)),
11401            &[
11402                Point::new(1, 4)..Point::new(1, 4),
11403                Point::new(3, 4)..Point::new(3, 4),
11404                Point::new(5, 0)..Point::new(5, 0)
11405            ]
11406        );
11407    });
11408}
11409
11410#[gpui::test]
11411async fn test_autoindent_disabled(cx: &mut TestAppContext) {
11412    init_test(cx, |settings| {
11413        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
11414    });
11415
11416    let language = Arc::new(
11417        Language::new(
11418            LanguageConfig {
11419                brackets: BracketPairConfig {
11420                    pairs: vec![
11421                        BracketPair {
11422                            start: "{".to_string(),
11423                            end: "}".to_string(),
11424                            close: false,
11425                            surround: false,
11426                            newline: true,
11427                        },
11428                        BracketPair {
11429                            start: "(".to_string(),
11430                            end: ")".to_string(),
11431                            close: false,
11432                            surround: false,
11433                            newline: true,
11434                        },
11435                    ],
11436                    ..Default::default()
11437                },
11438                ..Default::default()
11439            },
11440            Some(tree_sitter_rust::LANGUAGE.into()),
11441        )
11442        .with_indents_query(
11443            r#"
11444                (_ "(" ")" @end) @indent
11445                (_ "{" "}" @end) @indent
11446            "#,
11447        )
11448        .unwrap(),
11449    );
11450
11451    let text = "fn a() {}";
11452
11453    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11454    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11455    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11456    editor
11457        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11458        .await;
11459
11460    editor.update_in(cx, |editor, window, cx| {
11461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11462            s.select_ranges([
11463                MultiBufferOffset(5)..MultiBufferOffset(5),
11464                MultiBufferOffset(8)..MultiBufferOffset(8),
11465                MultiBufferOffset(9)..MultiBufferOffset(9),
11466            ])
11467        });
11468        editor.newline(&Newline, window, cx);
11469        assert_eq!(
11470            editor.text(cx),
11471            indoc!(
11472                "
11473                fn a(
11474
11475                ) {
11476
11477                }
11478                "
11479            )
11480        );
11481        assert_eq!(
11482            editor.selections.ranges(&editor.display_snapshot(cx)),
11483            &[
11484                Point::new(1, 0)..Point::new(1, 0),
11485                Point::new(3, 0)..Point::new(3, 0),
11486                Point::new(5, 0)..Point::new(5, 0)
11487            ]
11488        );
11489    });
11490}
11491
11492#[gpui::test]
11493async fn test_autoindent_none_does_not_preserve_indentation_on_newline(cx: &mut TestAppContext) {
11494    init_test(cx, |settings| {
11495        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
11496    });
11497
11498    let mut cx = EditorTestContext::new(cx).await;
11499
11500    cx.set_state(indoc! {"
11501        hello
11502            indented lineˇ
11503        world
11504    "});
11505
11506    cx.update_editor(|editor, window, cx| {
11507        editor.newline(&Newline, window, cx);
11508    });
11509
11510    cx.assert_editor_state(indoc! {"
11511        hello
11512            indented line
11513        ˇ
11514        world
11515    "});
11516}
11517
11518#[gpui::test]
11519async fn test_autoindent_preserve_indent_maintains_indentation_on_newline(cx: &mut TestAppContext) {
11520    // When auto_indent is "preserve_indent", pressing Enter on an indented line
11521    // should preserve the indentation but not adjust based on syntax.
11522    init_test(cx, |settings| {
11523        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
11524    });
11525
11526    let mut cx = EditorTestContext::new(cx).await;
11527
11528    cx.set_state(indoc! {"
11529        hello
11530            indented lineˇ
11531        world
11532    "});
11533
11534    cx.update_editor(|editor, window, cx| {
11535        editor.newline(&Newline, window, cx);
11536    });
11537
11538    // The new line SHOULD have the same indentation as the previous line
11539    cx.assert_editor_state(indoc! {"
11540        hello
11541            indented line
11542            ˇ
11543        world
11544    "});
11545}
11546
11547#[gpui::test]
11548async fn test_autoindent_preserve_indent_does_not_apply_syntax_indent(cx: &mut TestAppContext) {
11549    init_test(cx, |settings| {
11550        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
11551    });
11552
11553    let language = Arc::new(
11554        Language::new(
11555            LanguageConfig {
11556                brackets: BracketPairConfig {
11557                    pairs: vec![BracketPair {
11558                        start: "{".to_string(),
11559                        end: "}".to_string(),
11560                        close: false,
11561                        surround: false,
11562                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11563                    }],
11564                    ..Default::default()
11565                },
11566                ..Default::default()
11567            },
11568            Some(tree_sitter_rust::LANGUAGE.into()),
11569        )
11570        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11571        .unwrap(),
11572    );
11573
11574    let buffer =
11575        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), cx));
11576    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11577    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11578    editor
11579        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11580        .await;
11581
11582    // Position cursor at end of line containing `{`
11583    editor.update_in(cx, |editor, window, cx| {
11584        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11585            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11586        });
11587        editor.newline(&Newline, window, cx);
11588
11589        // With PreserveIndent, the new line should have 0 indentation (same as the fn line)
11590        // NOT 4 spaces (which tree-sitter would add for being inside `{}`)
11591        assert_eq!(editor.text(cx), "fn foo() {\n\n}");
11592    });
11593}
11594
11595#[gpui::test]
11596async fn test_autoindent_syntax_aware_applies_syntax_indent(cx: &mut TestAppContext) {
11597    // Companion test to show that SyntaxAware DOES apply tree-sitter indentation
11598    init_test(cx, |settings| {
11599        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware)
11600    });
11601
11602    let language = Arc::new(
11603        Language::new(
11604            LanguageConfig {
11605                brackets: BracketPairConfig {
11606                    pairs: vec![BracketPair {
11607                        start: "{".to_string(),
11608                        end: "}".to_string(),
11609                        close: false,
11610                        surround: false,
11611                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11612                    }],
11613                    ..Default::default()
11614                },
11615                ..Default::default()
11616            },
11617            Some(tree_sitter_rust::LANGUAGE.into()),
11618        )
11619        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11620        .unwrap(),
11621    );
11622
11623    let buffer =
11624        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), 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    // Position cursor at end of line containing `{`
11632    editor.update_in(cx, |editor, window, cx| {
11633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11634            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11635        });
11636        editor.newline(&Newline, window, cx);
11637
11638        // With SyntaxAware, tree-sitter adds indentation for being inside `{}`
11639        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
11640    });
11641}
11642
11643#[gpui::test]
11644async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
11645    init_test(cx, |settings| {
11646        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware);
11647        settings.languages.0.insert(
11648            "python".into(),
11649            LanguageSettingsContent {
11650                auto_indent: Some(settings::AutoIndentMode::None),
11651                ..Default::default()
11652            },
11653        );
11654    });
11655
11656    let mut cx = EditorTestContext::new(cx).await;
11657
11658    let injected_language = Arc::new(
11659        Language::new(
11660            LanguageConfig {
11661                brackets: BracketPairConfig {
11662                    pairs: vec![
11663                        BracketPair {
11664                            start: "{".to_string(),
11665                            end: "}".to_string(),
11666                            close: false,
11667                            surround: false,
11668                            newline: true,
11669                        },
11670                        BracketPair {
11671                            start: "(".to_string(),
11672                            end: ")".to_string(),
11673                            close: true,
11674                            surround: false,
11675                            newline: true,
11676                        },
11677                    ],
11678                    ..Default::default()
11679                },
11680                name: "python".into(),
11681                ..Default::default()
11682            },
11683            Some(tree_sitter_python::LANGUAGE.into()),
11684        )
11685        .with_indents_query(
11686            r#"
11687                (_ "(" ")" @end) @indent
11688                (_ "{" "}" @end) @indent
11689            "#,
11690        )
11691        .unwrap(),
11692    );
11693
11694    let language = Arc::new(
11695        Language::new(
11696            LanguageConfig {
11697                brackets: BracketPairConfig {
11698                    pairs: vec![
11699                        BracketPair {
11700                            start: "{".to_string(),
11701                            end: "}".to_string(),
11702                            close: false,
11703                            surround: false,
11704                            newline: true,
11705                        },
11706                        BracketPair {
11707                            start: "(".to_string(),
11708                            end: ")".to_string(),
11709                            close: true,
11710                            surround: false,
11711                            newline: true,
11712                        },
11713                    ],
11714                    ..Default::default()
11715                },
11716                name: LanguageName::new_static("rust"),
11717                ..Default::default()
11718            },
11719            Some(tree_sitter_rust::LANGUAGE.into()),
11720        )
11721        .with_indents_query(
11722            r#"
11723                (_ "(" ")" @end) @indent
11724                (_ "{" "}" @end) @indent
11725            "#,
11726        )
11727        .unwrap()
11728        .with_injection_query(
11729            r#"
11730            (macro_invocation
11731                macro: (identifier) @_macro_name
11732                (token_tree) @injection.content
11733                (#set! injection.language "python"))
11734           "#,
11735        )
11736        .unwrap(),
11737    );
11738
11739    cx.language_registry().add(injected_language);
11740    cx.language_registry().add(language.clone());
11741
11742    cx.update_buffer(|buffer, cx| {
11743        buffer.set_language(Some(language), cx);
11744    });
11745
11746    cx.set_state(r#"struct A {ˇ}"#);
11747
11748    cx.update_editor(|editor, window, cx| {
11749        editor.newline(&Default::default(), window, cx);
11750    });
11751
11752    cx.assert_editor_state(indoc!(
11753        "struct A {
11754            ˇ
11755        }"
11756    ));
11757
11758    cx.set_state(r#"select_biased!(ˇ)"#);
11759
11760    cx.update_editor(|editor, window, cx| {
11761        editor.newline(&Default::default(), window, cx);
11762        editor.handle_input("def ", window, cx);
11763        editor.handle_input("(", window, cx);
11764        editor.newline(&Default::default(), window, cx);
11765        editor.handle_input("a", window, cx);
11766    });
11767
11768    cx.assert_editor_state(indoc!(
11769        "select_biased!(
11770        def (
1177111772        )
11773        )"
11774    ));
11775}
11776
11777#[gpui::test]
11778async fn test_autoindent_selections(cx: &mut TestAppContext) {
11779    init_test(cx, |_| {});
11780
11781    {
11782        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
11783        cx.set_state(indoc! {"
11784            impl A {
11785
11786                fn b() {}
11787
11788            «fn c() {
11789
11790            }ˇ»
11791            }
11792        "});
11793
11794        cx.update_editor(|editor, window, cx| {
11795            editor.autoindent(&Default::default(), window, cx);
11796        });
11797        cx.wait_for_autoindent_applied().await;
11798
11799        cx.assert_editor_state(indoc! {"
11800            impl A {
11801
11802                fn b() {}
11803
11804                «fn c() {
11805
11806                }ˇ»
11807            }
11808        "});
11809    }
11810
11811    {
11812        let mut cx = EditorTestContext::new_multibuffer(
11813            cx,
11814            [indoc! { "
11815                impl A {
11816                «
11817                // a
11818                fn b(){}
11819                »
11820                «
11821                    }
11822                    fn c(){}
11823                »
11824            "}],
11825        );
11826
11827        let buffer = cx.update_editor(|editor, _, cx| {
11828            let buffer = editor.buffer().update(cx, |buffer, _| {
11829                buffer.all_buffers().iter().next().unwrap().clone()
11830            });
11831            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
11832            buffer
11833        });
11834
11835        cx.run_until_parked();
11836        cx.update_editor(|editor, window, cx| {
11837            editor.select_all(&Default::default(), window, cx);
11838            editor.autoindent(&Default::default(), window, cx)
11839        });
11840        cx.run_until_parked();
11841
11842        cx.update(|_, cx| {
11843            assert_eq!(
11844                buffer.read(cx).text(),
11845                indoc! { "
11846                    impl A {
11847
11848                        // a
11849                        fn b(){}
11850
11851
11852                    }
11853                    fn c(){}
11854
11855                " }
11856            )
11857        });
11858    }
11859}
11860
11861#[gpui::test]
11862async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
11863    init_test(cx, |_| {});
11864
11865    let mut cx = EditorTestContext::new(cx).await;
11866
11867    let language = Arc::new(Language::new(
11868        LanguageConfig {
11869            brackets: BracketPairConfig {
11870                pairs: vec![
11871                    BracketPair {
11872                        start: "{".to_string(),
11873                        end: "}".to_string(),
11874                        close: true,
11875                        surround: true,
11876                        newline: true,
11877                    },
11878                    BracketPair {
11879                        start: "(".to_string(),
11880                        end: ")".to_string(),
11881                        close: true,
11882                        surround: true,
11883                        newline: true,
11884                    },
11885                    BracketPair {
11886                        start: "/*".to_string(),
11887                        end: " */".to_string(),
11888                        close: true,
11889                        surround: true,
11890                        newline: true,
11891                    },
11892                    BracketPair {
11893                        start: "[".to_string(),
11894                        end: "]".to_string(),
11895                        close: false,
11896                        surround: false,
11897                        newline: true,
11898                    },
11899                    BracketPair {
11900                        start: "\"".to_string(),
11901                        end: "\"".to_string(),
11902                        close: true,
11903                        surround: true,
11904                        newline: false,
11905                    },
11906                    BracketPair {
11907                        start: "<".to_string(),
11908                        end: ">".to_string(),
11909                        close: false,
11910                        surround: true,
11911                        newline: true,
11912                    },
11913                ],
11914                ..Default::default()
11915            },
11916            autoclose_before: "})]".to_string(),
11917            ..Default::default()
11918        },
11919        Some(tree_sitter_rust::LANGUAGE.into()),
11920    ));
11921
11922    cx.language_registry().add(language.clone());
11923    cx.update_buffer(|buffer, cx| {
11924        buffer.set_language(Some(language), cx);
11925    });
11926
11927    cx.set_state(
11928        &r#"
11929            🏀ˇ
11930            εˇ
11931            ❤️ˇ
11932        "#
11933        .unindent(),
11934    );
11935
11936    // autoclose multiple nested brackets at multiple cursors
11937    cx.update_editor(|editor, window, cx| {
11938        editor.handle_input("{", window, cx);
11939        editor.handle_input("{", window, cx);
11940        editor.handle_input("{", window, cx);
11941    });
11942    cx.assert_editor_state(
11943        &"
11944            🏀{{{ˇ}}}
11945            ε{{{ˇ}}}
11946            ❤️{{{ˇ}}}
11947        "
11948        .unindent(),
11949    );
11950
11951    // insert a different closing bracket
11952    cx.update_editor(|editor, window, cx| {
11953        editor.handle_input(")", window, cx);
11954    });
11955    cx.assert_editor_state(
11956        &"
11957            🏀{{{)ˇ}}}
11958            ε{{{)ˇ}}}
11959            ❤️{{{)ˇ}}}
11960        "
11961        .unindent(),
11962    );
11963
11964    // skip over the auto-closed brackets when typing a closing bracket
11965    cx.update_editor(|editor, window, cx| {
11966        editor.move_right(&MoveRight, window, cx);
11967        editor.handle_input("}", window, cx);
11968        editor.handle_input("}", window, cx);
11969        editor.handle_input("}", window, cx);
11970    });
11971    cx.assert_editor_state(
11972        &"
11973            🏀{{{)}}}}ˇ
11974            ε{{{)}}}}ˇ
11975            ❤️{{{)}}}}ˇ
11976        "
11977        .unindent(),
11978    );
11979
11980    // autoclose multi-character pairs
11981    cx.set_state(
11982        &"
11983            ˇ
11984            ˇ
11985        "
11986        .unindent(),
11987    );
11988    cx.update_editor(|editor, window, cx| {
11989        editor.handle_input("/", window, cx);
11990        editor.handle_input("*", window, cx);
11991    });
11992    cx.assert_editor_state(
11993        &"
11994            /*ˇ */
11995            /*ˇ */
11996        "
11997        .unindent(),
11998    );
11999
12000    // one cursor autocloses a multi-character pair, one cursor
12001    // does not autoclose.
12002    cx.set_state(
12003        &"
1200412005            ˇ
12006        "
12007        .unindent(),
12008    );
12009    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
12010    cx.assert_editor_state(
12011        &"
12012            /*ˇ */
1201312014        "
12015        .unindent(),
12016    );
12017
12018    // Don't autoclose if the next character isn't whitespace and isn't
12019    // listed in the language's "autoclose_before" section.
12020    cx.set_state("ˇa b");
12021    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
12022    cx.assert_editor_state("{ˇa b");
12023
12024    // Don't autoclose if `close` is false for the bracket pair
12025    cx.set_state("ˇ");
12026    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
12027    cx.assert_editor_state("");
12028
12029    // Surround with brackets if text is selected
12030    cx.set_state("«aˇ» b");
12031    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
12032    cx.assert_editor_state("{«aˇ»} b");
12033
12034    // Autoclose when not immediately after a word character
12035    cx.set_state("a ˇ");
12036    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
12037    cx.assert_editor_state("a \"ˇ\"");
12038
12039    // Autoclose pair where the start and end characters are the same
12040    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
12041    cx.assert_editor_state("a \"\"ˇ");
12042
12043    // Don't autoclose when immediately after a word character
12044    cx.set_state("");
12045    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
12046    cx.assert_editor_state("a\"ˇ");
12047
12048    // Do autoclose when after a non-word character
12049    cx.set_state("");
12050    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
12051    cx.assert_editor_state("{\"ˇ\"");
12052
12053    // Non identical pairs autoclose regardless of preceding character
12054    cx.set_state("");
12055    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
12056    cx.assert_editor_state("a{ˇ}");
12057
12058    // Don't autoclose pair if autoclose is disabled
12059    cx.set_state("ˇ");
12060    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
12061    cx.assert_editor_state("");
12062
12063    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
12064    cx.set_state("«aˇ» b");
12065    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
12066    cx.assert_editor_state("<«aˇ»> b");
12067}
12068
12069#[gpui::test]
12070async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
12071    init_test(cx, |settings| {
12072        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
12073    });
12074
12075    let mut cx = EditorTestContext::new(cx).await;
12076
12077    let language = Arc::new(Language::new(
12078        LanguageConfig {
12079            brackets: BracketPairConfig {
12080                pairs: vec![
12081                    BracketPair {
12082                        start: "{".to_string(),
12083                        end: "}".to_string(),
12084                        close: true,
12085                        surround: true,
12086                        newline: true,
12087                    },
12088                    BracketPair {
12089                        start: "(".to_string(),
12090                        end: ")".to_string(),
12091                        close: true,
12092                        surround: true,
12093                        newline: true,
12094                    },
12095                    BracketPair {
12096                        start: "[".to_string(),
12097                        end: "]".to_string(),
12098                        close: false,
12099                        surround: false,
12100                        newline: true,
12101                    },
12102                ],
12103                ..Default::default()
12104            },
12105            autoclose_before: "})]".to_string(),
12106            ..Default::default()
12107        },
12108        Some(tree_sitter_rust::LANGUAGE.into()),
12109    ));
12110
12111    cx.language_registry().add(language.clone());
12112    cx.update_buffer(|buffer, cx| {
12113        buffer.set_language(Some(language), cx);
12114    });
12115
12116    cx.set_state(
12117        &"
12118            ˇ
12119            ˇ
12120            ˇ
12121        "
12122        .unindent(),
12123    );
12124
12125    // ensure only matching closing brackets are skipped over
12126    cx.update_editor(|editor, window, cx| {
12127        editor.handle_input("}", window, cx);
12128        editor.move_left(&MoveLeft, window, cx);
12129        editor.handle_input(")", window, cx);
12130        editor.move_left(&MoveLeft, window, cx);
12131    });
12132    cx.assert_editor_state(
12133        &"
12134            ˇ)}
12135            ˇ)}
12136            ˇ)}
12137        "
12138        .unindent(),
12139    );
12140
12141    // skip-over closing brackets at multiple cursors
12142    cx.update_editor(|editor, window, cx| {
12143        editor.handle_input(")", window, cx);
12144        editor.handle_input("}", window, cx);
12145    });
12146    cx.assert_editor_state(
12147        &"
12148            )}ˇ
12149            )}ˇ
12150            )}ˇ
12151        "
12152        .unindent(),
12153    );
12154
12155    // ignore non-close brackets
12156    cx.update_editor(|editor, window, cx| {
12157        editor.handle_input("]", window, cx);
12158        editor.move_left(&MoveLeft, window, cx);
12159        editor.handle_input("]", window, cx);
12160    });
12161    cx.assert_editor_state(
12162        &"
12163            )}]ˇ]
12164            )}]ˇ]
12165            )}]ˇ]
12166        "
12167        .unindent(),
12168    );
12169}
12170
12171#[gpui::test]
12172async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
12173    init_test(cx, |_| {});
12174
12175    let mut cx = EditorTestContext::new(cx).await;
12176
12177    let html_language = Arc::new(
12178        Language::new(
12179            LanguageConfig {
12180                name: "HTML".into(),
12181                brackets: BracketPairConfig {
12182                    pairs: vec![
12183                        BracketPair {
12184                            start: "<".into(),
12185                            end: ">".into(),
12186                            close: true,
12187                            ..Default::default()
12188                        },
12189                        BracketPair {
12190                            start: "{".into(),
12191                            end: "}".into(),
12192                            close: true,
12193                            ..Default::default()
12194                        },
12195                        BracketPair {
12196                            start: "(".into(),
12197                            end: ")".into(),
12198                            close: true,
12199                            ..Default::default()
12200                        },
12201                    ],
12202                    ..Default::default()
12203                },
12204                autoclose_before: "})]>".into(),
12205                ..Default::default()
12206            },
12207            Some(tree_sitter_html::LANGUAGE.into()),
12208        )
12209        .with_injection_query(
12210            r#"
12211            (script_element
12212                (raw_text) @injection.content
12213                (#set! injection.language "javascript"))
12214            "#,
12215        )
12216        .unwrap(),
12217    );
12218
12219    let javascript_language = Arc::new(Language::new(
12220        LanguageConfig {
12221            name: "JavaScript".into(),
12222            brackets: BracketPairConfig {
12223                pairs: vec![
12224                    BracketPair {
12225                        start: "/*".into(),
12226                        end: " */".into(),
12227                        close: true,
12228                        ..Default::default()
12229                    },
12230                    BracketPair {
12231                        start: "{".into(),
12232                        end: "}".into(),
12233                        close: true,
12234                        ..Default::default()
12235                    },
12236                    BracketPair {
12237                        start: "(".into(),
12238                        end: ")".into(),
12239                        close: true,
12240                        ..Default::default()
12241                    },
12242                ],
12243                ..Default::default()
12244            },
12245            autoclose_before: "})]>".into(),
12246            ..Default::default()
12247        },
12248        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12249    ));
12250
12251    cx.language_registry().add(html_language.clone());
12252    cx.language_registry().add(javascript_language);
12253    cx.executor().run_until_parked();
12254
12255    cx.update_buffer(|buffer, cx| {
12256        buffer.set_language(Some(html_language), cx);
12257    });
12258
12259    cx.set_state(
12260        &r#"
12261            <body>ˇ
12262                <script>
12263                    var x = 1;ˇ
12264                </script>
12265            </body>ˇ
12266        "#
12267        .unindent(),
12268    );
12269
12270    // Precondition: different languages are active at different locations.
12271    cx.update_editor(|editor, window, cx| {
12272        let snapshot = editor.snapshot(window, cx);
12273        let cursors = editor
12274            .selections
12275            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
12276        let languages = cursors
12277            .iter()
12278            .map(|c| snapshot.language_at(c.start).unwrap().name())
12279            .collect::<Vec<_>>();
12280        assert_eq!(
12281            languages,
12282            &[
12283                LanguageName::from("HTML"),
12284                LanguageName::from("JavaScript"),
12285                LanguageName::from("HTML"),
12286            ]
12287        );
12288    });
12289
12290    // Angle brackets autoclose in HTML, but not JavaScript.
12291    cx.update_editor(|editor, window, cx| {
12292        editor.handle_input("<", window, cx);
12293        editor.handle_input("a", window, cx);
12294    });
12295    cx.assert_editor_state(
12296        &r#"
12297            <body><aˇ>
12298                <script>
12299                    var x = 1;<aˇ
12300                </script>
12301            </body><aˇ>
12302        "#
12303        .unindent(),
12304    );
12305
12306    // Curly braces and parens autoclose in both HTML and JavaScript.
12307    cx.update_editor(|editor, window, cx| {
12308        editor.handle_input(" b=", window, cx);
12309        editor.handle_input("{", window, cx);
12310        editor.handle_input("c", window, cx);
12311        editor.handle_input("(", window, cx);
12312    });
12313    cx.assert_editor_state(
12314        &r#"
12315            <body><a b={c(ˇ)}>
12316                <script>
12317                    var x = 1;<a b={c(ˇ)}
12318                </script>
12319            </body><a b={c(ˇ)}>
12320        "#
12321        .unindent(),
12322    );
12323
12324    // Brackets that were already autoclosed are skipped.
12325    cx.update_editor(|editor, window, cx| {
12326        editor.handle_input(")", window, cx);
12327        editor.handle_input("d", window, cx);
12328        editor.handle_input("}", window, cx);
12329    });
12330    cx.assert_editor_state(
12331        &r#"
12332            <body><a b={c()d}ˇ>
12333                <script>
12334                    var x = 1;<a b={c()d}ˇ
12335                </script>
12336            </body><a b={c()d}ˇ>
12337        "#
12338        .unindent(),
12339    );
12340    cx.update_editor(|editor, window, cx| {
12341        editor.handle_input(">", window, cx);
12342    });
12343    cx.assert_editor_state(
12344        &r#"
12345            <body><a b={c()d}>ˇ
12346                <script>
12347                    var x = 1;<a b={c()d}>ˇ
12348                </script>
12349            </body><a b={c()d}>ˇ
12350        "#
12351        .unindent(),
12352    );
12353
12354    // Reset
12355    cx.set_state(
12356        &r#"
12357            <body>ˇ
12358                <script>
12359                    var x = 1;ˇ
12360                </script>
12361            </body>ˇ
12362        "#
12363        .unindent(),
12364    );
12365
12366    cx.update_editor(|editor, window, cx| {
12367        editor.handle_input("<", window, cx);
12368    });
12369    cx.assert_editor_state(
12370        &r#"
12371            <body><ˇ>
12372                <script>
12373                    var x = 1;<ˇ
12374                </script>
12375            </body><ˇ>
12376        "#
12377        .unindent(),
12378    );
12379
12380    // When backspacing, the closing angle brackets are removed.
12381    cx.update_editor(|editor, window, cx| {
12382        editor.backspace(&Backspace, window, cx);
12383    });
12384    cx.assert_editor_state(
12385        &r#"
12386            <body>ˇ
12387                <script>
12388                    var x = 1;ˇ
12389                </script>
12390            </body>ˇ
12391        "#
12392        .unindent(),
12393    );
12394
12395    // Block comments autoclose in JavaScript, but not HTML.
12396    cx.update_editor(|editor, window, cx| {
12397        editor.handle_input("/", window, cx);
12398        editor.handle_input("*", window, cx);
12399    });
12400    cx.assert_editor_state(
12401        &r#"
12402            <body>/*ˇ
12403                <script>
12404                    var x = 1;/*ˇ */
12405                </script>
12406            </body>/*ˇ
12407        "#
12408        .unindent(),
12409    );
12410}
12411
12412#[gpui::test]
12413async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
12414    init_test(cx, |_| {});
12415
12416    let mut cx = EditorTestContext::new(cx).await;
12417
12418    let rust_language = Arc::new(
12419        Language::new(
12420            LanguageConfig {
12421                name: "Rust".into(),
12422                brackets: serde_json::from_value(json!([
12423                    { "start": "{", "end": "}", "close": true, "newline": true },
12424                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
12425                ]))
12426                .unwrap(),
12427                autoclose_before: "})]>".into(),
12428                ..Default::default()
12429            },
12430            Some(tree_sitter_rust::LANGUAGE.into()),
12431        )
12432        .with_override_query("(string_literal) @string")
12433        .unwrap(),
12434    );
12435
12436    cx.language_registry().add(rust_language.clone());
12437    cx.update_buffer(|buffer, cx| {
12438        buffer.set_language(Some(rust_language), cx);
12439    });
12440
12441    cx.set_state(
12442        &r#"
12443            let x = ˇ
12444        "#
12445        .unindent(),
12446    );
12447
12448    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
12449    cx.update_editor(|editor, window, cx| {
12450        editor.handle_input("\"", window, cx);
12451    });
12452    cx.assert_editor_state(
12453        &r#"
12454            let x = "ˇ"
12455        "#
12456        .unindent(),
12457    );
12458
12459    // Inserting another quotation mark. The cursor moves across the existing
12460    // automatically-inserted quotation mark.
12461    cx.update_editor(|editor, window, cx| {
12462        editor.handle_input("\"", window, cx);
12463    });
12464    cx.assert_editor_state(
12465        &r#"
12466            let x = ""ˇ
12467        "#
12468        .unindent(),
12469    );
12470
12471    // Reset
12472    cx.set_state(
12473        &r#"
12474            let x = ˇ
12475        "#
12476        .unindent(),
12477    );
12478
12479    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
12480    cx.update_editor(|editor, window, cx| {
12481        editor.handle_input("\"", window, cx);
12482        editor.handle_input(" ", window, cx);
12483        editor.move_left(&Default::default(), window, cx);
12484        editor.handle_input("\\", window, cx);
12485        editor.handle_input("\"", window, cx);
12486    });
12487    cx.assert_editor_state(
12488        &r#"
12489            let x = "\"ˇ "
12490        "#
12491        .unindent(),
12492    );
12493
12494    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
12495    // mark. Nothing is inserted.
12496    cx.update_editor(|editor, window, cx| {
12497        editor.move_right(&Default::default(), window, cx);
12498        editor.handle_input("\"", window, cx);
12499    });
12500    cx.assert_editor_state(
12501        &r#"
12502            let x = "\" "ˇ
12503        "#
12504        .unindent(),
12505    );
12506}
12507
12508#[gpui::test]
12509async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
12510    init_test(cx, |_| {});
12511
12512    let mut cx = EditorTestContext::new(cx).await;
12513    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
12514
12515    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12516
12517    // Double quote inside single-quoted string
12518    cx.set_state(indoc! {r#"
12519        def main():
12520            items = ['"', ˇ]
12521    "#});
12522    cx.update_editor(|editor, window, cx| {
12523        editor.handle_input("\"", window, cx);
12524    });
12525    cx.assert_editor_state(indoc! {r#"
12526        def main():
12527            items = ['"', "ˇ"]
12528    "#});
12529
12530    // Two double quotes inside single-quoted string
12531    cx.set_state(indoc! {r#"
12532        def main():
12533            items = ['""', ˇ]
12534    "#});
12535    cx.update_editor(|editor, window, cx| {
12536        editor.handle_input("\"", window, cx);
12537    });
12538    cx.assert_editor_state(indoc! {r#"
12539        def main():
12540            items = ['""', "ˇ"]
12541    "#});
12542
12543    // Single quote inside double-quoted string
12544    cx.set_state(indoc! {r#"
12545        def main():
12546            items = ["'", ˇ]
12547    "#});
12548    cx.update_editor(|editor, window, cx| {
12549        editor.handle_input("'", window, cx);
12550    });
12551    cx.assert_editor_state(indoc! {r#"
12552        def main():
12553            items = ["'", 'ˇ']
12554    "#});
12555
12556    // Two single quotes inside double-quoted string
12557    cx.set_state(indoc! {r#"
12558        def main():
12559            items = ["''", ˇ]
12560    "#});
12561    cx.update_editor(|editor, window, cx| {
12562        editor.handle_input("'", window, cx);
12563    });
12564    cx.assert_editor_state(indoc! {r#"
12565        def main():
12566            items = ["''", 'ˇ']
12567    "#});
12568
12569    // Mixed quotes on same line
12570    cx.set_state(indoc! {r#"
12571        def main():
12572            items = ['"""', "'''''", ˇ]
12573    "#});
12574    cx.update_editor(|editor, window, cx| {
12575        editor.handle_input("\"", window, cx);
12576    });
12577    cx.assert_editor_state(indoc! {r#"
12578        def main():
12579            items = ['"""', "'''''", "ˇ"]
12580    "#});
12581    cx.update_editor(|editor, window, cx| {
12582        editor.move_right(&MoveRight, window, cx);
12583    });
12584    cx.update_editor(|editor, window, cx| {
12585        editor.handle_input(", ", window, cx);
12586    });
12587    cx.update_editor(|editor, window, cx| {
12588        editor.handle_input("'", window, cx);
12589    });
12590    cx.assert_editor_state(indoc! {r#"
12591        def main():
12592            items = ['"""', "'''''", "", 'ˇ']
12593    "#});
12594}
12595
12596#[gpui::test]
12597async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
12598    init_test(cx, |_| {});
12599
12600    let mut cx = EditorTestContext::new(cx).await;
12601    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
12602    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12603
12604    cx.set_state(indoc! {r#"
12605        def main():
12606            items = ["🎉", ˇ]
12607    "#});
12608    cx.update_editor(|editor, window, cx| {
12609        editor.handle_input("\"", window, cx);
12610    });
12611    cx.assert_editor_state(indoc! {r#"
12612        def main():
12613            items = ["🎉", "ˇ"]
12614    "#});
12615}
12616
12617#[gpui::test]
12618async fn test_surround_with_pair(cx: &mut TestAppContext) {
12619    init_test(cx, |_| {});
12620
12621    let language = Arc::new(Language::new(
12622        LanguageConfig {
12623            brackets: BracketPairConfig {
12624                pairs: vec![
12625                    BracketPair {
12626                        start: "{".to_string(),
12627                        end: "}".to_string(),
12628                        close: true,
12629                        surround: true,
12630                        newline: true,
12631                    },
12632                    BracketPair {
12633                        start: "/* ".to_string(),
12634                        end: "*/".to_string(),
12635                        close: true,
12636                        surround: true,
12637                        ..Default::default()
12638                    },
12639                ],
12640                ..Default::default()
12641            },
12642            ..Default::default()
12643        },
12644        Some(tree_sitter_rust::LANGUAGE.into()),
12645    ));
12646
12647    let text = r#"
12648        a
12649        b
12650        c
12651    "#
12652    .unindent();
12653
12654    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12655    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12656    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12657    editor
12658        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12659        .await;
12660
12661    editor.update_in(cx, |editor, window, cx| {
12662        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12663            s.select_display_ranges([
12664                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12665                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12666                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
12667            ])
12668        });
12669
12670        editor.handle_input("{", window, cx);
12671        editor.handle_input("{", window, cx);
12672        editor.handle_input("{", window, cx);
12673        assert_eq!(
12674            editor.text(cx),
12675            "
12676                {{{a}}}
12677                {{{b}}}
12678                {{{c}}}
12679            "
12680            .unindent()
12681        );
12682        assert_eq!(
12683            display_ranges(editor, cx),
12684            [
12685                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
12686                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
12687                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
12688            ]
12689        );
12690
12691        editor.undo(&Undo, window, cx);
12692        editor.undo(&Undo, window, cx);
12693        editor.undo(&Undo, window, cx);
12694        assert_eq!(
12695            editor.text(cx),
12696            "
12697                a
12698                b
12699                c
12700            "
12701            .unindent()
12702        );
12703        assert_eq!(
12704            display_ranges(editor, cx),
12705            [
12706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12707                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12708                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12709            ]
12710        );
12711
12712        // Ensure inserting the first character of a multi-byte bracket pair
12713        // doesn't surround the selections with the bracket.
12714        editor.handle_input("/", window, cx);
12715        assert_eq!(
12716            editor.text(cx),
12717            "
12718                /
12719                /
12720                /
12721            "
12722            .unindent()
12723        );
12724        assert_eq!(
12725            display_ranges(editor, cx),
12726            [
12727                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12728                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12729                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12730            ]
12731        );
12732
12733        editor.undo(&Undo, window, cx);
12734        assert_eq!(
12735            editor.text(cx),
12736            "
12737                a
12738                b
12739                c
12740            "
12741            .unindent()
12742        );
12743        assert_eq!(
12744            display_ranges(editor, cx),
12745            [
12746                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12747                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12748                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12749            ]
12750        );
12751
12752        // Ensure inserting the last character of a multi-byte bracket pair
12753        // doesn't surround the selections with the bracket.
12754        editor.handle_input("*", window, cx);
12755        assert_eq!(
12756            editor.text(cx),
12757            "
12758                *
12759                *
12760                *
12761            "
12762            .unindent()
12763        );
12764        assert_eq!(
12765            display_ranges(editor, cx),
12766            [
12767                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12768                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12769                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12770            ]
12771        );
12772    });
12773}
12774
12775#[gpui::test]
12776async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
12777    init_test(cx, |_| {});
12778
12779    let language = Arc::new(Language::new(
12780        LanguageConfig {
12781            brackets: BracketPairConfig {
12782                pairs: vec![BracketPair {
12783                    start: "{".to_string(),
12784                    end: "}".to_string(),
12785                    close: true,
12786                    surround: true,
12787                    newline: true,
12788                }],
12789                ..Default::default()
12790            },
12791            autoclose_before: "}".to_string(),
12792            ..Default::default()
12793        },
12794        Some(tree_sitter_rust::LANGUAGE.into()),
12795    ));
12796
12797    let text = r#"
12798        a
12799        b
12800        c
12801    "#
12802    .unindent();
12803
12804    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12805    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12806    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12807    editor
12808        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12809        .await;
12810
12811    editor.update_in(cx, |editor, window, cx| {
12812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12813            s.select_ranges([
12814                Point::new(0, 1)..Point::new(0, 1),
12815                Point::new(1, 1)..Point::new(1, 1),
12816                Point::new(2, 1)..Point::new(2, 1),
12817            ])
12818        });
12819
12820        editor.handle_input("{", window, cx);
12821        editor.handle_input("{", window, cx);
12822        editor.handle_input("_", window, cx);
12823        assert_eq!(
12824            editor.text(cx),
12825            "
12826                a{{_}}
12827                b{{_}}
12828                c{{_}}
12829            "
12830            .unindent()
12831        );
12832        assert_eq!(
12833            editor
12834                .selections
12835                .ranges::<Point>(&editor.display_snapshot(cx)),
12836            [
12837                Point::new(0, 4)..Point::new(0, 4),
12838                Point::new(1, 4)..Point::new(1, 4),
12839                Point::new(2, 4)..Point::new(2, 4)
12840            ]
12841        );
12842
12843        editor.backspace(&Default::default(), window, cx);
12844        editor.backspace(&Default::default(), window, cx);
12845        assert_eq!(
12846            editor.text(cx),
12847            "
12848                a{}
12849                b{}
12850                c{}
12851            "
12852            .unindent()
12853        );
12854        assert_eq!(
12855            editor
12856                .selections
12857                .ranges::<Point>(&editor.display_snapshot(cx)),
12858            [
12859                Point::new(0, 2)..Point::new(0, 2),
12860                Point::new(1, 2)..Point::new(1, 2),
12861                Point::new(2, 2)..Point::new(2, 2)
12862            ]
12863        );
12864
12865        editor.delete_to_previous_word_start(&Default::default(), window, cx);
12866        assert_eq!(
12867            editor.text(cx),
12868            "
12869                a
12870                b
12871                c
12872            "
12873            .unindent()
12874        );
12875        assert_eq!(
12876            editor
12877                .selections
12878                .ranges::<Point>(&editor.display_snapshot(cx)),
12879            [
12880                Point::new(0, 1)..Point::new(0, 1),
12881                Point::new(1, 1)..Point::new(1, 1),
12882                Point::new(2, 1)..Point::new(2, 1)
12883            ]
12884        );
12885    });
12886}
12887
12888#[gpui::test]
12889async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
12890    init_test(cx, |settings| {
12891        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
12892    });
12893
12894    let mut cx = EditorTestContext::new(cx).await;
12895
12896    let language = Arc::new(Language::new(
12897        LanguageConfig {
12898            brackets: BracketPairConfig {
12899                pairs: vec![
12900                    BracketPair {
12901                        start: "{".to_string(),
12902                        end: "}".to_string(),
12903                        close: true,
12904                        surround: true,
12905                        newline: true,
12906                    },
12907                    BracketPair {
12908                        start: "(".to_string(),
12909                        end: ")".to_string(),
12910                        close: true,
12911                        surround: true,
12912                        newline: true,
12913                    },
12914                    BracketPair {
12915                        start: "[".to_string(),
12916                        end: "]".to_string(),
12917                        close: false,
12918                        surround: true,
12919                        newline: true,
12920                    },
12921                ],
12922                ..Default::default()
12923            },
12924            autoclose_before: "})]".to_string(),
12925            ..Default::default()
12926        },
12927        Some(tree_sitter_rust::LANGUAGE.into()),
12928    ));
12929
12930    cx.language_registry().add(language.clone());
12931    cx.update_buffer(|buffer, cx| {
12932        buffer.set_language(Some(language), cx);
12933    });
12934
12935    cx.set_state(
12936        &"
12937            {(ˇ)}
12938            [[ˇ]]
12939            {(ˇ)}
12940        "
12941        .unindent(),
12942    );
12943
12944    cx.update_editor(|editor, window, cx| {
12945        editor.backspace(&Default::default(), window, cx);
12946        editor.backspace(&Default::default(), window, cx);
12947    });
12948
12949    cx.assert_editor_state(
12950        &"
12951            ˇ
12952            ˇ]]
12953            ˇ
12954        "
12955        .unindent(),
12956    );
12957
12958    cx.update_editor(|editor, window, cx| {
12959        editor.handle_input("{", window, cx);
12960        editor.handle_input("{", window, cx);
12961        editor.move_right(&MoveRight, window, cx);
12962        editor.move_right(&MoveRight, window, cx);
12963        editor.move_left(&MoveLeft, window, cx);
12964        editor.move_left(&MoveLeft, window, cx);
12965        editor.backspace(&Default::default(), window, cx);
12966    });
12967
12968    cx.assert_editor_state(
12969        &"
12970            {ˇ}
12971            {ˇ}]]
12972            {ˇ}
12973        "
12974        .unindent(),
12975    );
12976
12977    cx.update_editor(|editor, window, cx| {
12978        editor.backspace(&Default::default(), window, cx);
12979    });
12980
12981    cx.assert_editor_state(
12982        &"
12983            ˇ
12984            ˇ]]
12985            ˇ
12986        "
12987        .unindent(),
12988    );
12989}
12990
12991#[gpui::test]
12992async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
12993    init_test(cx, |_| {});
12994
12995    let language = Arc::new(Language::new(
12996        LanguageConfig::default(),
12997        Some(tree_sitter_rust::LANGUAGE.into()),
12998    ));
12999
13000    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
13001    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13002    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13003    editor
13004        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
13005        .await;
13006
13007    editor.update_in(cx, |editor, window, cx| {
13008        editor.set_auto_replace_emoji_shortcode(true);
13009
13010        editor.handle_input("Hello ", window, cx);
13011        editor.handle_input(":wave", window, cx);
13012        assert_eq!(editor.text(cx), "Hello :wave".unindent());
13013
13014        editor.handle_input(":", window, cx);
13015        assert_eq!(editor.text(cx), "Hello 👋".unindent());
13016
13017        editor.handle_input(" :smile", window, cx);
13018        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
13019
13020        editor.handle_input(":", window, cx);
13021        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
13022
13023        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
13024        editor.handle_input(":wave", window, cx);
13025        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
13026
13027        editor.handle_input(":", window, cx);
13028        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
13029
13030        editor.handle_input(":1", window, cx);
13031        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
13032
13033        editor.handle_input(":", window, cx);
13034        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
13035
13036        // Ensure shortcode does not get replaced when it is part of a word
13037        editor.handle_input(" Test:wave", window, cx);
13038        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
13039
13040        editor.handle_input(":", window, cx);
13041        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
13042
13043        editor.set_auto_replace_emoji_shortcode(false);
13044
13045        // Ensure shortcode does not get replaced when auto replace is off
13046        editor.handle_input(" :wave", window, cx);
13047        assert_eq!(
13048            editor.text(cx),
13049            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
13050        );
13051
13052        editor.handle_input(":", window, cx);
13053        assert_eq!(
13054            editor.text(cx),
13055            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
13056        );
13057    });
13058}
13059
13060#[gpui::test]
13061async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
13062    init_test(cx, |_| {});
13063
13064    let (text, insertion_ranges) = marked_text_ranges(
13065        indoc! {"
13066            ˇ
13067        "},
13068        false,
13069    );
13070
13071    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
13072    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13073
13074    _ = editor.update_in(cx, |editor, window, cx| {
13075        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
13076
13077        editor
13078            .insert_snippet(
13079                &insertion_ranges
13080                    .iter()
13081                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
13082                    .collect::<Vec<_>>(),
13083                snippet,
13084                window,
13085                cx,
13086            )
13087            .unwrap();
13088
13089        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
13090            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
13091            assert_eq!(editor.text(cx), expected_text);
13092            assert_eq!(
13093                editor.selections.ranges(&editor.display_snapshot(cx)),
13094                selection_ranges
13095                    .iter()
13096                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
13097                    .collect::<Vec<_>>()
13098            );
13099        }
13100
13101        assert(
13102            editor,
13103            cx,
13104            indoc! {"
13105            type «» =•
13106            "},
13107        );
13108
13109        assert!(editor.context_menu_visible(), "There should be a matches");
13110    });
13111}
13112
13113#[gpui::test]
13114async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
13115    init_test(cx, |_| {});
13116
13117    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
13118        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
13119        assert_eq!(editor.text(cx), expected_text);
13120        assert_eq!(
13121            editor.selections.ranges(&editor.display_snapshot(cx)),
13122            selection_ranges
13123                .iter()
13124                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
13125                .collect::<Vec<_>>()
13126        );
13127    }
13128
13129    let (text, insertion_ranges) = marked_text_ranges(
13130        indoc! {"
13131            ˇ
13132        "},
13133        false,
13134    );
13135
13136    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
13137    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13138
13139    _ = editor.update_in(cx, |editor, window, cx| {
13140        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
13141
13142        editor
13143            .insert_snippet(
13144                &insertion_ranges
13145                    .iter()
13146                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
13147                    .collect::<Vec<_>>(),
13148                snippet,
13149                window,
13150                cx,
13151            )
13152            .unwrap();
13153
13154        assert_state(
13155            editor,
13156            cx,
13157            indoc! {"
13158            type «» = ;•
13159            "},
13160        );
13161
13162        assert!(
13163            editor.context_menu_visible(),
13164            "Context menu should be visible for placeholder choices"
13165        );
13166
13167        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
13168
13169        assert_state(
13170            editor,
13171            cx,
13172            indoc! {"
13173            type  = «»;•
13174            "},
13175        );
13176
13177        assert!(
13178            !editor.context_menu_visible(),
13179            "Context menu should be hidden after moving to next tabstop"
13180        );
13181
13182        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
13183
13184        assert_state(
13185            editor,
13186            cx,
13187            indoc! {"
13188            type  = ; ˇ
13189            "},
13190        );
13191
13192        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
13193
13194        assert_state(
13195            editor,
13196            cx,
13197            indoc! {"
13198            type  = ; ˇ
13199            "},
13200        );
13201    });
13202
13203    _ = editor.update_in(cx, |editor, window, cx| {
13204        editor.select_all(&SelectAll, window, cx);
13205        editor.backspace(&Backspace, window, cx);
13206
13207        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
13208        let insertion_ranges = editor
13209            .selections
13210            .all(&editor.display_snapshot(cx))
13211            .iter()
13212            .map(|s| s.range())
13213            .collect::<Vec<_>>();
13214
13215        editor
13216            .insert_snippet(&insertion_ranges, snippet, window, cx)
13217            .unwrap();
13218
13219        assert_state(editor, cx, "fn «» = value;•");
13220
13221        assert!(
13222            editor.context_menu_visible(),
13223            "Context menu should be visible for placeholder choices"
13224        );
13225
13226        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
13227
13228        assert_state(editor, cx, "fn  = «valueˇ»;•");
13229
13230        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
13231
13232        assert_state(editor, cx, "fn «» = value;•");
13233
13234        assert!(
13235            editor.context_menu_visible(),
13236            "Context menu should be visible again after returning to first tabstop"
13237        );
13238
13239        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
13240
13241        assert_state(editor, cx, "fn «» = value;•");
13242    });
13243}
13244
13245#[gpui::test]
13246async fn test_snippets(cx: &mut TestAppContext) {
13247    init_test(cx, |_| {});
13248
13249    let mut cx = EditorTestContext::new(cx).await;
13250
13251    cx.set_state(indoc! {"
13252        a.ˇ b
13253        a.ˇ b
13254        a.ˇ b
13255    "});
13256
13257    cx.update_editor(|editor, window, cx| {
13258        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
13259        let insertion_ranges = editor
13260            .selections
13261            .all(&editor.display_snapshot(cx))
13262            .iter()
13263            .map(|s| s.range())
13264            .collect::<Vec<_>>();
13265        editor
13266            .insert_snippet(&insertion_ranges, snippet, window, cx)
13267            .unwrap();
13268    });
13269
13270    cx.assert_editor_state(indoc! {"
13271        a.f(«oneˇ», two, «threeˇ») b
13272        a.f(«oneˇ», two, «threeˇ») b
13273        a.f(«oneˇ», two, «threeˇ») b
13274    "});
13275
13276    // Can't move earlier than the first tab stop
13277    cx.update_editor(|editor, window, cx| {
13278        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
13279    });
13280    cx.assert_editor_state(indoc! {"
13281        a.f(«oneˇ», two, «threeˇ») b
13282        a.f(«oneˇ», two, «threeˇ») b
13283        a.f(«oneˇ», two, «threeˇ») b
13284    "});
13285
13286    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
13287    cx.assert_editor_state(indoc! {"
13288        a.f(one, «twoˇ», three) b
13289        a.f(one, «twoˇ», three) b
13290        a.f(one, «twoˇ», three) b
13291    "});
13292
13293    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
13294    cx.assert_editor_state(indoc! {"
13295        a.f(«oneˇ», two, «threeˇ») b
13296        a.f(«oneˇ», two, «threeˇ») b
13297        a.f(«oneˇ», two, «threeˇ») b
13298    "});
13299
13300    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
13301    cx.assert_editor_state(indoc! {"
13302        a.f(one, «twoˇ», three) b
13303        a.f(one, «twoˇ», three) b
13304        a.f(one, «twoˇ», three) b
13305    "});
13306    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
13307    cx.assert_editor_state(indoc! {"
13308        a.f(one, two, three)ˇ b
13309        a.f(one, two, three)ˇ b
13310        a.f(one, two, three)ˇ b
13311    "});
13312
13313    // As soon as the last tab stop is reached, snippet state is gone
13314    cx.update_editor(|editor, window, cx| {
13315        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
13316    });
13317    cx.assert_editor_state(indoc! {"
13318        a.f(one, two, three)ˇ b
13319        a.f(one, two, three)ˇ b
13320        a.f(one, two, three)ˇ b
13321    "});
13322}
13323
13324#[gpui::test]
13325async fn test_snippet_indentation(cx: &mut TestAppContext) {
13326    init_test(cx, |_| {});
13327
13328    let mut cx = EditorTestContext::new(cx).await;
13329
13330    cx.update_editor(|editor, window, cx| {
13331        let snippet = Snippet::parse(indoc! {"
13332            /*
13333             * Multiline comment with leading indentation
13334             *
13335             * $1
13336             */
13337            $0"})
13338        .unwrap();
13339        let insertion_ranges = editor
13340            .selections
13341            .all(&editor.display_snapshot(cx))
13342            .iter()
13343            .map(|s| s.range())
13344            .collect::<Vec<_>>();
13345        editor
13346            .insert_snippet(&insertion_ranges, snippet, window, cx)
13347            .unwrap();
13348    });
13349
13350    cx.assert_editor_state(indoc! {"
13351        /*
13352         * Multiline comment with leading indentation
13353         *
13354         * ˇ
13355         */
13356    "});
13357
13358    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
13359    cx.assert_editor_state(indoc! {"
13360        /*
13361         * Multiline comment with leading indentation
13362         *
13363         *•
13364         */
13365        ˇ"});
13366}
13367
13368#[gpui::test]
13369async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
13370    init_test(cx, |_| {});
13371
13372    let mut cx = EditorTestContext::new(cx).await;
13373    cx.update_editor(|editor, _, cx| {
13374        editor.project().unwrap().update(cx, |project, cx| {
13375            project.snippets().update(cx, |snippets, _cx| {
13376                let snippet = project::snippet_provider::Snippet {
13377                    prefix: vec!["multi word".to_string()],
13378                    body: "this is many words".to_string(),
13379                    description: Some("description".to_string()),
13380                    name: "multi-word snippet test".to_string(),
13381                };
13382                snippets.add_snippet_for_test(
13383                    None,
13384                    PathBuf::from("test_snippets.json"),
13385                    vec![Arc::new(snippet)],
13386                );
13387            });
13388        })
13389    });
13390
13391    for (input_to_simulate, should_match_snippet) in [
13392        ("m", true),
13393        ("m ", true),
13394        ("m w", true),
13395        ("aa m w", true),
13396        ("aa m g", false),
13397    ] {
13398        cx.set_state("ˇ");
13399        cx.simulate_input(input_to_simulate); // fails correctly
13400
13401        cx.update_editor(|editor, _, _| {
13402            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
13403            else {
13404                assert!(!should_match_snippet); // no completions! don't even show the menu
13405                return;
13406            };
13407            assert!(context_menu.visible());
13408            let completions = context_menu.completions.borrow();
13409
13410            assert_eq!(!completions.is_empty(), should_match_snippet);
13411        });
13412    }
13413}
13414
13415#[gpui::test]
13416async fn test_document_format_during_save(cx: &mut TestAppContext) {
13417    init_test(cx, |_| {});
13418
13419    let fs = FakeFs::new(cx.executor());
13420    fs.insert_file(path!("/file.rs"), Default::default()).await;
13421
13422    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
13423
13424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13425    language_registry.add(rust_lang());
13426    let mut fake_servers = language_registry.register_fake_lsp(
13427        "Rust",
13428        FakeLspAdapter {
13429            capabilities: lsp::ServerCapabilities {
13430                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13431                ..Default::default()
13432            },
13433            ..Default::default()
13434        },
13435    );
13436
13437    let buffer = project
13438        .update(cx, |project, cx| {
13439            project.open_local_buffer(path!("/file.rs"), cx)
13440        })
13441        .await
13442        .unwrap();
13443
13444    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13445    let (editor, cx) = cx.add_window_view(|window, cx| {
13446        build_editor_with_project(project.clone(), buffer, window, cx)
13447    });
13448    editor.update_in(cx, |editor, window, cx| {
13449        editor.set_text("one\ntwo\nthree\n", window, cx)
13450    });
13451    assert!(cx.read(|cx| editor.is_dirty(cx)));
13452
13453    let fake_server = fake_servers.next().await.unwrap();
13454
13455    {
13456        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13457            move |params, _| async move {
13458                assert_eq!(
13459                    params.text_document.uri,
13460                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13461                );
13462                assert_eq!(params.options.tab_size, 4);
13463                Ok(Some(vec![lsp::TextEdit::new(
13464                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13465                    ", ".to_string(),
13466                )]))
13467            },
13468        );
13469        let save = editor
13470            .update_in(cx, |editor, window, cx| {
13471                editor.save(
13472                    SaveOptions {
13473                        format: true,
13474                        autosave: false,
13475                    },
13476                    project.clone(),
13477                    window,
13478                    cx,
13479                )
13480            })
13481            .unwrap();
13482        save.await;
13483
13484        assert_eq!(
13485            editor.update(cx, |editor, cx| editor.text(cx)),
13486            "one, two\nthree\n"
13487        );
13488        assert!(!cx.read(|cx| editor.is_dirty(cx)));
13489    }
13490
13491    {
13492        editor.update_in(cx, |editor, window, cx| {
13493            editor.set_text("one\ntwo\nthree\n", window, cx)
13494        });
13495        assert!(cx.read(|cx| editor.is_dirty(cx)));
13496
13497        // Ensure we can still save even if formatting hangs.
13498        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13499            move |params, _| async move {
13500                assert_eq!(
13501                    params.text_document.uri,
13502                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13503                );
13504                futures::future::pending::<()>().await;
13505                unreachable!()
13506            },
13507        );
13508        let save = editor
13509            .update_in(cx, |editor, window, cx| {
13510                editor.save(
13511                    SaveOptions {
13512                        format: true,
13513                        autosave: false,
13514                    },
13515                    project.clone(),
13516                    window,
13517                    cx,
13518                )
13519            })
13520            .unwrap();
13521        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13522        save.await;
13523        assert_eq!(
13524            editor.update(cx, |editor, cx| editor.text(cx)),
13525            "one\ntwo\nthree\n"
13526        );
13527    }
13528
13529    // Set rust language override and assert overridden tabsize is sent to language server
13530    update_test_language_settings(cx, &|settings| {
13531        settings.languages.0.insert(
13532            "Rust".into(),
13533            LanguageSettingsContent {
13534                tab_size: NonZeroU32::new(8),
13535                ..Default::default()
13536            },
13537        );
13538    });
13539
13540    {
13541        editor.update_in(cx, |editor, window, cx| {
13542            editor.set_text("somehting_new\n", window, cx)
13543        });
13544        assert!(cx.read(|cx| editor.is_dirty(cx)));
13545        let _formatting_request_signal = fake_server
13546            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13547                assert_eq!(
13548                    params.text_document.uri,
13549                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13550                );
13551                assert_eq!(params.options.tab_size, 8);
13552                Ok(Some(vec![]))
13553            });
13554        let save = editor
13555            .update_in(cx, |editor, window, cx| {
13556                editor.save(
13557                    SaveOptions {
13558                        format: true,
13559                        autosave: false,
13560                    },
13561                    project.clone(),
13562                    window,
13563                    cx,
13564                )
13565            })
13566            .unwrap();
13567        save.await;
13568    }
13569}
13570
13571#[gpui::test]
13572async fn test_auto_formatter_skips_server_without_formatting(cx: &mut TestAppContext) {
13573    init_test(cx, |_| {});
13574
13575    let fs = FakeFs::new(cx.executor());
13576    fs.insert_file(path!("/file.rs"), Default::default()).await;
13577
13578    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
13579
13580    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13581    language_registry.add(rust_lang());
13582
13583    // First server: no formatting capability
13584    let mut no_format_servers = language_registry.register_fake_lsp(
13585        "Rust",
13586        FakeLspAdapter {
13587            name: "no-format-server",
13588            capabilities: lsp::ServerCapabilities {
13589                completion_provider: Some(lsp::CompletionOptions::default()),
13590                ..Default::default()
13591            },
13592            ..Default::default()
13593        },
13594    );
13595
13596    // Second server: has formatting capability
13597    let mut format_servers = language_registry.register_fake_lsp(
13598        "Rust",
13599        FakeLspAdapter {
13600            name: "format-server",
13601            capabilities: lsp::ServerCapabilities {
13602                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13603                ..Default::default()
13604            },
13605            ..Default::default()
13606        },
13607    );
13608
13609    let buffer = project
13610        .update(cx, |project, cx| {
13611            project.open_local_buffer(path!("/file.rs"), cx)
13612        })
13613        .await
13614        .unwrap();
13615
13616    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13617    let (editor, cx) = cx.add_window_view(|window, cx| {
13618        build_editor_with_project(project.clone(), buffer, window, cx)
13619    });
13620    editor.update_in(cx, |editor, window, cx| {
13621        editor.set_text("one\ntwo\nthree\n", window, cx)
13622    });
13623
13624    let _no_format_server = no_format_servers.next().await.unwrap();
13625    let format_server = format_servers.next().await.unwrap();
13626
13627    format_server.set_request_handler::<lsp::request::Formatting, _, _>(
13628        move |params, _| async move {
13629            assert_eq!(
13630                params.text_document.uri,
13631                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13632            );
13633            Ok(Some(vec![lsp::TextEdit::new(
13634                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13635                ", ".to_string(),
13636            )]))
13637        },
13638    );
13639
13640    let save = editor
13641        .update_in(cx, |editor, window, cx| {
13642            editor.save(
13643                SaveOptions {
13644                    format: true,
13645                    autosave: false,
13646                },
13647                project.clone(),
13648                window,
13649                cx,
13650            )
13651        })
13652        .unwrap();
13653    save.await;
13654
13655    assert_eq!(
13656        editor.update(cx, |editor, cx| editor.text(cx)),
13657        "one, two\nthree\n"
13658    );
13659}
13660
13661#[gpui::test]
13662async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
13663    init_test(cx, |settings| {
13664        settings.defaults.ensure_final_newline_on_save = Some(false);
13665    });
13666
13667    let fs = FakeFs::new(cx.executor());
13668    fs.insert_file(path!("/file.txt"), "foo".into()).await;
13669
13670    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
13671
13672    let buffer = project
13673        .update(cx, |project, cx| {
13674            project.open_local_buffer(path!("/file.txt"), cx)
13675        })
13676        .await
13677        .unwrap();
13678
13679    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13680    let (editor, cx) = cx.add_window_view(|window, cx| {
13681        build_editor_with_project(project.clone(), buffer, window, cx)
13682    });
13683    editor.update_in(cx, |editor, window, cx| {
13684        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
13685            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13686        });
13687    });
13688    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13689
13690    editor.update_in(cx, |editor, window, cx| {
13691        editor.handle_input("\n", window, cx)
13692    });
13693    cx.run_until_parked();
13694    save(&editor, &project, cx).await;
13695    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13696
13697    editor.update_in(cx, |editor, window, cx| {
13698        editor.undo(&Default::default(), window, cx);
13699    });
13700    save(&editor, &project, cx).await;
13701    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13702
13703    editor.update_in(cx, |editor, window, cx| {
13704        editor.redo(&Default::default(), window, cx);
13705    });
13706    cx.run_until_parked();
13707    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13708
13709    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
13710        let save = editor
13711            .update_in(cx, |editor, window, cx| {
13712                editor.save(
13713                    SaveOptions {
13714                        format: true,
13715                        autosave: false,
13716                    },
13717                    project.clone(),
13718                    window,
13719                    cx,
13720                )
13721            })
13722            .unwrap();
13723        save.await;
13724        assert!(!cx.read(|cx| editor.is_dirty(cx)));
13725    }
13726}
13727
13728#[gpui::test]
13729async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
13730    init_test(cx, |_| {});
13731
13732    let cols = 4;
13733    let rows = 10;
13734    let sample_text_1 = sample_text(rows, cols, 'a');
13735    assert_eq!(
13736        sample_text_1,
13737        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13738    );
13739    let sample_text_2 = sample_text(rows, cols, 'l');
13740    assert_eq!(
13741        sample_text_2,
13742        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13743    );
13744    let sample_text_3 = sample_text(rows, cols, 'v').replace('\u{7f}', ".");
13745    assert_eq!(
13746        sample_text_3,
13747        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n...."
13748    );
13749
13750    let fs = FakeFs::new(cx.executor());
13751    fs.insert_tree(
13752        path!("/a"),
13753        json!({
13754            "main.rs": sample_text_1,
13755            "other.rs": sample_text_2,
13756            "lib.rs": sample_text_3,
13757        }),
13758    )
13759    .await;
13760
13761    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13762    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
13763    let cx = &mut VisualTestContext::from_window(*window, cx);
13764
13765    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13766    language_registry.add(rust_lang());
13767    let mut fake_servers = language_registry.register_fake_lsp(
13768        "Rust",
13769        FakeLspAdapter {
13770            capabilities: lsp::ServerCapabilities {
13771                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13772                ..Default::default()
13773            },
13774            ..Default::default()
13775        },
13776    );
13777
13778    let worktree = project.update(cx, |project, cx| {
13779        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
13780        assert_eq!(worktrees.len(), 1);
13781        worktrees.pop().unwrap()
13782    });
13783    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13784
13785    let buffer_1 = project
13786        .update(cx, |project, cx| {
13787            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
13788        })
13789        .await
13790        .unwrap();
13791    let buffer_2 = project
13792        .update(cx, |project, cx| {
13793            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
13794        })
13795        .await
13796        .unwrap();
13797    let buffer_3 = project
13798        .update(cx, |project, cx| {
13799            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
13800        })
13801        .await
13802        .unwrap();
13803
13804    let multi_buffer = cx.new(|cx| {
13805        let mut multi_buffer = MultiBuffer::new(ReadWrite);
13806        multi_buffer.set_excerpts_for_path(
13807            PathKey::sorted(0),
13808            buffer_1.clone(),
13809            [
13810                Point::new(0, 0)..Point::new(2, 4),
13811                Point::new(5, 0)..Point::new(6, 4),
13812                Point::new(9, 0)..Point::new(9, 4),
13813            ],
13814            0,
13815            cx,
13816        );
13817        multi_buffer.set_excerpts_for_path(
13818            PathKey::sorted(1),
13819            buffer_2.clone(),
13820            [
13821                Point::new(0, 0)..Point::new(2, 4),
13822                Point::new(5, 0)..Point::new(6, 4),
13823                Point::new(9, 0)..Point::new(9, 4),
13824            ],
13825            0,
13826            cx,
13827        );
13828        multi_buffer.set_excerpts_for_path(
13829            PathKey::sorted(2),
13830            buffer_3.clone(),
13831            [
13832                Point::new(0, 0)..Point::new(2, 4),
13833                Point::new(5, 0)..Point::new(6, 4),
13834                Point::new(9, 0)..Point::new(9, 4),
13835            ],
13836            0,
13837            cx,
13838        );
13839        assert_eq!(multi_buffer.read(cx).excerpts().count(), 9);
13840        multi_buffer
13841    });
13842    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13843        Editor::new(
13844            EditorMode::full(),
13845            multi_buffer,
13846            Some(project.clone()),
13847            window,
13848            cx,
13849        )
13850    });
13851
13852    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13853        let a = editor.text(cx).find("aaaa").unwrap();
13854        editor.change_selections(
13855            SelectionEffects::scroll(Autoscroll::Next),
13856            window,
13857            cx,
13858            |s| s.select_ranges(Some(MultiBufferOffset(a + 1)..MultiBufferOffset(a + 2))),
13859        );
13860        editor.insert("|one|two|three|", window, cx);
13861    });
13862    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13863    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13864        let n = editor.text(cx).find("nnnn").unwrap();
13865        editor.change_selections(
13866            SelectionEffects::scroll(Autoscroll::Next),
13867            window,
13868            cx,
13869            |s| s.select_ranges(Some(MultiBufferOffset(n + 4)..MultiBufferOffset(n + 14))),
13870        );
13871        editor.insert("|four|five|six|", window, cx);
13872    });
13873    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13874
13875    // First two buffers should be edited, but not the third one.
13876    pretty_assertions::assert_eq!(
13877        editor_content_with_blocks(&multi_buffer_editor, cx),
13878        indoc! {"
13879            § main.rs
13880            § -----
13881            a|one|two|three|aa
13882            bbbb
13883            cccc
13884            § -----
13885            ffff
13886            gggg
13887            § -----
13888            jjjj
13889            § other.rs
13890            § -----
13891            llll
13892            mmmm
13893            nnnn|four|five|six|
13894            § -----
13895
13896            § -----
13897            uuuu
13898            § lib.rs
13899            § -----
13900            vvvv
13901            wwww
13902            xxxx
13903            § -----
13904            {{{{
13905            ||||
13906            § -----
13907            ...."}
13908    );
13909    buffer_1.update(cx, |buffer, _| {
13910        assert!(buffer.is_dirty());
13911        assert_eq!(
13912            buffer.text(),
13913            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
13914        )
13915    });
13916    buffer_2.update(cx, |buffer, _| {
13917        assert!(buffer.is_dirty());
13918        assert_eq!(
13919            buffer.text(),
13920            "llll\nmmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu",
13921        )
13922    });
13923    buffer_3.update(cx, |buffer, _| {
13924        assert!(!buffer.is_dirty());
13925        assert_eq!(buffer.text(), sample_text_3,)
13926    });
13927    cx.executor().run_until_parked();
13928
13929    let save = multi_buffer_editor
13930        .update_in(cx, |editor, window, cx| {
13931            editor.save(
13932                SaveOptions {
13933                    format: true,
13934                    autosave: false,
13935                },
13936                project.clone(),
13937                window,
13938                cx,
13939            )
13940        })
13941        .unwrap();
13942
13943    let fake_server = fake_servers.next().await.unwrap();
13944    fake_server
13945        .server
13946        .on_request::<lsp::request::Formatting, _, _>(move |_params, _| async move {
13947            Ok(Some(vec![lsp::TextEdit::new(
13948                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13949                "[formatted]".to_string(),
13950            )]))
13951        })
13952        .detach();
13953    save.await;
13954
13955    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
13956    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
13957    assert_eq!(
13958        editor_content_with_blocks(&multi_buffer_editor, cx),
13959        indoc! {"
13960            § main.rs
13961            § -----
13962            a|o[formatted]bbbb
13963            cccc
13964            § -----
13965            ffff
13966            gggg
13967            § -----
13968            jjjj
13969
13970            § other.rs
13971            § -----
13972            lll[formatted]mmmm
13973            nnnn|four|five|six|
13974            § -----
13975
13976            § -----
13977            uuuu
13978
13979            § lib.rs
13980            § -----
13981            vvvv
13982            wwww
13983            xxxx
13984            § -----
13985            {{{{
13986            ||||
13987            § -----
13988            ...."}
13989    );
13990    buffer_1.update(cx, |buffer, _| {
13991        assert!(!buffer.is_dirty());
13992        assert_eq!(
13993            buffer.text(),
13994            "a|o[formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
13995        )
13996    });
13997    // Diff < left / right > :
13998    //  lll[formatted]mmmm
13999    // <nnnn|four|five|six|
14000    // <oooo
14001    // >nnnn|four|five|six|oooo
14002    //  pppp
14003    // <
14004    //  ssss
14005    //  tttt
14006    //  uuuu
14007
14008    buffer_2.update(cx, |buffer, _| {
14009        assert!(!buffer.is_dirty());
14010        assert_eq!(
14011            buffer.text(),
14012            "lll[formatted]mmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu\n",
14013        )
14014    });
14015    buffer_3.update(cx, |buffer, _| {
14016        assert!(!buffer.is_dirty());
14017        assert_eq!(buffer.text(), sample_text_3,)
14018    });
14019}
14020
14021#[gpui::test]
14022async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
14023    init_test(cx, |_| {});
14024
14025    let fs = FakeFs::new(cx.executor());
14026    fs.insert_tree(
14027        path!("/dir"),
14028        json!({
14029            "file1.rs": "fn main() { println!(\"hello\"); }",
14030            "file2.rs": "fn test() { println!(\"test\"); }",
14031            "file3.rs": "fn other() { println!(\"other\"); }\n",
14032        }),
14033    )
14034    .await;
14035
14036    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
14037    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
14038    let cx = &mut VisualTestContext::from_window(*window, cx);
14039
14040    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14041    language_registry.add(rust_lang());
14042
14043    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
14044    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14045
14046    // Open three buffers
14047    let buffer_1 = project
14048        .update(cx, |project, cx| {
14049            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
14050        })
14051        .await
14052        .unwrap();
14053    let buffer_2 = project
14054        .update(cx, |project, cx| {
14055            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
14056        })
14057        .await
14058        .unwrap();
14059    let buffer_3 = project
14060        .update(cx, |project, cx| {
14061            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
14062        })
14063        .await
14064        .unwrap();
14065
14066    // Create a multi-buffer with all three buffers
14067    let multi_buffer = cx.new(|cx| {
14068        let mut multi_buffer = MultiBuffer::new(ReadWrite);
14069        multi_buffer.set_excerpts_for_path(
14070            PathKey::sorted(0),
14071            buffer_1.clone(),
14072            [Point::new(0, 0)..Point::new(1, 0)],
14073            0,
14074            cx,
14075        );
14076        multi_buffer.set_excerpts_for_path(
14077            PathKey::sorted(1),
14078            buffer_2.clone(),
14079            [Point::new(0, 0)..Point::new(1, 0)],
14080            0,
14081            cx,
14082        );
14083        multi_buffer.set_excerpts_for_path(
14084            PathKey::sorted(2),
14085            buffer_3.clone(),
14086            [Point::new(0, 0)..Point::new(1, 0)],
14087            0,
14088            cx,
14089        );
14090        multi_buffer
14091    });
14092
14093    let editor = cx.new_window_entity(|window, cx| {
14094        Editor::new(
14095            EditorMode::full(),
14096            multi_buffer,
14097            Some(project.clone()),
14098            window,
14099            cx,
14100        )
14101    });
14102
14103    // Edit only the first buffer
14104    editor.update_in(cx, |editor, window, cx| {
14105        editor.change_selections(
14106            SelectionEffects::scroll(Autoscroll::Next),
14107            window,
14108            cx,
14109            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
14110        );
14111        editor.insert("// edited", window, cx);
14112    });
14113
14114    // Verify that only buffer 1 is dirty
14115    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
14116    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
14117    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
14118
14119    // Get write counts after file creation (files were created with initial content)
14120    // We expect each file to have been written once during creation
14121    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
14122    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
14123    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
14124
14125    // Perform autosave
14126    let save_task = editor.update_in(cx, |editor, window, cx| {
14127        editor.save(
14128            SaveOptions {
14129                format: true,
14130                autosave: true,
14131            },
14132            project.clone(),
14133            window,
14134            cx,
14135        )
14136    });
14137    save_task.await.unwrap();
14138
14139    // Only the dirty buffer should have been saved
14140    assert_eq!(
14141        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
14142        1,
14143        "Buffer 1 was dirty, so it should have been written once during autosave"
14144    );
14145    assert_eq!(
14146        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
14147        0,
14148        "Buffer 2 was clean, so it should not have been written during autosave"
14149    );
14150    assert_eq!(
14151        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
14152        0,
14153        "Buffer 3 was clean, so it should not have been written during autosave"
14154    );
14155
14156    // Verify buffer states after autosave
14157    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
14158    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
14159    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
14160
14161    // Now perform a manual save (format = true)
14162    let save_task = editor.update_in(cx, |editor, window, cx| {
14163        editor.save(
14164            SaveOptions {
14165                format: true,
14166                autosave: false,
14167            },
14168            project.clone(),
14169            window,
14170            cx,
14171        )
14172    });
14173    save_task.await.unwrap();
14174
14175    // During manual save, clean buffers don't get written to disk
14176    // They just get did_save called for language server notifications
14177    assert_eq!(
14178        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
14179        1,
14180        "Buffer 1 should only have been written once total (during autosave, not manual save)"
14181    );
14182    assert_eq!(
14183        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
14184        0,
14185        "Buffer 2 should not have been written at all"
14186    );
14187    assert_eq!(
14188        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
14189        0,
14190        "Buffer 3 should not have been written at all"
14191    );
14192}
14193
14194async fn setup_range_format_test(
14195    cx: &mut TestAppContext,
14196) -> (
14197    Entity<Project>,
14198    Entity<Editor>,
14199    &mut gpui::VisualTestContext,
14200    lsp::FakeLanguageServer,
14201) {
14202    init_test(cx, |_| {});
14203
14204    let fs = FakeFs::new(cx.executor());
14205    fs.insert_file(path!("/file.rs"), Default::default()).await;
14206
14207    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14208
14209    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14210    language_registry.add(rust_lang());
14211    let mut fake_servers = language_registry.register_fake_lsp(
14212        "Rust",
14213        FakeLspAdapter {
14214            capabilities: lsp::ServerCapabilities {
14215                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
14216                ..lsp::ServerCapabilities::default()
14217            },
14218            ..FakeLspAdapter::default()
14219        },
14220    );
14221
14222    let buffer = project
14223        .update(cx, |project, cx| {
14224            project.open_local_buffer(path!("/file.rs"), cx)
14225        })
14226        .await
14227        .unwrap();
14228
14229    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14230    let (editor, cx) = cx.add_window_view(|window, cx| {
14231        build_editor_with_project(project.clone(), buffer, window, cx)
14232    });
14233
14234    let fake_server = fake_servers.next().await.unwrap();
14235
14236    (project, editor, cx, fake_server)
14237}
14238
14239#[gpui::test]
14240async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
14241    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
14242
14243    editor.update_in(cx, |editor, window, cx| {
14244        editor.set_text("one\ntwo\nthree\n", window, cx)
14245    });
14246    assert!(cx.read(|cx| editor.is_dirty(cx)));
14247
14248    let save = editor
14249        .update_in(cx, |editor, window, cx| {
14250            editor.save(
14251                SaveOptions {
14252                    format: true,
14253                    autosave: false,
14254                },
14255                project.clone(),
14256                window,
14257                cx,
14258            )
14259        })
14260        .unwrap();
14261    fake_server
14262        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
14263            assert_eq!(
14264                params.text_document.uri,
14265                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
14266            );
14267            assert_eq!(params.options.tab_size, 4);
14268            Ok(Some(vec![lsp::TextEdit::new(
14269                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
14270                ", ".to_string(),
14271            )]))
14272        })
14273        .next()
14274        .await;
14275    save.await;
14276    assert_eq!(
14277        editor.update(cx, |editor, cx| editor.text(cx)),
14278        "one, two\nthree\n"
14279    );
14280    assert!(!cx.read(|cx| editor.is_dirty(cx)));
14281}
14282
14283#[gpui::test]
14284async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
14285    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
14286
14287    editor.update_in(cx, |editor, window, cx| {
14288        editor.set_text("one\ntwo\nthree\n", window, cx)
14289    });
14290    assert!(cx.read(|cx| editor.is_dirty(cx)));
14291
14292    // Test that save still works when formatting hangs
14293    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
14294        move |params, _| async move {
14295            assert_eq!(
14296                params.text_document.uri,
14297                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
14298            );
14299            futures::future::pending::<()>().await;
14300            unreachable!()
14301        },
14302    );
14303    let save = editor
14304        .update_in(cx, |editor, window, cx| {
14305            editor.save(
14306                SaveOptions {
14307                    format: true,
14308                    autosave: false,
14309                },
14310                project.clone(),
14311                window,
14312                cx,
14313            )
14314        })
14315        .unwrap();
14316    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
14317    save.await;
14318    assert_eq!(
14319        editor.update(cx, |editor, cx| editor.text(cx)),
14320        "one\ntwo\nthree\n"
14321    );
14322    assert!(!cx.read(|cx| editor.is_dirty(cx)));
14323}
14324
14325#[gpui::test]
14326async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
14327    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
14328
14329    // Buffer starts clean, no formatting should be requested
14330    let save = editor
14331        .update_in(cx, |editor, window, cx| {
14332            editor.save(
14333                SaveOptions {
14334                    format: false,
14335                    autosave: false,
14336                },
14337                project.clone(),
14338                window,
14339                cx,
14340            )
14341        })
14342        .unwrap();
14343    let _pending_format_request = fake_server
14344        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
14345            panic!("Should not be invoked");
14346        })
14347        .next();
14348    save.await;
14349    cx.run_until_parked();
14350}
14351
14352#[gpui::test]
14353async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
14354    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
14355
14356    // Set Rust language override and assert overridden tabsize is sent to language server
14357    update_test_language_settings(cx, &|settings| {
14358        settings.languages.0.insert(
14359            "Rust".into(),
14360            LanguageSettingsContent {
14361                tab_size: NonZeroU32::new(8),
14362                ..Default::default()
14363            },
14364        );
14365    });
14366
14367    editor.update_in(cx, |editor, window, cx| {
14368        editor.set_text("something_new\n", window, cx)
14369    });
14370    assert!(cx.read(|cx| editor.is_dirty(cx)));
14371    let save = editor
14372        .update_in(cx, |editor, window, cx| {
14373            editor.save(
14374                SaveOptions {
14375                    format: true,
14376                    autosave: false,
14377                },
14378                project.clone(),
14379                window,
14380                cx,
14381            )
14382        })
14383        .unwrap();
14384    fake_server
14385        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
14386            assert_eq!(
14387                params.text_document.uri,
14388                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
14389            );
14390            assert_eq!(params.options.tab_size, 8);
14391            Ok(Some(Vec::new()))
14392        })
14393        .next()
14394        .await;
14395    save.await;
14396}
14397
14398#[gpui::test]
14399async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
14400    init_test(cx, |settings| {
14401        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
14402            settings::LanguageServerFormatterSpecifier::Current,
14403        )))
14404    });
14405
14406    let fs = FakeFs::new(cx.executor());
14407    fs.insert_file(path!("/file.rs"), Default::default()).await;
14408
14409    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14410
14411    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14412    language_registry.add(Arc::new(Language::new(
14413        LanguageConfig {
14414            name: "Rust".into(),
14415            matcher: LanguageMatcher {
14416                path_suffixes: vec!["rs".to_string()],
14417                ..Default::default()
14418            },
14419            ..LanguageConfig::default()
14420        },
14421        Some(tree_sitter_rust::LANGUAGE.into()),
14422    )));
14423    update_test_language_settings(cx, &|settings| {
14424        // Enable Prettier formatting for the same buffer, and ensure
14425        // LSP is called instead of Prettier.
14426        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
14427    });
14428    let mut fake_servers = language_registry.register_fake_lsp(
14429        "Rust",
14430        FakeLspAdapter {
14431            capabilities: lsp::ServerCapabilities {
14432                document_formatting_provider: Some(lsp::OneOf::Left(true)),
14433                ..Default::default()
14434            },
14435            ..Default::default()
14436        },
14437    );
14438
14439    let buffer = project
14440        .update(cx, |project, cx| {
14441            project.open_local_buffer(path!("/file.rs"), cx)
14442        })
14443        .await
14444        .unwrap();
14445
14446    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14447    let (editor, cx) = cx.add_window_view(|window, cx| {
14448        build_editor_with_project(project.clone(), buffer, window, cx)
14449    });
14450    editor.update_in(cx, |editor, window, cx| {
14451        editor.set_text("one\ntwo\nthree\n", window, cx)
14452    });
14453
14454    let fake_server = fake_servers.next().await.unwrap();
14455
14456    let format = editor
14457        .update_in(cx, |editor, window, cx| {
14458            editor.perform_format(
14459                project.clone(),
14460                FormatTrigger::Manual,
14461                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14462                window,
14463                cx,
14464            )
14465        })
14466        .unwrap();
14467    fake_server
14468        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
14469            assert_eq!(
14470                params.text_document.uri,
14471                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
14472            );
14473            assert_eq!(params.options.tab_size, 4);
14474            Ok(Some(vec![lsp::TextEdit::new(
14475                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
14476                ", ".to_string(),
14477            )]))
14478        })
14479        .next()
14480        .await;
14481    format.await;
14482    assert_eq!(
14483        editor.update(cx, |editor, cx| editor.text(cx)),
14484        "one, two\nthree\n"
14485    );
14486
14487    editor.update_in(cx, |editor, window, cx| {
14488        editor.set_text("one\ntwo\nthree\n", window, cx)
14489    });
14490    // Ensure we don't lock if formatting hangs.
14491    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
14492        move |params, _| async move {
14493            assert_eq!(
14494                params.text_document.uri,
14495                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
14496            );
14497            futures::future::pending::<()>().await;
14498            unreachable!()
14499        },
14500    );
14501    let format = editor
14502        .update_in(cx, |editor, window, cx| {
14503            editor.perform_format(
14504                project,
14505                FormatTrigger::Manual,
14506                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14507                window,
14508                cx,
14509            )
14510        })
14511        .unwrap();
14512    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
14513    format.await;
14514    assert_eq!(
14515        editor.update(cx, |editor, cx| editor.text(cx)),
14516        "one\ntwo\nthree\n"
14517    );
14518}
14519
14520#[gpui::test]
14521async fn test_multiple_formatters(cx: &mut TestAppContext) {
14522    init_test(cx, |settings| {
14523        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
14524        settings.defaults.formatter = Some(FormatterList::Vec(vec![
14525            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
14526            Formatter::CodeAction("code-action-1".into()),
14527            Formatter::CodeAction("code-action-2".into()),
14528        ]))
14529    });
14530
14531    let fs = FakeFs::new(cx.executor());
14532    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
14533        .await;
14534
14535    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14536    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14537    language_registry.add(rust_lang());
14538
14539    let mut fake_servers = language_registry.register_fake_lsp(
14540        "Rust",
14541        FakeLspAdapter {
14542            capabilities: lsp::ServerCapabilities {
14543                document_formatting_provider: Some(lsp::OneOf::Left(true)),
14544                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14545                    commands: vec!["the-command-for-code-action-1".into()],
14546                    ..Default::default()
14547                }),
14548                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14549                ..Default::default()
14550            },
14551            ..Default::default()
14552        },
14553    );
14554
14555    let buffer = project
14556        .update(cx, |project, cx| {
14557            project.open_local_buffer(path!("/file.rs"), cx)
14558        })
14559        .await
14560        .unwrap();
14561
14562    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14563    let (editor, cx) = cx.add_window_view(|window, cx| {
14564        build_editor_with_project(project.clone(), buffer, window, cx)
14565    });
14566
14567    let fake_server = fake_servers.next().await.unwrap();
14568    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
14569        move |_params, _| async move {
14570            Ok(Some(vec![lsp::TextEdit::new(
14571                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14572                "applied-formatting\n".to_string(),
14573            )]))
14574        },
14575    );
14576    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14577        move |params, _| async move {
14578            let requested_code_actions = params.context.only.expect("Expected code action request");
14579            assert_eq!(requested_code_actions.len(), 1);
14580
14581            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
14582            let code_action = match requested_code_actions[0].as_str() {
14583                "code-action-1" => lsp::CodeAction {
14584                    kind: Some("code-action-1".into()),
14585                    edit: Some(lsp::WorkspaceEdit::new(
14586                        [(
14587                            uri,
14588                            vec![lsp::TextEdit::new(
14589                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14590                                "applied-code-action-1-edit\n".to_string(),
14591                            )],
14592                        )]
14593                        .into_iter()
14594                        .collect(),
14595                    )),
14596                    command: Some(lsp::Command {
14597                        command: "the-command-for-code-action-1".into(),
14598                        ..Default::default()
14599                    }),
14600                    ..Default::default()
14601                },
14602                "code-action-2" => lsp::CodeAction {
14603                    kind: Some("code-action-2".into()),
14604                    edit: Some(lsp::WorkspaceEdit::new(
14605                        [(
14606                            uri,
14607                            vec![lsp::TextEdit::new(
14608                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14609                                "applied-code-action-2-edit\n".to_string(),
14610                            )],
14611                        )]
14612                        .into_iter()
14613                        .collect(),
14614                    )),
14615                    ..Default::default()
14616                },
14617                req => panic!("Unexpected code action request: {:?}", req),
14618            };
14619            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14620                code_action,
14621            )]))
14622        },
14623    );
14624
14625    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
14626        move |params, _| async move { Ok(params) }
14627    });
14628
14629    let command_lock = Arc::new(futures::lock::Mutex::new(()));
14630    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14631        let fake = fake_server.clone();
14632        let lock = command_lock.clone();
14633        move |params, _| {
14634            assert_eq!(params.command, "the-command-for-code-action-1");
14635            let fake = fake.clone();
14636            let lock = lock.clone();
14637            async move {
14638                lock.lock().await;
14639                fake.server
14640                    .request::<lsp::request::ApplyWorkspaceEdit>(
14641                        lsp::ApplyWorkspaceEditParams {
14642                            label: None,
14643                            edit: lsp::WorkspaceEdit {
14644                                changes: Some(
14645                                    [(
14646                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
14647                                        vec![lsp::TextEdit {
14648                                            range: lsp::Range::new(
14649                                                lsp::Position::new(0, 0),
14650                                                lsp::Position::new(0, 0),
14651                                            ),
14652                                            new_text: "applied-code-action-1-command\n".into(),
14653                                        }],
14654                                    )]
14655                                    .into_iter()
14656                                    .collect(),
14657                                ),
14658                                ..Default::default()
14659                            },
14660                        },
14661                        DEFAULT_LSP_REQUEST_TIMEOUT,
14662                    )
14663                    .await
14664                    .into_response()
14665                    .unwrap();
14666                Ok(Some(json!(null)))
14667            }
14668        }
14669    });
14670
14671    editor
14672        .update_in(cx, |editor, window, cx| {
14673            editor.perform_format(
14674                project.clone(),
14675                FormatTrigger::Manual,
14676                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14677                window,
14678                cx,
14679            )
14680        })
14681        .unwrap()
14682        .await;
14683    editor.update(cx, |editor, cx| {
14684        assert_eq!(
14685            editor.text(cx),
14686            r#"
14687                applied-code-action-2-edit
14688                applied-code-action-1-command
14689                applied-code-action-1-edit
14690                applied-formatting
14691                one
14692                two
14693                three
14694            "#
14695            .unindent()
14696        );
14697    });
14698
14699    editor.update_in(cx, |editor, window, cx| {
14700        editor.undo(&Default::default(), window, cx);
14701        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14702    });
14703
14704    // Perform a manual edit while waiting for an LSP command
14705    // that's being run as part of a formatting code action.
14706    let lock_guard = command_lock.lock().await;
14707    let format = editor
14708        .update_in(cx, |editor, window, cx| {
14709            editor.perform_format(
14710                project.clone(),
14711                FormatTrigger::Manual,
14712                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14713                window,
14714                cx,
14715            )
14716        })
14717        .unwrap();
14718    cx.run_until_parked();
14719    editor.update(cx, |editor, cx| {
14720        assert_eq!(
14721            editor.text(cx),
14722            r#"
14723                applied-code-action-1-edit
14724                applied-formatting
14725                one
14726                two
14727                three
14728            "#
14729            .unindent()
14730        );
14731
14732        editor.buffer.update(cx, |buffer, cx| {
14733            let ix = buffer.len(cx);
14734            buffer.edit([(ix..ix, "edited\n")], None, cx);
14735        });
14736    });
14737
14738    // Allow the LSP command to proceed. Because the buffer was edited,
14739    // the second code action will not be run.
14740    drop(lock_guard);
14741    format.await;
14742    editor.update_in(cx, |editor, window, cx| {
14743        assert_eq!(
14744            editor.text(cx),
14745            r#"
14746                applied-code-action-1-command
14747                applied-code-action-1-edit
14748                applied-formatting
14749                one
14750                two
14751                three
14752                edited
14753            "#
14754            .unindent()
14755        );
14756
14757        // The manual edit is undone first, because it is the last thing the user did
14758        // (even though the command completed afterwards).
14759        editor.undo(&Default::default(), window, cx);
14760        assert_eq!(
14761            editor.text(cx),
14762            r#"
14763                applied-code-action-1-command
14764                applied-code-action-1-edit
14765                applied-formatting
14766                one
14767                two
14768                three
14769            "#
14770            .unindent()
14771        );
14772
14773        // All the formatting (including the command, which completed after the manual edit)
14774        // is undone together.
14775        editor.undo(&Default::default(), window, cx);
14776        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14777    });
14778}
14779
14780#[gpui::test]
14781async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
14782    init_test(cx, |settings| {
14783        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
14784            settings::LanguageServerFormatterSpecifier::Current,
14785        )]))
14786    });
14787
14788    let fs = FakeFs::new(cx.executor());
14789    fs.insert_file(path!("/file.ts"), Default::default()).await;
14790
14791    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14792
14793    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14794    language_registry.add(Arc::new(Language::new(
14795        LanguageConfig {
14796            name: "TypeScript".into(),
14797            matcher: LanguageMatcher {
14798                path_suffixes: vec!["ts".to_string()],
14799                ..Default::default()
14800            },
14801            ..LanguageConfig::default()
14802        },
14803        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14804    )));
14805    update_test_language_settings(cx, &|settings| {
14806        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
14807    });
14808    let mut fake_servers = language_registry.register_fake_lsp(
14809        "TypeScript",
14810        FakeLspAdapter {
14811            capabilities: lsp::ServerCapabilities {
14812                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14813                ..Default::default()
14814            },
14815            ..Default::default()
14816        },
14817    );
14818
14819    let buffer = project
14820        .update(cx, |project, cx| {
14821            project.open_local_buffer(path!("/file.ts"), cx)
14822        })
14823        .await
14824        .unwrap();
14825
14826    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14827    let (editor, cx) = cx.add_window_view(|window, cx| {
14828        build_editor_with_project(project.clone(), buffer, window, cx)
14829    });
14830    editor.update_in(cx, |editor, window, cx| {
14831        editor.set_text(
14832            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14833            window,
14834            cx,
14835        )
14836    });
14837
14838    let fake_server = fake_servers.next().await.unwrap();
14839
14840    let format = editor
14841        .update_in(cx, |editor, window, cx| {
14842            editor.perform_code_action_kind(
14843                project.clone(),
14844                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14845                window,
14846                cx,
14847            )
14848        })
14849        .unwrap();
14850    fake_server
14851        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
14852            assert_eq!(
14853                params.text_document.uri,
14854                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14855            );
14856            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14857                lsp::CodeAction {
14858                    title: "Organize Imports".to_string(),
14859                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
14860                    edit: Some(lsp::WorkspaceEdit {
14861                        changes: Some(
14862                            [(
14863                                params.text_document.uri.clone(),
14864                                vec![lsp::TextEdit::new(
14865                                    lsp::Range::new(
14866                                        lsp::Position::new(1, 0),
14867                                        lsp::Position::new(2, 0),
14868                                    ),
14869                                    "".to_string(),
14870                                )],
14871                            )]
14872                            .into_iter()
14873                            .collect(),
14874                        ),
14875                        ..Default::default()
14876                    }),
14877                    ..Default::default()
14878                },
14879            )]))
14880        })
14881        .next()
14882        .await;
14883    format.await;
14884    assert_eq!(
14885        editor.update(cx, |editor, cx| editor.text(cx)),
14886        "import { a } from 'module';\n\nconst x = a;\n"
14887    );
14888
14889    editor.update_in(cx, |editor, window, cx| {
14890        editor.set_text(
14891            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14892            window,
14893            cx,
14894        )
14895    });
14896    // Ensure we don't lock if code action hangs.
14897    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14898        move |params, _| async move {
14899            assert_eq!(
14900                params.text_document.uri,
14901                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14902            );
14903            futures::future::pending::<()>().await;
14904            unreachable!()
14905        },
14906    );
14907    let format = editor
14908        .update_in(cx, |editor, window, cx| {
14909            editor.perform_code_action_kind(
14910                project,
14911                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14912                window,
14913                cx,
14914            )
14915        })
14916        .unwrap();
14917    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
14918    format.await;
14919    assert_eq!(
14920        editor.update(cx, |editor, cx| editor.text(cx)),
14921        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
14922    );
14923}
14924
14925#[gpui::test]
14926async fn test_formatter_failure_does_not_abort_subsequent_formatters(cx: &mut TestAppContext) {
14927    init_test(cx, |settings| {
14928        settings.defaults.formatter = Some(FormatterList::Vec(vec![
14929            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
14930            Formatter::CodeAction("organize-imports".into()),
14931        ]))
14932    });
14933
14934    let fs = FakeFs::new(cx.executor());
14935    fs.insert_file(path!("/file.rs"), "fn main() {}\n".into())
14936        .await;
14937
14938    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14940    language_registry.add(rust_lang());
14941
14942    let mut fake_servers = language_registry.register_fake_lsp(
14943        "Rust",
14944        FakeLspAdapter {
14945            capabilities: lsp::ServerCapabilities {
14946                document_formatting_provider: Some(lsp::OneOf::Left(true)),
14947                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14948                ..Default::default()
14949            },
14950            ..Default::default()
14951        },
14952    );
14953
14954    let buffer = project
14955        .update(cx, |project, cx| {
14956            project.open_local_buffer(path!("/file.rs"), cx)
14957        })
14958        .await
14959        .unwrap();
14960
14961    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14962    let (editor, cx) = cx.add_window_view(|window, cx| {
14963        build_editor_with_project(project.clone(), buffer, window, cx)
14964    });
14965
14966    let fake_server = fake_servers.next().await.unwrap();
14967
14968    // Formatter #1 (LanguageServer) returns an error to simulate failure
14969    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
14970        move |_params, _| async move { Err(anyhow::anyhow!("Simulated formatter failure")) },
14971    );
14972
14973    // Formatter #2 (CodeAction) returns a successful edit
14974    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14975        move |_params, _| async move {
14976            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
14977            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14978                lsp::CodeAction {
14979                    kind: Some("organize-imports".into()),
14980                    edit: Some(lsp::WorkspaceEdit::new(
14981                        [(
14982                            uri,
14983                            vec![lsp::TextEdit::new(
14984                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14985                                "use std::io;\n".to_string(),
14986                            )],
14987                        )]
14988                        .into_iter()
14989                        .collect(),
14990                    )),
14991                    ..Default::default()
14992                },
14993            )]))
14994        },
14995    );
14996
14997    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
14998        move |params, _| async move { Ok(params) }
14999    });
15000
15001    editor
15002        .update_in(cx, |editor, window, cx| {
15003            editor.perform_format(
15004                project.clone(),
15005                FormatTrigger::Manual,
15006                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
15007                window,
15008                cx,
15009            )
15010        })
15011        .unwrap()
15012        .await;
15013
15014    // Formatter #1 (LanguageServer) failed, but formatter #2 (CodeAction) should have applied
15015    editor.update(cx, |editor, cx| {
15016        assert_eq!(editor.text(cx), "use std::io;\nfn main() {}\n");
15017    });
15018
15019    // The entire format operation should undo as one transaction
15020    editor.update_in(cx, |editor, window, cx| {
15021        editor.undo(&Default::default(), window, cx);
15022        assert_eq!(editor.text(cx), "fn main() {}\n");
15023    });
15024}
15025
15026#[gpui::test]
15027async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
15028    init_test(cx, |_| {});
15029
15030    let mut cx = EditorLspTestContext::new_rust(
15031        lsp::ServerCapabilities {
15032            document_formatting_provider: Some(lsp::OneOf::Left(true)),
15033            ..Default::default()
15034        },
15035        cx,
15036    )
15037    .await;
15038
15039    cx.set_state(indoc! {"
15040        one.twoˇ
15041    "});
15042
15043    // The format request takes a long time. When it completes, it inserts
15044    // a newline and an indent before the `.`
15045    cx.lsp
15046        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
15047            let executor = cx.background_executor().clone();
15048            async move {
15049                executor.timer(Duration::from_millis(100)).await;
15050                Ok(Some(vec![lsp::TextEdit {
15051                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
15052                    new_text: "\n    ".into(),
15053                }]))
15054            }
15055        });
15056
15057    // Submit a format request.
15058    let format_1 = cx
15059        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
15060        .unwrap();
15061    cx.executor().run_until_parked();
15062
15063    // Submit a second format request.
15064    let format_2 = cx
15065        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
15066        .unwrap();
15067    cx.executor().run_until_parked();
15068
15069    // Wait for both format requests to complete
15070    cx.executor().advance_clock(Duration::from_millis(200));
15071    format_1.await.unwrap();
15072    format_2.await.unwrap();
15073
15074    // The formatting edits only happens once.
15075    cx.assert_editor_state(indoc! {"
15076        one
15077            .twoˇ
15078    "});
15079}
15080
15081#[gpui::test]
15082async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
15083    init_test(cx, |settings| {
15084        settings.defaults.formatter = Some(FormatterList::default())
15085    });
15086
15087    let mut cx = EditorLspTestContext::new_rust(
15088        lsp::ServerCapabilities {
15089            document_formatting_provider: Some(lsp::OneOf::Left(true)),
15090            ..Default::default()
15091        },
15092        cx,
15093    )
15094    .await;
15095
15096    // Record which buffer changes have been sent to the language server
15097    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
15098    cx.lsp
15099        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
15100            let buffer_changes = buffer_changes.clone();
15101            move |params, _| {
15102                buffer_changes.lock().extend(
15103                    params
15104                        .content_changes
15105                        .into_iter()
15106                        .map(|e| (e.range.unwrap(), e.text)),
15107                );
15108            }
15109        });
15110    // Handle formatting requests to the language server.
15111    cx.lsp
15112        .set_request_handler::<lsp::request::Formatting, _, _>({
15113            move |_, _| {
15114                // Insert blank lines between each line of the buffer.
15115                async move {
15116                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
15117                    // DidChangedTextDocument to the LSP before sending the formatting request.
15118                    // assert_eq!(
15119                    //     &buffer_changes.lock()[1..],
15120                    //     &[
15121                    //         (
15122                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
15123                    //             "".into()
15124                    //         ),
15125                    //         (
15126                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
15127                    //             "".into()
15128                    //         ),
15129                    //         (
15130                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
15131                    //             "\n".into()
15132                    //         ),
15133                    //     ]
15134                    // );
15135
15136                    Ok(Some(vec![
15137                        lsp::TextEdit {
15138                            range: lsp::Range::new(
15139                                lsp::Position::new(1, 0),
15140                                lsp::Position::new(1, 0),
15141                            ),
15142                            new_text: "\n".into(),
15143                        },
15144                        lsp::TextEdit {
15145                            range: lsp::Range::new(
15146                                lsp::Position::new(2, 0),
15147                                lsp::Position::new(2, 0),
15148                            ),
15149                            new_text: "\n".into(),
15150                        },
15151                    ]))
15152                }
15153            }
15154        });
15155
15156    // Set up a buffer white some trailing whitespace and no trailing newline.
15157    cx.set_state(
15158        &[
15159            "one ",   //
15160            "twoˇ",   //
15161            "three ", //
15162            "four",   //
15163        ]
15164        .join("\n"),
15165    );
15166
15167    // Submit a format request.
15168    let format = cx
15169        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
15170        .unwrap();
15171
15172    cx.run_until_parked();
15173    // After formatting the buffer, the trailing whitespace is stripped,
15174    // a newline is appended, and the edits provided by the language server
15175    // have been applied.
15176    format.await.unwrap();
15177
15178    cx.assert_editor_state(
15179        &[
15180            "one",   //
15181            "",      //
15182            "twoˇ",  //
15183            "",      //
15184            "three", //
15185            "four",  //
15186            "",      //
15187        ]
15188        .join("\n"),
15189    );
15190
15191    // Undoing the formatting undoes the trailing whitespace removal, the
15192    // trailing newline, and the LSP edits.
15193    cx.update_buffer(|buffer, cx| buffer.undo(cx));
15194    cx.assert_editor_state(
15195        &[
15196            "one ",   //
15197            "twoˇ",   //
15198            "three ", //
15199            "four",   //
15200        ]
15201        .join("\n"),
15202    );
15203}
15204
15205#[gpui::test]
15206async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
15207    cx: &mut TestAppContext,
15208) {
15209    init_test(cx, |_| {});
15210
15211    cx.update(|cx| {
15212        cx.update_global::<SettingsStore, _>(|settings, cx| {
15213            settings.update_user_settings(cx, |settings| {
15214                settings.editor.auto_signature_help = Some(true);
15215                settings.editor.hover_popover_delay = Some(DelayMs(300));
15216            });
15217        });
15218    });
15219
15220    let mut cx = EditorLspTestContext::new_rust(
15221        lsp::ServerCapabilities {
15222            signature_help_provider: Some(lsp::SignatureHelpOptions {
15223                ..Default::default()
15224            }),
15225            ..Default::default()
15226        },
15227        cx,
15228    )
15229    .await;
15230
15231    let language = Language::new(
15232        LanguageConfig {
15233            name: "Rust".into(),
15234            brackets: BracketPairConfig {
15235                pairs: vec![
15236                    BracketPair {
15237                        start: "{".to_string(),
15238                        end: "}".to_string(),
15239                        close: true,
15240                        surround: true,
15241                        newline: true,
15242                    },
15243                    BracketPair {
15244                        start: "(".to_string(),
15245                        end: ")".to_string(),
15246                        close: true,
15247                        surround: true,
15248                        newline: true,
15249                    },
15250                    BracketPair {
15251                        start: "/*".to_string(),
15252                        end: " */".to_string(),
15253                        close: true,
15254                        surround: true,
15255                        newline: true,
15256                    },
15257                    BracketPair {
15258                        start: "[".to_string(),
15259                        end: "]".to_string(),
15260                        close: false,
15261                        surround: false,
15262                        newline: true,
15263                    },
15264                    BracketPair {
15265                        start: "\"".to_string(),
15266                        end: "\"".to_string(),
15267                        close: true,
15268                        surround: true,
15269                        newline: false,
15270                    },
15271                    BracketPair {
15272                        start: "<".to_string(),
15273                        end: ">".to_string(),
15274                        close: false,
15275                        surround: true,
15276                        newline: true,
15277                    },
15278                ],
15279                ..Default::default()
15280            },
15281            autoclose_before: "})]".to_string(),
15282            ..Default::default()
15283        },
15284        Some(tree_sitter_rust::LANGUAGE.into()),
15285    );
15286    let language = Arc::new(language);
15287
15288    cx.language_registry().add(language.clone());
15289    cx.update_buffer(|buffer, cx| {
15290        buffer.set_language(Some(language), cx);
15291    });
15292
15293    cx.set_state(
15294        &r#"
15295            fn main() {
15296                sampleˇ
15297            }
15298        "#
15299        .unindent(),
15300    );
15301
15302    cx.update_editor(|editor, window, cx| {
15303        editor.handle_input("(", window, cx);
15304    });
15305    cx.assert_editor_state(
15306        &"
15307            fn main() {
15308                sample(ˇ)
15309            }
15310        "
15311        .unindent(),
15312    );
15313
15314    let mocked_response = lsp::SignatureHelp {
15315        signatures: vec![lsp::SignatureInformation {
15316            label: "fn sample(param1: u8, param2: u8)".to_string(),
15317            documentation: None,
15318            parameters: Some(vec![
15319                lsp::ParameterInformation {
15320                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15321                    documentation: None,
15322                },
15323                lsp::ParameterInformation {
15324                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15325                    documentation: None,
15326                },
15327            ]),
15328            active_parameter: None,
15329        }],
15330        active_signature: Some(0),
15331        active_parameter: Some(0),
15332    };
15333    handle_signature_help_request(&mut cx, mocked_response).await;
15334
15335    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15336        .await;
15337
15338    cx.editor(|editor, _, _| {
15339        let signature_help_state = editor.signature_help_state.popover().cloned();
15340        let signature = signature_help_state.unwrap();
15341        assert_eq!(
15342            signature.signatures[signature.current_signature].label,
15343            "fn sample(param1: u8, param2: u8)"
15344        );
15345    });
15346}
15347
15348#[gpui::test]
15349async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
15350    init_test(cx, |_| {});
15351
15352    let delay_ms = 500;
15353    cx.update(|cx| {
15354        cx.update_global::<SettingsStore, _>(|settings, cx| {
15355            settings.update_user_settings(cx, |settings| {
15356                settings.editor.auto_signature_help = Some(true);
15357                settings.editor.show_signature_help_after_edits = Some(false);
15358                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
15359            });
15360        });
15361    });
15362
15363    let mut cx = EditorLspTestContext::new_rust(
15364        lsp::ServerCapabilities {
15365            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15366            ..lsp::ServerCapabilities::default()
15367        },
15368        cx,
15369    )
15370    .await;
15371
15372    let mocked_response = lsp::SignatureHelp {
15373        signatures: vec![lsp::SignatureInformation {
15374            label: "fn sample(param1: u8)".to_string(),
15375            documentation: None,
15376            parameters: Some(vec![lsp::ParameterInformation {
15377                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15378                documentation: None,
15379            }]),
15380            active_parameter: None,
15381        }],
15382        active_signature: Some(0),
15383        active_parameter: Some(0),
15384    };
15385
15386    cx.set_state(indoc! {"
15387        fn main() {
15388            sample(ˇ);
15389        }
15390
15391        fn sample(param1: u8) {}
15392    "});
15393
15394    // Manual trigger should show immediately without delay
15395    cx.update_editor(|editor, window, cx| {
15396        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15397    });
15398    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15399    cx.run_until_parked();
15400    cx.editor(|editor, _, _| {
15401        assert!(
15402            editor.signature_help_state.is_shown(),
15403            "Manual trigger should show signature help without delay"
15404        );
15405    });
15406
15407    cx.update_editor(|editor, _, cx| {
15408        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
15409    });
15410    cx.run_until_parked();
15411    cx.editor(|editor, _, _| {
15412        assert!(!editor.signature_help_state.is_shown());
15413    });
15414
15415    // Auto trigger (cursor movement into brackets) should respect delay
15416    cx.set_state(indoc! {"
15417        fn main() {
15418            sampleˇ();
15419        }
15420
15421        fn sample(param1: u8) {}
15422    "});
15423    cx.update_editor(|editor, window, cx| {
15424        editor.move_right(&MoveRight, window, cx);
15425    });
15426    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15427    cx.run_until_parked();
15428    cx.editor(|editor, _, _| {
15429        assert!(
15430            !editor.signature_help_state.is_shown(),
15431            "Auto trigger should wait for delay before showing signature help"
15432        );
15433    });
15434
15435    cx.executor()
15436        .advance_clock(Duration::from_millis(delay_ms + 50));
15437    cx.run_until_parked();
15438    cx.editor(|editor, _, _| {
15439        assert!(
15440            editor.signature_help_state.is_shown(),
15441            "Auto trigger should show signature help after delay elapsed"
15442        );
15443    });
15444}
15445
15446#[gpui::test]
15447async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
15448    init_test(cx, |_| {});
15449
15450    let delay_ms = 500;
15451    cx.update(|cx| {
15452        cx.update_global::<SettingsStore, _>(|settings, cx| {
15453            settings.update_user_settings(cx, |settings| {
15454                settings.editor.auto_signature_help = Some(false);
15455                settings.editor.show_signature_help_after_edits = Some(true);
15456                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
15457            });
15458        });
15459    });
15460
15461    let mut cx = EditorLspTestContext::new_rust(
15462        lsp::ServerCapabilities {
15463            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15464            ..lsp::ServerCapabilities::default()
15465        },
15466        cx,
15467    )
15468    .await;
15469
15470    let language = Arc::new(Language::new(
15471        LanguageConfig {
15472            name: "Rust".into(),
15473            brackets: BracketPairConfig {
15474                pairs: vec![BracketPair {
15475                    start: "(".to_string(),
15476                    end: ")".to_string(),
15477                    close: true,
15478                    surround: true,
15479                    newline: true,
15480                }],
15481                ..BracketPairConfig::default()
15482            },
15483            autoclose_before: "})".to_string(),
15484            ..LanguageConfig::default()
15485        },
15486        Some(tree_sitter_rust::LANGUAGE.into()),
15487    ));
15488    cx.language_registry().add(language.clone());
15489    cx.update_buffer(|buffer, cx| {
15490        buffer.set_language(Some(language), cx);
15491    });
15492
15493    let mocked_response = lsp::SignatureHelp {
15494        signatures: vec![lsp::SignatureInformation {
15495            label: "fn sample(param1: u8)".to_string(),
15496            documentation: None,
15497            parameters: Some(vec![lsp::ParameterInformation {
15498                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15499                documentation: None,
15500            }]),
15501            active_parameter: None,
15502        }],
15503        active_signature: Some(0),
15504        active_parameter: Some(0),
15505    };
15506
15507    cx.set_state(indoc! {"
15508        fn main() {
15509            sampleˇ
15510        }
15511    "});
15512
15513    // Typing bracket should show signature help immediately without delay
15514    cx.update_editor(|editor, window, cx| {
15515        editor.handle_input("(", window, cx);
15516    });
15517    handle_signature_help_request(&mut cx, mocked_response).await;
15518    cx.run_until_parked();
15519    cx.editor(|editor, _, _| {
15520        assert!(
15521            editor.signature_help_state.is_shown(),
15522            "show_signature_help_after_edits should show signature help without delay"
15523        );
15524    });
15525}
15526
15527#[gpui::test]
15528async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
15529    init_test(cx, |_| {});
15530
15531    cx.update(|cx| {
15532        cx.update_global::<SettingsStore, _>(|settings, cx| {
15533            settings.update_user_settings(cx, |settings| {
15534                settings.editor.auto_signature_help = Some(false);
15535                settings.editor.show_signature_help_after_edits = Some(false);
15536            });
15537        });
15538    });
15539
15540    let mut cx = EditorLspTestContext::new_rust(
15541        lsp::ServerCapabilities {
15542            signature_help_provider: Some(lsp::SignatureHelpOptions {
15543                ..Default::default()
15544            }),
15545            ..Default::default()
15546        },
15547        cx,
15548    )
15549    .await;
15550
15551    let language = Language::new(
15552        LanguageConfig {
15553            name: "Rust".into(),
15554            brackets: BracketPairConfig {
15555                pairs: vec![
15556                    BracketPair {
15557                        start: "{".to_string(),
15558                        end: "}".to_string(),
15559                        close: true,
15560                        surround: true,
15561                        newline: true,
15562                    },
15563                    BracketPair {
15564                        start: "(".to_string(),
15565                        end: ")".to_string(),
15566                        close: true,
15567                        surround: true,
15568                        newline: true,
15569                    },
15570                    BracketPair {
15571                        start: "/*".to_string(),
15572                        end: " */".to_string(),
15573                        close: true,
15574                        surround: true,
15575                        newline: true,
15576                    },
15577                    BracketPair {
15578                        start: "[".to_string(),
15579                        end: "]".to_string(),
15580                        close: false,
15581                        surround: false,
15582                        newline: true,
15583                    },
15584                    BracketPair {
15585                        start: "\"".to_string(),
15586                        end: "\"".to_string(),
15587                        close: true,
15588                        surround: true,
15589                        newline: false,
15590                    },
15591                    BracketPair {
15592                        start: "<".to_string(),
15593                        end: ">".to_string(),
15594                        close: false,
15595                        surround: true,
15596                        newline: true,
15597                    },
15598                ],
15599                ..Default::default()
15600            },
15601            autoclose_before: "})]".to_string(),
15602            ..Default::default()
15603        },
15604        Some(tree_sitter_rust::LANGUAGE.into()),
15605    );
15606    let language = Arc::new(language);
15607
15608    cx.language_registry().add(language.clone());
15609    cx.update_buffer(|buffer, cx| {
15610        buffer.set_language(Some(language), cx);
15611    });
15612
15613    // Ensure that signature_help is not called when no signature help is enabled.
15614    cx.set_state(
15615        &r#"
15616            fn main() {
15617                sampleˇ
15618            }
15619        "#
15620        .unindent(),
15621    );
15622    cx.update_editor(|editor, window, cx| {
15623        editor.handle_input("(", window, cx);
15624    });
15625    cx.assert_editor_state(
15626        &"
15627            fn main() {
15628                sample(ˇ)
15629            }
15630        "
15631        .unindent(),
15632    );
15633    cx.editor(|editor, _, _| {
15634        assert!(editor.signature_help_state.task().is_none());
15635    });
15636
15637    let mocked_response = lsp::SignatureHelp {
15638        signatures: vec![lsp::SignatureInformation {
15639            label: "fn sample(param1: u8, param2: u8)".to_string(),
15640            documentation: None,
15641            parameters: Some(vec![
15642                lsp::ParameterInformation {
15643                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15644                    documentation: None,
15645                },
15646                lsp::ParameterInformation {
15647                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15648                    documentation: None,
15649                },
15650            ]),
15651            active_parameter: None,
15652        }],
15653        active_signature: Some(0),
15654        active_parameter: Some(0),
15655    };
15656
15657    // Ensure that signature_help is called when enabled afte edits
15658    cx.update(|_, cx| {
15659        cx.update_global::<SettingsStore, _>(|settings, cx| {
15660            settings.update_user_settings(cx, |settings| {
15661                settings.editor.auto_signature_help = Some(false);
15662                settings.editor.show_signature_help_after_edits = Some(true);
15663            });
15664        });
15665    });
15666    cx.set_state(
15667        &r#"
15668            fn main() {
15669                sampleˇ
15670            }
15671        "#
15672        .unindent(),
15673    );
15674    cx.update_editor(|editor, window, cx| {
15675        editor.handle_input("(", window, cx);
15676    });
15677    cx.assert_editor_state(
15678        &"
15679            fn main() {
15680                sample(ˇ)
15681            }
15682        "
15683        .unindent(),
15684    );
15685    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15686    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15687        .await;
15688    cx.update_editor(|editor, _, _| {
15689        let signature_help_state = editor.signature_help_state.popover().cloned();
15690        assert!(signature_help_state.is_some());
15691        let signature = signature_help_state.unwrap();
15692        assert_eq!(
15693            signature.signatures[signature.current_signature].label,
15694            "fn sample(param1: u8, param2: u8)"
15695        );
15696        editor.signature_help_state = SignatureHelpState::default();
15697    });
15698
15699    // Ensure that signature_help is called when auto signature help override is enabled
15700    cx.update(|_, cx| {
15701        cx.update_global::<SettingsStore, _>(|settings, cx| {
15702            settings.update_user_settings(cx, |settings| {
15703                settings.editor.auto_signature_help = Some(true);
15704                settings.editor.show_signature_help_after_edits = Some(false);
15705            });
15706        });
15707    });
15708    cx.set_state(
15709        &r#"
15710            fn main() {
15711                sampleˇ
15712            }
15713        "#
15714        .unindent(),
15715    );
15716    cx.update_editor(|editor, window, cx| {
15717        editor.handle_input("(", window, cx);
15718    });
15719    cx.assert_editor_state(
15720        &"
15721            fn main() {
15722                sample(ˇ)
15723            }
15724        "
15725        .unindent(),
15726    );
15727    handle_signature_help_request(&mut cx, mocked_response).await;
15728    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15729        .await;
15730    cx.editor(|editor, _, _| {
15731        let signature_help_state = editor.signature_help_state.popover().cloned();
15732        assert!(signature_help_state.is_some());
15733        let signature = signature_help_state.unwrap();
15734        assert_eq!(
15735            signature.signatures[signature.current_signature].label,
15736            "fn sample(param1: u8, param2: u8)"
15737        );
15738    });
15739}
15740
15741#[gpui::test]
15742async fn test_signature_help(cx: &mut TestAppContext) {
15743    init_test(cx, |_| {});
15744    cx.update(|cx| {
15745        cx.update_global::<SettingsStore, _>(|settings, cx| {
15746            settings.update_user_settings(cx, |settings| {
15747                settings.editor.auto_signature_help = Some(true);
15748            });
15749        });
15750    });
15751
15752    let mut cx = EditorLspTestContext::new_rust(
15753        lsp::ServerCapabilities {
15754            signature_help_provider: Some(lsp::SignatureHelpOptions {
15755                ..Default::default()
15756            }),
15757            ..Default::default()
15758        },
15759        cx,
15760    )
15761    .await;
15762
15763    // A test that directly calls `show_signature_help`
15764    cx.update_editor(|editor, window, cx| {
15765        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15766    });
15767
15768    let mocked_response = lsp::SignatureHelp {
15769        signatures: vec![lsp::SignatureInformation {
15770            label: "fn sample(param1: u8, param2: u8)".to_string(),
15771            documentation: None,
15772            parameters: Some(vec![
15773                lsp::ParameterInformation {
15774                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15775                    documentation: None,
15776                },
15777                lsp::ParameterInformation {
15778                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15779                    documentation: None,
15780                },
15781            ]),
15782            active_parameter: None,
15783        }],
15784        active_signature: Some(0),
15785        active_parameter: Some(0),
15786    };
15787    handle_signature_help_request(&mut cx, mocked_response).await;
15788
15789    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15790        .await;
15791
15792    cx.editor(|editor, _, _| {
15793        let signature_help_state = editor.signature_help_state.popover().cloned();
15794        assert!(signature_help_state.is_some());
15795        let signature = signature_help_state.unwrap();
15796        assert_eq!(
15797            signature.signatures[signature.current_signature].label,
15798            "fn sample(param1: u8, param2: u8)"
15799        );
15800    });
15801
15802    // When exiting outside from inside the brackets, `signature_help` is closed.
15803    cx.set_state(indoc! {"
15804        fn main() {
15805            sample(ˇ);
15806        }
15807
15808        fn sample(param1: u8, param2: u8) {}
15809    "});
15810
15811    cx.update_editor(|editor, window, cx| {
15812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15813            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
15814        });
15815    });
15816
15817    let mocked_response = lsp::SignatureHelp {
15818        signatures: Vec::new(),
15819        active_signature: None,
15820        active_parameter: None,
15821    };
15822    handle_signature_help_request(&mut cx, mocked_response).await;
15823
15824    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15825        .await;
15826
15827    cx.editor(|editor, _, _| {
15828        assert!(!editor.signature_help_state.is_shown());
15829    });
15830
15831    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
15832    cx.set_state(indoc! {"
15833        fn main() {
15834            sample(ˇ);
15835        }
15836
15837        fn sample(param1: u8, param2: u8) {}
15838    "});
15839
15840    let mocked_response = lsp::SignatureHelp {
15841        signatures: vec![lsp::SignatureInformation {
15842            label: "fn sample(param1: u8, param2: u8)".to_string(),
15843            documentation: None,
15844            parameters: Some(vec![
15845                lsp::ParameterInformation {
15846                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15847                    documentation: None,
15848                },
15849                lsp::ParameterInformation {
15850                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15851                    documentation: None,
15852                },
15853            ]),
15854            active_parameter: None,
15855        }],
15856        active_signature: Some(0),
15857        active_parameter: Some(0),
15858    };
15859    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15860    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15861        .await;
15862    cx.editor(|editor, _, _| {
15863        assert!(editor.signature_help_state.is_shown());
15864    });
15865
15866    // Restore the popover with more parameter input
15867    cx.set_state(indoc! {"
15868        fn main() {
15869            sample(param1, param2ˇ);
15870        }
15871
15872        fn sample(param1: u8, param2: u8) {}
15873    "});
15874
15875    let mocked_response = lsp::SignatureHelp {
15876        signatures: vec![lsp::SignatureInformation {
15877            label: "fn sample(param1: u8, param2: u8)".to_string(),
15878            documentation: None,
15879            parameters: Some(vec![
15880                lsp::ParameterInformation {
15881                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15882                    documentation: None,
15883                },
15884                lsp::ParameterInformation {
15885                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15886                    documentation: None,
15887                },
15888            ]),
15889            active_parameter: None,
15890        }],
15891        active_signature: Some(0),
15892        active_parameter: Some(1),
15893    };
15894    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15895    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15896        .await;
15897
15898    // When selecting a range, the popover is gone.
15899    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
15900    cx.update_editor(|editor, window, cx| {
15901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15902            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15903        })
15904    });
15905    cx.assert_editor_state(indoc! {"
15906        fn main() {
15907            sample(param1, «ˇparam2»);
15908        }
15909
15910        fn sample(param1: u8, param2: u8) {}
15911    "});
15912    cx.editor(|editor, _, _| {
15913        assert!(!editor.signature_help_state.is_shown());
15914    });
15915
15916    // When unselecting again, the popover is back if within the brackets.
15917    cx.update_editor(|editor, window, cx| {
15918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15919            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15920        })
15921    });
15922    cx.assert_editor_state(indoc! {"
15923        fn main() {
15924            sample(param1, ˇparam2);
15925        }
15926
15927        fn sample(param1: u8, param2: u8) {}
15928    "});
15929    handle_signature_help_request(&mut cx, mocked_response).await;
15930    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15931        .await;
15932    cx.editor(|editor, _, _| {
15933        assert!(editor.signature_help_state.is_shown());
15934    });
15935
15936    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
15937    cx.update_editor(|editor, window, cx| {
15938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15939            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
15940            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15941        })
15942    });
15943    cx.assert_editor_state(indoc! {"
15944        fn main() {
15945            sample(param1, ˇparam2);
15946        }
15947
15948        fn sample(param1: u8, param2: u8) {}
15949    "});
15950
15951    let mocked_response = lsp::SignatureHelp {
15952        signatures: vec![lsp::SignatureInformation {
15953            label: "fn sample(param1: u8, param2: u8)".to_string(),
15954            documentation: None,
15955            parameters: Some(vec![
15956                lsp::ParameterInformation {
15957                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15958                    documentation: None,
15959                },
15960                lsp::ParameterInformation {
15961                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15962                    documentation: None,
15963                },
15964            ]),
15965            active_parameter: None,
15966        }],
15967        active_signature: Some(0),
15968        active_parameter: Some(1),
15969    };
15970    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15971    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15972        .await;
15973    cx.update_editor(|editor, _, cx| {
15974        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
15975    });
15976    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15977        .await;
15978    cx.update_editor(|editor, window, cx| {
15979        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15980            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15981        })
15982    });
15983    cx.assert_editor_state(indoc! {"
15984        fn main() {
15985            sample(param1, «ˇparam2»);
15986        }
15987
15988        fn sample(param1: u8, param2: u8) {}
15989    "});
15990    cx.update_editor(|editor, window, cx| {
15991        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15992            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15993        })
15994    });
15995    cx.assert_editor_state(indoc! {"
15996        fn main() {
15997            sample(param1, ˇparam2);
15998        }
15999
16000        fn sample(param1: u8, param2: u8) {}
16001    "});
16002    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
16003        .await;
16004}
16005
16006#[gpui::test]
16007async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
16008    init_test(cx, |_| {});
16009
16010    let mut cx = EditorLspTestContext::new_rust(
16011        lsp::ServerCapabilities {
16012            signature_help_provider: Some(lsp::SignatureHelpOptions {
16013                ..Default::default()
16014            }),
16015            ..Default::default()
16016        },
16017        cx,
16018    )
16019    .await;
16020
16021    cx.set_state(indoc! {"
16022        fn main() {
16023            overloadedˇ
16024        }
16025    "});
16026
16027    cx.update_editor(|editor, window, cx| {
16028        editor.handle_input("(", window, cx);
16029        editor.show_signature_help(&ShowSignatureHelp, window, cx);
16030    });
16031
16032    // Mock response with 3 signatures
16033    let mocked_response = lsp::SignatureHelp {
16034        signatures: vec![
16035            lsp::SignatureInformation {
16036                label: "fn overloaded(x: i32)".to_string(),
16037                documentation: None,
16038                parameters: Some(vec![lsp::ParameterInformation {
16039                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
16040                    documentation: None,
16041                }]),
16042                active_parameter: None,
16043            },
16044            lsp::SignatureInformation {
16045                label: "fn overloaded(x: i32, y: i32)".to_string(),
16046                documentation: None,
16047                parameters: Some(vec![
16048                    lsp::ParameterInformation {
16049                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
16050                        documentation: None,
16051                    },
16052                    lsp::ParameterInformation {
16053                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
16054                        documentation: None,
16055                    },
16056                ]),
16057                active_parameter: None,
16058            },
16059            lsp::SignatureInformation {
16060                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
16061                documentation: None,
16062                parameters: Some(vec![
16063                    lsp::ParameterInformation {
16064                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
16065                        documentation: None,
16066                    },
16067                    lsp::ParameterInformation {
16068                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
16069                        documentation: None,
16070                    },
16071                    lsp::ParameterInformation {
16072                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
16073                        documentation: None,
16074                    },
16075                ]),
16076                active_parameter: None,
16077            },
16078        ],
16079        active_signature: Some(1),
16080        active_parameter: Some(0),
16081    };
16082    handle_signature_help_request(&mut cx, mocked_response).await;
16083
16084    cx.condition(|editor, _| editor.signature_help_state.is_shown())
16085        .await;
16086
16087    // Verify we have multiple signatures and the right one is selected
16088    cx.editor(|editor, _, _| {
16089        let popover = editor.signature_help_state.popover().cloned().unwrap();
16090        assert_eq!(popover.signatures.len(), 3);
16091        // active_signature was 1, so that should be the current
16092        assert_eq!(popover.current_signature, 1);
16093        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
16094        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
16095        assert_eq!(
16096            popover.signatures[2].label,
16097            "fn overloaded(x: i32, y: i32, z: i32)"
16098        );
16099    });
16100
16101    // Test navigation functionality
16102    cx.update_editor(|editor, window, cx| {
16103        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
16104    });
16105
16106    cx.editor(|editor, _, _| {
16107        let popover = editor.signature_help_state.popover().cloned().unwrap();
16108        assert_eq!(popover.current_signature, 2);
16109    });
16110
16111    // Test wrap around
16112    cx.update_editor(|editor, window, cx| {
16113        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
16114    });
16115
16116    cx.editor(|editor, _, _| {
16117        let popover = editor.signature_help_state.popover().cloned().unwrap();
16118        assert_eq!(popover.current_signature, 0);
16119    });
16120
16121    // Test previous navigation
16122    cx.update_editor(|editor, window, cx| {
16123        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
16124    });
16125
16126    cx.editor(|editor, _, _| {
16127        let popover = editor.signature_help_state.popover().cloned().unwrap();
16128        assert_eq!(popover.current_signature, 2);
16129    });
16130}
16131
16132#[gpui::test]
16133async fn test_completion_mode(cx: &mut TestAppContext) {
16134    init_test(cx, |_| {});
16135    let mut cx = EditorLspTestContext::new_rust(
16136        lsp::ServerCapabilities {
16137            completion_provider: Some(lsp::CompletionOptions {
16138                resolve_provider: Some(true),
16139                ..Default::default()
16140            }),
16141            ..Default::default()
16142        },
16143        cx,
16144    )
16145    .await;
16146
16147    struct Run {
16148        run_description: &'static str,
16149        initial_state: String,
16150        buffer_marked_text: String,
16151        completion_label: &'static str,
16152        completion_text: &'static str,
16153        expected_with_insert_mode: String,
16154        expected_with_replace_mode: String,
16155        expected_with_replace_subsequence_mode: String,
16156        expected_with_replace_suffix_mode: String,
16157    }
16158
16159    let runs = [
16160        Run {
16161            run_description: "Start of word matches completion text",
16162            initial_state: "before ediˇ after".into(),
16163            buffer_marked_text: "before <edi|> after".into(),
16164            completion_label: "editor",
16165            completion_text: "editor",
16166            expected_with_insert_mode: "before editorˇ after".into(),
16167            expected_with_replace_mode: "before editorˇ after".into(),
16168            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
16169            expected_with_replace_suffix_mode: "before editorˇ after".into(),
16170        },
16171        Run {
16172            run_description: "Accept same text at the middle of the word",
16173            initial_state: "before ediˇtor after".into(),
16174            buffer_marked_text: "before <edi|tor> after".into(),
16175            completion_label: "editor",
16176            completion_text: "editor",
16177            expected_with_insert_mode: "before editorˇtor after".into(),
16178            expected_with_replace_mode: "before editorˇ after".into(),
16179            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
16180            expected_with_replace_suffix_mode: "before editorˇ after".into(),
16181        },
16182        Run {
16183            run_description: "End of word matches completion text -- cursor at end",
16184            initial_state: "before torˇ after".into(),
16185            buffer_marked_text: "before <tor|> after".into(),
16186            completion_label: "editor",
16187            completion_text: "editor",
16188            expected_with_insert_mode: "before editorˇ after".into(),
16189            expected_with_replace_mode: "before editorˇ after".into(),
16190            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
16191            expected_with_replace_suffix_mode: "before editorˇ after".into(),
16192        },
16193        Run {
16194            run_description: "End of word matches completion text -- cursor at start",
16195            initial_state: "before ˇtor after".into(),
16196            buffer_marked_text: "before <|tor> after".into(),
16197            completion_label: "editor",
16198            completion_text: "editor",
16199            expected_with_insert_mode: "before editorˇtor after".into(),
16200            expected_with_replace_mode: "before editorˇ after".into(),
16201            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
16202            expected_with_replace_suffix_mode: "before editorˇ after".into(),
16203        },
16204        Run {
16205            run_description: "Prepend text containing whitespace",
16206            initial_state: "pˇfield: bool".into(),
16207            buffer_marked_text: "<p|field>: bool".into(),
16208            completion_label: "pub ",
16209            completion_text: "pub ",
16210            expected_with_insert_mode: "pub ˇfield: bool".into(),
16211            expected_with_replace_mode: "pub ˇ: bool".into(),
16212            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
16213            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
16214        },
16215        Run {
16216            run_description: "Add element to start of list",
16217            initial_state: "[element_ˇelement_2]".into(),
16218            buffer_marked_text: "[<element_|element_2>]".into(),
16219            completion_label: "element_1",
16220            completion_text: "element_1",
16221            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
16222            expected_with_replace_mode: "[element_1ˇ]".into(),
16223            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
16224            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
16225        },
16226        Run {
16227            run_description: "Add element to start of list -- first and second elements are equal",
16228            initial_state: "[elˇelement]".into(),
16229            buffer_marked_text: "[<el|element>]".into(),
16230            completion_label: "element",
16231            completion_text: "element",
16232            expected_with_insert_mode: "[elementˇelement]".into(),
16233            expected_with_replace_mode: "[elementˇ]".into(),
16234            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
16235            expected_with_replace_suffix_mode: "[elementˇ]".into(),
16236        },
16237        Run {
16238            run_description: "Ends with matching suffix",
16239            initial_state: "SubˇError".into(),
16240            buffer_marked_text: "<Sub|Error>".into(),
16241            completion_label: "SubscriptionError",
16242            completion_text: "SubscriptionError",
16243            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
16244            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
16245            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
16246            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
16247        },
16248        Run {
16249            run_description: "Suffix is a subsequence -- contiguous",
16250            initial_state: "SubˇErr".into(),
16251            buffer_marked_text: "<Sub|Err>".into(),
16252            completion_label: "SubscriptionError",
16253            completion_text: "SubscriptionError",
16254            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
16255            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
16256            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
16257            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
16258        },
16259        Run {
16260            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
16261            initial_state: "Suˇscrirr".into(),
16262            buffer_marked_text: "<Su|scrirr>".into(),
16263            completion_label: "SubscriptionError",
16264            completion_text: "SubscriptionError",
16265            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
16266            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
16267            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
16268            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
16269        },
16270        Run {
16271            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
16272            initial_state: "foo(indˇix)".into(),
16273            buffer_marked_text: "foo(<ind|ix>)".into(),
16274            completion_label: "node_index",
16275            completion_text: "node_index",
16276            expected_with_insert_mode: "foo(node_indexˇix)".into(),
16277            expected_with_replace_mode: "foo(node_indexˇ)".into(),
16278            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
16279            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
16280        },
16281        Run {
16282            run_description: "Replace range ends before cursor - should extend to cursor",
16283            initial_state: "before editˇo after".into(),
16284            buffer_marked_text: "before <{ed}>it|o after".into(),
16285            completion_label: "editor",
16286            completion_text: "editor",
16287            expected_with_insert_mode: "before editorˇo after".into(),
16288            expected_with_replace_mode: "before editorˇo after".into(),
16289            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
16290            expected_with_replace_suffix_mode: "before editorˇo after".into(),
16291        },
16292        Run {
16293            run_description: "Uses label for suffix matching",
16294            initial_state: "before ediˇtor after".into(),
16295            buffer_marked_text: "before <edi|tor> after".into(),
16296            completion_label: "editor",
16297            completion_text: "editor()",
16298            expected_with_insert_mode: "before editor()ˇtor after".into(),
16299            expected_with_replace_mode: "before editor()ˇ after".into(),
16300            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
16301            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
16302        },
16303        Run {
16304            run_description: "Case insensitive subsequence and suffix matching",
16305            initial_state: "before EDiˇtoR after".into(),
16306            buffer_marked_text: "before <EDi|toR> after".into(),
16307            completion_label: "editor",
16308            completion_text: "editor",
16309            expected_with_insert_mode: "before editorˇtoR after".into(),
16310            expected_with_replace_mode: "before editorˇ after".into(),
16311            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
16312            expected_with_replace_suffix_mode: "before editorˇ after".into(),
16313        },
16314    ];
16315
16316    for run in runs {
16317        let run_variations = [
16318            (LspInsertMode::Insert, run.expected_with_insert_mode),
16319            (LspInsertMode::Replace, run.expected_with_replace_mode),
16320            (
16321                LspInsertMode::ReplaceSubsequence,
16322                run.expected_with_replace_subsequence_mode,
16323            ),
16324            (
16325                LspInsertMode::ReplaceSuffix,
16326                run.expected_with_replace_suffix_mode,
16327            ),
16328        ];
16329
16330        for (lsp_insert_mode, expected_text) in run_variations {
16331            eprintln!(
16332                "run = {:?}, mode = {lsp_insert_mode:.?}",
16333                run.run_description,
16334            );
16335
16336            update_test_language_settings(&mut cx, &|settings| {
16337                settings.defaults.completions = Some(CompletionSettingsContent {
16338                    lsp_insert_mode: Some(lsp_insert_mode),
16339                    words: Some(WordsCompletionMode::Disabled),
16340                    words_min_length: Some(0),
16341                    ..Default::default()
16342                });
16343            });
16344
16345            cx.set_state(&run.initial_state);
16346
16347            // Set up resolve handler before showing completions, since resolve may be
16348            // triggered when menu becomes visible (for documentation), not just on confirm.
16349            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
16350                move |_, _, _| async move {
16351                    Ok(lsp::CompletionItem {
16352                        additional_text_edits: None,
16353                        ..Default::default()
16354                    })
16355                },
16356            );
16357
16358            cx.update_editor(|editor, window, cx| {
16359                editor.show_completions(&ShowCompletions, window, cx);
16360            });
16361
16362            let counter = Arc::new(AtomicUsize::new(0));
16363            handle_completion_request_with_insert_and_replace(
16364                &mut cx,
16365                &run.buffer_marked_text,
16366                vec![(run.completion_label, run.completion_text)],
16367                counter.clone(),
16368            )
16369            .await;
16370            cx.condition(|editor, _| editor.context_menu_visible())
16371                .await;
16372            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16373
16374            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16375                editor
16376                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
16377                    .unwrap()
16378            });
16379            cx.assert_editor_state(&expected_text);
16380            apply_additional_edits.await.unwrap();
16381        }
16382    }
16383}
16384
16385#[gpui::test]
16386async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
16387    init_test(cx, |_| {});
16388    let mut cx = EditorLspTestContext::new_rust(
16389        lsp::ServerCapabilities {
16390            completion_provider: Some(lsp::CompletionOptions {
16391                resolve_provider: Some(true),
16392                ..Default::default()
16393            }),
16394            ..Default::default()
16395        },
16396        cx,
16397    )
16398    .await;
16399
16400    let initial_state = "SubˇError";
16401    let buffer_marked_text = "<Sub|Error>";
16402    let completion_text = "SubscriptionError";
16403    let expected_with_insert_mode = "SubscriptionErrorˇError";
16404    let expected_with_replace_mode = "SubscriptionErrorˇ";
16405
16406    update_test_language_settings(&mut cx, &|settings| {
16407        settings.defaults.completions = Some(CompletionSettingsContent {
16408            words: Some(WordsCompletionMode::Disabled),
16409            words_min_length: Some(0),
16410            // set the opposite here to ensure that the action is overriding the default behavior
16411            lsp_insert_mode: Some(LspInsertMode::Insert),
16412            ..Default::default()
16413        });
16414    });
16415
16416    cx.set_state(initial_state);
16417    cx.update_editor(|editor, window, cx| {
16418        editor.show_completions(&ShowCompletions, window, cx);
16419    });
16420
16421    let counter = Arc::new(AtomicUsize::new(0));
16422    handle_completion_request_with_insert_and_replace(
16423        &mut cx,
16424        buffer_marked_text,
16425        vec![(completion_text, completion_text)],
16426        counter.clone(),
16427    )
16428    .await;
16429    cx.condition(|editor, _| editor.context_menu_visible())
16430        .await;
16431    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16432
16433    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16434        editor
16435            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16436            .unwrap()
16437    });
16438    cx.assert_editor_state(expected_with_replace_mode);
16439    handle_resolve_completion_request(&mut cx, None).await;
16440    apply_additional_edits.await.unwrap();
16441
16442    update_test_language_settings(&mut cx, &|settings| {
16443        settings.defaults.completions = Some(CompletionSettingsContent {
16444            words: Some(WordsCompletionMode::Disabled),
16445            words_min_length: Some(0),
16446            // set the opposite here to ensure that the action is overriding the default behavior
16447            lsp_insert_mode: Some(LspInsertMode::Replace),
16448            ..Default::default()
16449        });
16450    });
16451
16452    cx.set_state(initial_state);
16453    cx.update_editor(|editor, window, cx| {
16454        editor.show_completions(&ShowCompletions, window, cx);
16455    });
16456    handle_completion_request_with_insert_and_replace(
16457        &mut cx,
16458        buffer_marked_text,
16459        vec![(completion_text, completion_text)],
16460        counter.clone(),
16461    )
16462    .await;
16463    cx.condition(|editor, _| editor.context_menu_visible())
16464        .await;
16465    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16466
16467    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16468        editor
16469            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
16470            .unwrap()
16471    });
16472    cx.assert_editor_state(expected_with_insert_mode);
16473    handle_resolve_completion_request(&mut cx, None).await;
16474    apply_additional_edits.await.unwrap();
16475}
16476
16477#[gpui::test]
16478async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
16479    init_test(cx, |_| {});
16480    let mut cx = EditorLspTestContext::new_rust(
16481        lsp::ServerCapabilities {
16482            completion_provider: Some(lsp::CompletionOptions {
16483                resolve_provider: Some(true),
16484                ..Default::default()
16485            }),
16486            ..Default::default()
16487        },
16488        cx,
16489    )
16490    .await;
16491
16492    // scenario: surrounding text matches completion text
16493    let completion_text = "to_offset";
16494    let initial_state = indoc! {"
16495        1. buf.to_offˇsuffix
16496        2. buf.to_offˇsuf
16497        3. buf.to_offˇfix
16498        4. buf.to_offˇ
16499        5. into_offˇensive
16500        6. ˇsuffix
16501        7. let ˇ //
16502        8. aaˇzz
16503        9. buf.to_off«zzzzzˇ»suffix
16504        10. buf.«ˇzzzzz»suffix
16505        11. to_off«ˇzzzzz»
16506
16507        buf.to_offˇsuffix  // newest cursor
16508    "};
16509    let completion_marked_buffer = indoc! {"
16510        1. buf.to_offsuffix
16511        2. buf.to_offsuf
16512        3. buf.to_offfix
16513        4. buf.to_off
16514        5. into_offensive
16515        6. suffix
16516        7. let  //
16517        8. aazz
16518        9. buf.to_offzzzzzsuffix
16519        10. buf.zzzzzsuffix
16520        11. to_offzzzzz
16521
16522        buf.<to_off|suffix>  // newest cursor
16523    "};
16524    let expected = indoc! {"
16525        1. buf.to_offsetˇ
16526        2. buf.to_offsetˇsuf
16527        3. buf.to_offsetˇfix
16528        4. buf.to_offsetˇ
16529        5. into_offsetˇensive
16530        6. to_offsetˇsuffix
16531        7. let to_offsetˇ //
16532        8. aato_offsetˇzz
16533        9. buf.to_offsetˇ
16534        10. buf.to_offsetˇsuffix
16535        11. to_offsetˇ
16536
16537        buf.to_offsetˇ  // newest cursor
16538    "};
16539    cx.set_state(initial_state);
16540    cx.update_editor(|editor, window, cx| {
16541        editor.show_completions(&ShowCompletions, window, cx);
16542    });
16543    handle_completion_request_with_insert_and_replace(
16544        &mut cx,
16545        completion_marked_buffer,
16546        vec![(completion_text, completion_text)],
16547        Arc::new(AtomicUsize::new(0)),
16548    )
16549    .await;
16550    cx.condition(|editor, _| editor.context_menu_visible())
16551        .await;
16552    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16553        editor
16554            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16555            .unwrap()
16556    });
16557    cx.assert_editor_state(expected);
16558    handle_resolve_completion_request(&mut cx, None).await;
16559    apply_additional_edits.await.unwrap();
16560
16561    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
16562    let completion_text = "foo_and_bar";
16563    let initial_state = indoc! {"
16564        1. ooanbˇ
16565        2. zooanbˇ
16566        3. ooanbˇz
16567        4. zooanbˇz
16568        5. ooanˇ
16569        6. oanbˇ
16570
16571        ooanbˇ
16572    "};
16573    let completion_marked_buffer = indoc! {"
16574        1. ooanb
16575        2. zooanb
16576        3. ooanbz
16577        4. zooanbz
16578        5. ooan
16579        6. oanb
16580
16581        <ooanb|>
16582    "};
16583    let expected = indoc! {"
16584        1. foo_and_barˇ
16585        2. zfoo_and_barˇ
16586        3. foo_and_barˇz
16587        4. zfoo_and_barˇz
16588        5. ooanfoo_and_barˇ
16589        6. oanbfoo_and_barˇ
16590
16591        foo_and_barˇ
16592    "};
16593    cx.set_state(initial_state);
16594    cx.update_editor(|editor, window, cx| {
16595        editor.show_completions(&ShowCompletions, window, cx);
16596    });
16597    handle_completion_request_with_insert_and_replace(
16598        &mut cx,
16599        completion_marked_buffer,
16600        vec![(completion_text, completion_text)],
16601        Arc::new(AtomicUsize::new(0)),
16602    )
16603    .await;
16604    cx.condition(|editor, _| editor.context_menu_visible())
16605        .await;
16606    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16607        editor
16608            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16609            .unwrap()
16610    });
16611    cx.assert_editor_state(expected);
16612    handle_resolve_completion_request(&mut cx, None).await;
16613    apply_additional_edits.await.unwrap();
16614
16615    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
16616    // (expects the same as if it was inserted at the end)
16617    let completion_text = "foo_and_bar";
16618    let initial_state = indoc! {"
16619        1. ooˇanb
16620        2. zooˇanb
16621        3. ooˇanbz
16622        4. zooˇanbz
16623
16624        ooˇanb
16625    "};
16626    let completion_marked_buffer = indoc! {"
16627        1. ooanb
16628        2. zooanb
16629        3. ooanbz
16630        4. zooanbz
16631
16632        <oo|anb>
16633    "};
16634    let expected = indoc! {"
16635        1. foo_and_barˇ
16636        2. zfoo_and_barˇ
16637        3. foo_and_barˇz
16638        4. zfoo_and_barˇz
16639
16640        foo_and_barˇ
16641    "};
16642    cx.set_state(initial_state);
16643    cx.update_editor(|editor, window, cx| {
16644        editor.show_completions(&ShowCompletions, window, cx);
16645    });
16646    handle_completion_request_with_insert_and_replace(
16647        &mut cx,
16648        completion_marked_buffer,
16649        vec![(completion_text, completion_text)],
16650        Arc::new(AtomicUsize::new(0)),
16651    )
16652    .await;
16653    cx.condition(|editor, _| editor.context_menu_visible())
16654        .await;
16655    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16656        editor
16657            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16658            .unwrap()
16659    });
16660    cx.assert_editor_state(expected);
16661    handle_resolve_completion_request(&mut cx, None).await;
16662    apply_additional_edits.await.unwrap();
16663}
16664
16665// This used to crash
16666#[gpui::test]
16667async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
16668    init_test(cx, |_| {});
16669
16670    let buffer_text = indoc! {"
16671        fn main() {
16672            10.satu;
16673
16674            //
16675            // separate1
16676            // separate2
16677            // separate3
16678            //
16679
16680            10.satu20;
16681        }
16682    "};
16683    let multibuffer_text_with_selections = indoc! {"
16684        fn main() {
16685            10.satuˇ;
16686
16687            //
16688
16689            10.satuˇ20;
16690        }
16691    "};
16692    let expected_multibuffer = indoc! {"
16693        fn main() {
16694            10.saturating_sub()ˇ;
16695
16696            //
16697
16698            10.saturating_sub()ˇ;
16699        }
16700    "};
16701
16702    let fs = FakeFs::new(cx.executor());
16703    fs.insert_tree(
16704        path!("/a"),
16705        json!({
16706            "main.rs": buffer_text,
16707        }),
16708    )
16709    .await;
16710
16711    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16713    language_registry.add(rust_lang());
16714    let mut fake_servers = language_registry.register_fake_lsp(
16715        "Rust",
16716        FakeLspAdapter {
16717            capabilities: lsp::ServerCapabilities {
16718                completion_provider: Some(lsp::CompletionOptions {
16719                    resolve_provider: None,
16720                    ..lsp::CompletionOptions::default()
16721                }),
16722                ..lsp::ServerCapabilities::default()
16723            },
16724            ..FakeLspAdapter::default()
16725        },
16726    );
16727    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16728    let workspace = window
16729        .read_with(cx, |mw, _| mw.workspace().clone())
16730        .unwrap();
16731    let cx = &mut VisualTestContext::from_window(*window, cx);
16732    let buffer = project
16733        .update(cx, |project, cx| {
16734            project.open_local_buffer(path!("/a/main.rs"), cx)
16735        })
16736        .await
16737        .unwrap();
16738
16739    let multi_buffer = cx.new(|cx| {
16740        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
16741        multi_buffer.set_excerpts_for_path(
16742            PathKey::sorted(0),
16743            buffer.clone(),
16744            [
16745                Point::zero()..Point::new(2, 0),
16746                Point::new(7, 0)..buffer.read(cx).max_point(),
16747            ],
16748            0,
16749            cx,
16750        );
16751        multi_buffer
16752    });
16753
16754    let editor = workspace.update_in(cx, |_, window, cx| {
16755        cx.new(|cx| {
16756            Editor::new(
16757                EditorMode::Full {
16758                    scale_ui_elements_with_buffer_font_size: false,
16759                    show_active_line_background: false,
16760                    sizing_behavior: SizingBehavior::Default,
16761                },
16762                multi_buffer.clone(),
16763                Some(project.clone()),
16764                window,
16765                cx,
16766            )
16767        })
16768    });
16769
16770    let pane = workspace.update_in(cx, |workspace, _, _| workspace.active_pane().clone());
16771    pane.update_in(cx, |pane, window, cx| {
16772        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
16773    });
16774
16775    let fake_server = fake_servers.next().await.unwrap();
16776    cx.run_until_parked();
16777
16778    editor.update_in(cx, |editor, window, cx| {
16779        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16780            s.select_ranges([
16781                Point::new(1, 11)..Point::new(1, 11),
16782                Point::new(5, 11)..Point::new(5, 11),
16783            ])
16784        });
16785
16786        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
16787    });
16788
16789    editor.update_in(cx, |editor, window, cx| {
16790        editor.show_completions(&ShowCompletions, window, cx);
16791    });
16792
16793    fake_server
16794        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16795            let completion_item = lsp::CompletionItem {
16796                label: "saturating_sub()".into(),
16797                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16798                    lsp::InsertReplaceEdit {
16799                        new_text: "saturating_sub()".to_owned(),
16800                        insert: lsp::Range::new(
16801                            lsp::Position::new(9, 7),
16802                            lsp::Position::new(9, 11),
16803                        ),
16804                        replace: lsp::Range::new(
16805                            lsp::Position::new(9, 7),
16806                            lsp::Position::new(9, 13),
16807                        ),
16808                    },
16809                )),
16810                ..lsp::CompletionItem::default()
16811            };
16812
16813            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
16814        })
16815        .next()
16816        .await
16817        .unwrap();
16818
16819    cx.condition(&editor, |editor, _| editor.context_menu_visible())
16820        .await;
16821
16822    editor
16823        .update_in(cx, |editor, window, cx| {
16824            editor
16825                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16826                .unwrap()
16827        })
16828        .await
16829        .unwrap();
16830
16831    editor.update(cx, |editor, cx| {
16832        assert_text_with_selections(editor, expected_multibuffer, cx);
16833    })
16834}
16835
16836#[gpui::test]
16837async fn test_completion(cx: &mut TestAppContext) {
16838    init_test(cx, |_| {});
16839
16840    let mut cx = EditorLspTestContext::new_rust(
16841        lsp::ServerCapabilities {
16842            completion_provider: Some(lsp::CompletionOptions {
16843                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16844                resolve_provider: Some(true),
16845                ..Default::default()
16846            }),
16847            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16848            ..Default::default()
16849        },
16850        cx,
16851    )
16852    .await;
16853    let counter = Arc::new(AtomicUsize::new(0));
16854
16855    cx.set_state(indoc! {"
16856        oneˇ
16857        two
16858        three
16859    "});
16860    cx.simulate_keystroke(".");
16861    handle_completion_request(
16862        indoc! {"
16863            one.|<>
16864            two
16865            three
16866        "},
16867        vec!["first_completion", "second_completion"],
16868        true,
16869        counter.clone(),
16870        &mut cx,
16871    )
16872    .await;
16873    cx.condition(|editor, _| editor.context_menu_visible())
16874        .await;
16875    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16876
16877    let _handler = handle_signature_help_request(
16878        &mut cx,
16879        lsp::SignatureHelp {
16880            signatures: vec![lsp::SignatureInformation {
16881                label: "test signature".to_string(),
16882                documentation: None,
16883                parameters: Some(vec![lsp::ParameterInformation {
16884                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
16885                    documentation: None,
16886                }]),
16887                active_parameter: None,
16888            }],
16889            active_signature: None,
16890            active_parameter: None,
16891        },
16892    );
16893    cx.update_editor(|editor, window, cx| {
16894        assert!(
16895            !editor.signature_help_state.is_shown(),
16896            "No signature help was called for"
16897        );
16898        editor.show_signature_help(&ShowSignatureHelp, window, cx);
16899    });
16900    cx.run_until_parked();
16901    cx.update_editor(|editor, _, _| {
16902        assert!(
16903            !editor.signature_help_state.is_shown(),
16904            "No signature help should be shown when completions menu is open"
16905        );
16906    });
16907
16908    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16909        editor.context_menu_next(&Default::default(), window, cx);
16910        editor
16911            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16912            .unwrap()
16913    });
16914    cx.assert_editor_state(indoc! {"
16915        one.second_completionˇ
16916        two
16917        three
16918    "});
16919
16920    handle_resolve_completion_request(
16921        &mut cx,
16922        Some(vec![
16923            (
16924                //This overlaps with the primary completion edit which is
16925                //misbehavior from the LSP spec, test that we filter it out
16926                indoc! {"
16927                    one.second_ˇcompletion
16928                    two
16929                    threeˇ
16930                "},
16931                "overlapping additional edit",
16932            ),
16933            (
16934                indoc! {"
16935                    one.second_completion
16936                    two
16937                    threeˇ
16938                "},
16939                "\nadditional edit",
16940            ),
16941        ]),
16942    )
16943    .await;
16944    apply_additional_edits.await.unwrap();
16945    cx.assert_editor_state(indoc! {"
16946        one.second_completionˇ
16947        two
16948        three
16949        additional edit
16950    "});
16951
16952    cx.set_state(indoc! {"
16953        one.second_completion
16954        twoˇ
16955        threeˇ
16956        additional edit
16957    "});
16958    cx.simulate_keystroke(" ");
16959    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16960    cx.simulate_keystroke("s");
16961    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16962
16963    cx.assert_editor_state(indoc! {"
16964        one.second_completion
16965        two sˇ
16966        three sˇ
16967        additional edit
16968    "});
16969    handle_completion_request(
16970        indoc! {"
16971            one.second_completion
16972            two s
16973            three <s|>
16974            additional edit
16975        "},
16976        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16977        true,
16978        counter.clone(),
16979        &mut cx,
16980    )
16981    .await;
16982    cx.condition(|editor, _| editor.context_menu_visible())
16983        .await;
16984    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16985
16986    cx.simulate_keystroke("i");
16987
16988    handle_completion_request(
16989        indoc! {"
16990            one.second_completion
16991            two si
16992            three <si|>
16993            additional edit
16994        "},
16995        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16996        true,
16997        counter.clone(),
16998        &mut cx,
16999    )
17000    .await;
17001    cx.condition(|editor, _| editor.context_menu_visible())
17002        .await;
17003    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
17004
17005    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17006        editor
17007            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17008            .unwrap()
17009    });
17010    cx.assert_editor_state(indoc! {"
17011        one.second_completion
17012        two sixth_completionˇ
17013        three sixth_completionˇ
17014        additional edit
17015    "});
17016
17017    apply_additional_edits.await.unwrap();
17018
17019    update_test_language_settings(&mut cx, &|settings| {
17020        settings.defaults.show_completions_on_input = Some(false);
17021    });
17022    cx.set_state("editorˇ");
17023    cx.simulate_keystroke(".");
17024    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
17025    cx.simulate_keystrokes("c l o");
17026    cx.assert_editor_state("editor.cloˇ");
17027    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
17028    cx.update_editor(|editor, window, cx| {
17029        editor.show_completions(&ShowCompletions, window, cx);
17030    });
17031    handle_completion_request(
17032        "editor.<clo|>",
17033        vec!["close", "clobber"],
17034        true,
17035        counter.clone(),
17036        &mut cx,
17037    )
17038    .await;
17039    cx.condition(|editor, _| editor.context_menu_visible())
17040        .await;
17041    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
17042
17043    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17044        editor
17045            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17046            .unwrap()
17047    });
17048    cx.assert_editor_state("editor.clobberˇ");
17049    handle_resolve_completion_request(&mut cx, None).await;
17050    apply_additional_edits.await.unwrap();
17051}
17052
17053#[gpui::test]
17054async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
17055    init_test(cx, |_| {});
17056
17057    let fs = FakeFs::new(cx.executor());
17058    fs.insert_tree(
17059        path!("/a"),
17060        json!({
17061            "main.rs": "",
17062        }),
17063    )
17064    .await;
17065
17066    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17067    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17068    language_registry.add(rust_lang());
17069    let command_calls = Arc::new(AtomicUsize::new(0));
17070    let registered_command = "_the/command";
17071
17072    let closure_command_calls = command_calls.clone();
17073    let mut fake_servers = language_registry.register_fake_lsp(
17074        "Rust",
17075        FakeLspAdapter {
17076            capabilities: lsp::ServerCapabilities {
17077                completion_provider: Some(lsp::CompletionOptions {
17078                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17079                    ..lsp::CompletionOptions::default()
17080                }),
17081                execute_command_provider: Some(lsp::ExecuteCommandOptions {
17082                    commands: vec![registered_command.to_owned()],
17083                    ..lsp::ExecuteCommandOptions::default()
17084                }),
17085                ..lsp::ServerCapabilities::default()
17086            },
17087            initializer: Some(Box::new(move |fake_server| {
17088                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
17089                    move |params, _| async move {
17090                        Ok(Some(lsp::CompletionResponse::Array(vec![
17091                            lsp::CompletionItem {
17092                                label: "registered_command".to_owned(),
17093                                text_edit: gen_text_edit(&params, ""),
17094                                command: Some(lsp::Command {
17095                                    title: registered_command.to_owned(),
17096                                    command: "_the/command".to_owned(),
17097                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
17098                                }),
17099                                ..lsp::CompletionItem::default()
17100                            },
17101                            lsp::CompletionItem {
17102                                label: "unregistered_command".to_owned(),
17103                                text_edit: gen_text_edit(&params, ""),
17104                                command: Some(lsp::Command {
17105                                    title: "????????????".to_owned(),
17106                                    command: "????????????".to_owned(),
17107                                    arguments: Some(vec![serde_json::Value::Null]),
17108                                }),
17109                                ..lsp::CompletionItem::default()
17110                            },
17111                        ])))
17112                    },
17113                );
17114                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
17115                    let command_calls = closure_command_calls.clone();
17116                    move |params, _| {
17117                        assert_eq!(params.command, registered_command);
17118                        let command_calls = command_calls.clone();
17119                        async move {
17120                            command_calls.fetch_add(1, atomic::Ordering::Release);
17121                            Ok(Some(json!(null)))
17122                        }
17123                    }
17124                });
17125            })),
17126            ..FakeLspAdapter::default()
17127        },
17128    );
17129    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
17130    let workspace = window
17131        .read_with(cx, |mw, _| mw.workspace().clone())
17132        .unwrap();
17133    let cx = &mut VisualTestContext::from_window(*window, cx);
17134    let editor = workspace
17135        .update_in(cx, |workspace, window, cx| {
17136            workspace.open_abs_path(
17137                PathBuf::from(path!("/a/main.rs")),
17138                OpenOptions::default(),
17139                window,
17140                cx,
17141            )
17142        })
17143        .await
17144        .unwrap()
17145        .downcast::<Editor>()
17146        .unwrap();
17147    let _fake_server = fake_servers.next().await.unwrap();
17148    cx.run_until_parked();
17149
17150    editor.update_in(cx, |editor, window, cx| {
17151        cx.focus_self(window);
17152        editor.move_to_end(&MoveToEnd, window, cx);
17153        editor.handle_input(".", window, cx);
17154    });
17155    cx.run_until_parked();
17156    editor.update(cx, |editor, _| {
17157        assert!(editor.context_menu_visible());
17158        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17159        {
17160            let completion_labels = menu
17161                .completions
17162                .borrow()
17163                .iter()
17164                .map(|c| c.label.text.clone())
17165                .collect::<Vec<_>>();
17166            assert_eq!(
17167                completion_labels,
17168                &["registered_command", "unregistered_command",],
17169            );
17170        } else {
17171            panic!("expected completion menu to be open");
17172        }
17173    });
17174
17175    editor
17176        .update_in(cx, |editor, window, cx| {
17177            editor
17178                .confirm_completion(&ConfirmCompletion::default(), window, cx)
17179                .unwrap()
17180        })
17181        .await
17182        .unwrap();
17183    cx.run_until_parked();
17184    assert_eq!(
17185        command_calls.load(atomic::Ordering::Acquire),
17186        1,
17187        "For completion with a registered command, Zed should send a command execution request",
17188    );
17189
17190    editor.update_in(cx, |editor, window, cx| {
17191        cx.focus_self(window);
17192        editor.handle_input(".", window, cx);
17193    });
17194    cx.run_until_parked();
17195    editor.update(cx, |editor, _| {
17196        assert!(editor.context_menu_visible());
17197        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17198        {
17199            let completion_labels = menu
17200                .completions
17201                .borrow()
17202                .iter()
17203                .map(|c| c.label.text.clone())
17204                .collect::<Vec<_>>();
17205            assert_eq!(
17206                completion_labels,
17207                &["registered_command", "unregistered_command",],
17208            );
17209        } else {
17210            panic!("expected completion menu to be open");
17211        }
17212    });
17213    editor
17214        .update_in(cx, |editor, window, cx| {
17215            editor.context_menu_next(&Default::default(), window, cx);
17216            editor
17217                .confirm_completion(&ConfirmCompletion::default(), window, cx)
17218                .unwrap()
17219        })
17220        .await
17221        .unwrap();
17222    cx.run_until_parked();
17223    assert_eq!(
17224        command_calls.load(atomic::Ordering::Acquire),
17225        1,
17226        "For completion with an unregistered command, Zed should not send a command execution request",
17227    );
17228}
17229
17230#[gpui::test]
17231async fn test_completion_reuse(cx: &mut TestAppContext) {
17232    init_test(cx, |_| {});
17233
17234    let mut cx = EditorLspTestContext::new_rust(
17235        lsp::ServerCapabilities {
17236            completion_provider: Some(lsp::CompletionOptions {
17237                trigger_characters: Some(vec![".".to_string()]),
17238                ..Default::default()
17239            }),
17240            ..Default::default()
17241        },
17242        cx,
17243    )
17244    .await;
17245
17246    let counter = Arc::new(AtomicUsize::new(0));
17247    cx.set_state("objˇ");
17248    cx.simulate_keystroke(".");
17249
17250    // Initial completion request returns complete results
17251    let is_incomplete = false;
17252    handle_completion_request(
17253        "obj.|<>",
17254        vec!["a", "ab", "abc"],
17255        is_incomplete,
17256        counter.clone(),
17257        &mut cx,
17258    )
17259    .await;
17260    cx.run_until_parked();
17261    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17262    cx.assert_editor_state("obj.ˇ");
17263    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
17264
17265    // Type "a" - filters existing completions
17266    cx.simulate_keystroke("a");
17267    cx.run_until_parked();
17268    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17269    cx.assert_editor_state("obj.aˇ");
17270    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
17271
17272    // Type "b" - filters existing completions
17273    cx.simulate_keystroke("b");
17274    cx.run_until_parked();
17275    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17276    cx.assert_editor_state("obj.abˇ");
17277    check_displayed_completions(vec!["ab", "abc"], &mut cx);
17278
17279    // Type "c" - filters existing completions
17280    cx.simulate_keystroke("c");
17281    cx.run_until_parked();
17282    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17283    cx.assert_editor_state("obj.abcˇ");
17284    check_displayed_completions(vec!["abc"], &mut cx);
17285
17286    // Backspace to delete "c" - filters existing completions
17287    cx.update_editor(|editor, window, cx| {
17288        editor.backspace(&Backspace, window, cx);
17289    });
17290    cx.run_until_parked();
17291    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17292    cx.assert_editor_state("obj.abˇ");
17293    check_displayed_completions(vec!["ab", "abc"], &mut cx);
17294
17295    // Moving cursor to the left dismisses menu.
17296    cx.update_editor(|editor, window, cx| {
17297        editor.move_left(&MoveLeft, window, cx);
17298    });
17299    cx.run_until_parked();
17300    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17301    cx.assert_editor_state("obj.aˇb");
17302    cx.update_editor(|editor, _, _| {
17303        assert_eq!(editor.context_menu_visible(), false);
17304    });
17305
17306    // Type "b" - new request
17307    cx.simulate_keystroke("b");
17308    let is_incomplete = false;
17309    handle_completion_request(
17310        "obj.<ab|>a",
17311        vec!["ab", "abc"],
17312        is_incomplete,
17313        counter.clone(),
17314        &mut cx,
17315    )
17316    .await;
17317    cx.run_until_parked();
17318    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
17319    cx.assert_editor_state("obj.abˇb");
17320    check_displayed_completions(vec!["ab", "abc"], &mut cx);
17321
17322    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
17323    cx.update_editor(|editor, window, cx| {
17324        editor.backspace(&Backspace, window, cx);
17325    });
17326    let is_incomplete = false;
17327    handle_completion_request(
17328        "obj.<a|>b",
17329        vec!["a", "ab", "abc"],
17330        is_incomplete,
17331        counter.clone(),
17332        &mut cx,
17333    )
17334    .await;
17335    cx.run_until_parked();
17336    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
17337    cx.assert_editor_state("obj.aˇb");
17338    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
17339
17340    // Backspace to delete "a" - dismisses menu.
17341    cx.update_editor(|editor, window, cx| {
17342        editor.backspace(&Backspace, window, cx);
17343    });
17344    cx.run_until_parked();
17345    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
17346    cx.assert_editor_state("obj.ˇb");
17347    cx.update_editor(|editor, _, _| {
17348        assert_eq!(editor.context_menu_visible(), false);
17349    });
17350}
17351
17352#[gpui::test]
17353async fn test_word_completion(cx: &mut TestAppContext) {
17354    let lsp_fetch_timeout_ms = 10;
17355    init_test(cx, |language_settings| {
17356        language_settings.defaults.completions = Some(CompletionSettingsContent {
17357            words_min_length: Some(0),
17358            lsp_fetch_timeout_ms: Some(10),
17359            lsp_insert_mode: Some(LspInsertMode::Insert),
17360            ..Default::default()
17361        });
17362    });
17363
17364    let mut cx = EditorLspTestContext::new_rust(
17365        lsp::ServerCapabilities {
17366            completion_provider: Some(lsp::CompletionOptions {
17367                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17368                ..lsp::CompletionOptions::default()
17369            }),
17370            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17371            ..lsp::ServerCapabilities::default()
17372        },
17373        cx,
17374    )
17375    .await;
17376
17377    let throttle_completions = Arc::new(AtomicBool::new(false));
17378
17379    let lsp_throttle_completions = throttle_completions.clone();
17380    let _completion_requests_handler =
17381        cx.lsp
17382            .server
17383            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
17384                let lsp_throttle_completions = lsp_throttle_completions.clone();
17385                let cx = cx.clone();
17386                async move {
17387                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
17388                        cx.background_executor()
17389                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
17390                            .await;
17391                    }
17392                    Ok(Some(lsp::CompletionResponse::Array(vec![
17393                        lsp::CompletionItem {
17394                            label: "first".into(),
17395                            ..lsp::CompletionItem::default()
17396                        },
17397                        lsp::CompletionItem {
17398                            label: "last".into(),
17399                            ..lsp::CompletionItem::default()
17400                        },
17401                    ])))
17402                }
17403            });
17404
17405    cx.set_state(indoc! {"
17406        oneˇ
17407        two
17408        three
17409    "});
17410    cx.simulate_keystroke(".");
17411    cx.executor().run_until_parked();
17412    cx.condition(|editor, _| editor.context_menu_visible())
17413        .await;
17414    cx.update_editor(|editor, window, cx| {
17415        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17416        {
17417            assert_eq!(
17418                completion_menu_entries(menu),
17419                &["first", "last"],
17420                "When LSP server is fast to reply, no fallback word completions are used"
17421            );
17422        } else {
17423            panic!("expected completion menu to be open");
17424        }
17425        editor.cancel(&Cancel, window, cx);
17426    });
17427    cx.executor().run_until_parked();
17428    cx.condition(|editor, _| !editor.context_menu_visible())
17429        .await;
17430
17431    throttle_completions.store(true, atomic::Ordering::Release);
17432    cx.simulate_keystroke(".");
17433    cx.executor()
17434        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
17435    cx.executor().run_until_parked();
17436    cx.condition(|editor, _| editor.context_menu_visible())
17437        .await;
17438    cx.update_editor(|editor, _, _| {
17439        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17440        {
17441            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
17442                "When LSP server is slow, document words can be shown instead, if configured accordingly");
17443        } else {
17444            panic!("expected completion menu to be open");
17445        }
17446    });
17447}
17448
17449#[gpui::test]
17450async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
17451    init_test(cx, |language_settings| {
17452        language_settings.defaults.completions = Some(CompletionSettingsContent {
17453            words: Some(WordsCompletionMode::Enabled),
17454            words_min_length: Some(0),
17455            lsp_insert_mode: Some(LspInsertMode::Insert),
17456            ..Default::default()
17457        });
17458    });
17459
17460    let mut cx = EditorLspTestContext::new_rust(
17461        lsp::ServerCapabilities {
17462            completion_provider: Some(lsp::CompletionOptions {
17463                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17464                ..lsp::CompletionOptions::default()
17465            }),
17466            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17467            ..lsp::ServerCapabilities::default()
17468        },
17469        cx,
17470    )
17471    .await;
17472
17473    let _completion_requests_handler =
17474        cx.lsp
17475            .server
17476            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
17477                Ok(Some(lsp::CompletionResponse::Array(vec![
17478                    lsp::CompletionItem {
17479                        label: "first".into(),
17480                        ..lsp::CompletionItem::default()
17481                    },
17482                    lsp::CompletionItem {
17483                        label: "last".into(),
17484                        ..lsp::CompletionItem::default()
17485                    },
17486                ])))
17487            });
17488
17489    cx.set_state(indoc! {"ˇ
17490        first
17491        last
17492        second
17493    "});
17494    cx.simulate_keystroke(".");
17495    cx.executor().run_until_parked();
17496    cx.condition(|editor, _| editor.context_menu_visible())
17497        .await;
17498    cx.update_editor(|editor, _, _| {
17499        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17500        {
17501            assert_eq!(
17502                completion_menu_entries(menu),
17503                &["first", "last", "second"],
17504                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
17505            );
17506        } else {
17507            panic!("expected completion menu to be open");
17508        }
17509    });
17510}
17511
17512#[gpui::test]
17513async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
17514    init_test(cx, |language_settings| {
17515        language_settings.defaults.completions = Some(CompletionSettingsContent {
17516            words: Some(WordsCompletionMode::Disabled),
17517            words_min_length: Some(0),
17518            lsp_insert_mode: Some(LspInsertMode::Insert),
17519            ..Default::default()
17520        });
17521    });
17522
17523    let mut cx = EditorLspTestContext::new_rust(
17524        lsp::ServerCapabilities {
17525            completion_provider: Some(lsp::CompletionOptions {
17526                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17527                ..lsp::CompletionOptions::default()
17528            }),
17529            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17530            ..lsp::ServerCapabilities::default()
17531        },
17532        cx,
17533    )
17534    .await;
17535
17536    let _completion_requests_handler =
17537        cx.lsp
17538            .server
17539            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
17540                panic!("LSP completions should not be queried when dealing with word completions")
17541            });
17542
17543    cx.set_state(indoc! {"ˇ
17544        first
17545        last
17546        second
17547    "});
17548    cx.update_editor(|editor, window, cx| {
17549        editor.show_word_completions(&ShowWordCompletions, window, cx);
17550    });
17551    cx.executor().run_until_parked();
17552    cx.condition(|editor, _| editor.context_menu_visible())
17553        .await;
17554    cx.update_editor(|editor, _, _| {
17555        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17556        {
17557            assert_eq!(
17558                completion_menu_entries(menu),
17559                &["first", "last", "second"],
17560                "`ShowWordCompletions` action should show word completions"
17561            );
17562        } else {
17563            panic!("expected completion menu to be open");
17564        }
17565    });
17566
17567    cx.simulate_keystroke("l");
17568    cx.executor().run_until_parked();
17569    cx.condition(|editor, _| editor.context_menu_visible())
17570        .await;
17571    cx.update_editor(|editor, _, _| {
17572        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17573        {
17574            assert_eq!(
17575                completion_menu_entries(menu),
17576                &["last"],
17577                "After showing word completions, further editing should filter them and not query the LSP"
17578            );
17579        } else {
17580            panic!("expected completion menu to be open");
17581        }
17582    });
17583}
17584
17585#[gpui::test]
17586async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
17587    init_test(cx, |language_settings| {
17588        language_settings.defaults.completions = Some(CompletionSettingsContent {
17589            words_min_length: Some(0),
17590            lsp: Some(false),
17591            lsp_insert_mode: Some(LspInsertMode::Insert),
17592            ..Default::default()
17593        });
17594    });
17595
17596    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17597
17598    cx.set_state(indoc! {"ˇ
17599        0_usize
17600        let
17601        33
17602        4.5f32
17603    "});
17604    cx.update_editor(|editor, window, cx| {
17605        editor.show_completions(&ShowCompletions, window, cx);
17606    });
17607    cx.executor().run_until_parked();
17608    cx.condition(|editor, _| editor.context_menu_visible())
17609        .await;
17610    cx.update_editor(|editor, window, cx| {
17611        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17612        {
17613            assert_eq!(
17614                completion_menu_entries(menu),
17615                &["let"],
17616                "With no digits in the completion query, no digits should be in the word completions"
17617            );
17618        } else {
17619            panic!("expected completion menu to be open");
17620        }
17621        editor.cancel(&Cancel, window, cx);
17622    });
17623
17624    cx.set_state(indoc! {"17625        0_usize
17626        let
17627        3
17628        33.35f32
17629    "});
17630    cx.update_editor(|editor, window, cx| {
17631        editor.show_completions(&ShowCompletions, window, cx);
17632    });
17633    cx.executor().run_until_parked();
17634    cx.condition(|editor, _| editor.context_menu_visible())
17635        .await;
17636    cx.update_editor(|editor, _, _| {
17637        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17638        {
17639            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
17640                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
17641        } else {
17642            panic!("expected completion menu to be open");
17643        }
17644    });
17645}
17646
17647#[gpui::test]
17648async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
17649    init_test(cx, |language_settings| {
17650        language_settings.defaults.completions = Some(CompletionSettingsContent {
17651            words: Some(WordsCompletionMode::Enabled),
17652            words_min_length: Some(3),
17653            lsp_insert_mode: Some(LspInsertMode::Insert),
17654            ..Default::default()
17655        });
17656    });
17657
17658    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17659    cx.set_state(indoc! {"ˇ
17660        wow
17661        wowen
17662        wowser
17663    "});
17664    cx.simulate_keystroke("w");
17665    cx.executor().run_until_parked();
17666    cx.update_editor(|editor, _, _| {
17667        if editor.context_menu.borrow_mut().is_some() {
17668            panic!(
17669                "expected completion menu to be hidden, as words completion threshold is not met"
17670            );
17671        }
17672    });
17673
17674    cx.update_editor(|editor, window, cx| {
17675        editor.show_word_completions(&ShowWordCompletions, window, cx);
17676    });
17677    cx.executor().run_until_parked();
17678    cx.update_editor(|editor, window, cx| {
17679        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17680        {
17681            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");
17682        } else {
17683            panic!("expected completion menu to be open after the word completions are called with an action");
17684        }
17685
17686        editor.cancel(&Cancel, window, cx);
17687    });
17688    cx.update_editor(|editor, _, _| {
17689        if editor.context_menu.borrow_mut().is_some() {
17690            panic!("expected completion menu to be hidden after canceling");
17691        }
17692    });
17693
17694    cx.simulate_keystroke("o");
17695    cx.executor().run_until_parked();
17696    cx.update_editor(|editor, _, _| {
17697        if editor.context_menu.borrow_mut().is_some() {
17698            panic!(
17699                "expected completion menu to be hidden, as words completion threshold is not met still"
17700            );
17701        }
17702    });
17703
17704    cx.simulate_keystroke("w");
17705    cx.executor().run_until_parked();
17706    cx.update_editor(|editor, _, _| {
17707        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17708        {
17709            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
17710        } else {
17711            panic!("expected completion menu to be open after the word completions threshold is met");
17712        }
17713    });
17714}
17715
17716#[gpui::test]
17717async fn test_word_completions_disabled(cx: &mut TestAppContext) {
17718    init_test(cx, |language_settings| {
17719        language_settings.defaults.completions = Some(CompletionSettingsContent {
17720            words: Some(WordsCompletionMode::Enabled),
17721            words_min_length: Some(0),
17722            lsp_insert_mode: Some(LspInsertMode::Insert),
17723            ..Default::default()
17724        });
17725    });
17726
17727    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17728    cx.update_editor(|editor, _, _| {
17729        editor.disable_word_completions();
17730    });
17731    cx.set_state(indoc! {"ˇ
17732        wow
17733        wowen
17734        wowser
17735    "});
17736    cx.simulate_keystroke("w");
17737    cx.executor().run_until_parked();
17738    cx.update_editor(|editor, _, _| {
17739        if editor.context_menu.borrow_mut().is_some() {
17740            panic!(
17741                "expected completion menu to be hidden, as words completion are disabled for this editor"
17742            );
17743        }
17744    });
17745
17746    cx.update_editor(|editor, window, cx| {
17747        editor.show_word_completions(&ShowWordCompletions, window, cx);
17748    });
17749    cx.executor().run_until_parked();
17750    cx.update_editor(|editor, _, _| {
17751        if editor.context_menu.borrow_mut().is_some() {
17752            panic!(
17753                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
17754            );
17755        }
17756    });
17757}
17758
17759#[gpui::test]
17760async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
17761    init_test(cx, |language_settings| {
17762        language_settings.defaults.completions = Some(CompletionSettingsContent {
17763            words: Some(WordsCompletionMode::Disabled),
17764            words_min_length: Some(0),
17765            lsp_insert_mode: Some(LspInsertMode::Insert),
17766            ..Default::default()
17767        });
17768    });
17769
17770    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17771    cx.update_editor(|editor, _, _| {
17772        editor.set_completion_provider(None);
17773    });
17774    cx.set_state(indoc! {"ˇ
17775        wow
17776        wowen
17777        wowser
17778    "});
17779    cx.simulate_keystroke("w");
17780    cx.executor().run_until_parked();
17781    cx.update_editor(|editor, _, _| {
17782        if editor.context_menu.borrow_mut().is_some() {
17783            panic!("expected completion menu to be hidden, as disabled in settings");
17784        }
17785    });
17786}
17787
17788fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
17789    let position = || lsp::Position {
17790        line: params.text_document_position.position.line,
17791        character: params.text_document_position.position.character,
17792    };
17793    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17794        range: lsp::Range {
17795            start: position(),
17796            end: position(),
17797        },
17798        new_text: text.to_string(),
17799    }))
17800}
17801
17802#[gpui::test]
17803async fn test_multiline_completion(cx: &mut TestAppContext) {
17804    init_test(cx, |_| {});
17805
17806    let fs = FakeFs::new(cx.executor());
17807    fs.insert_tree(
17808        path!("/a"),
17809        json!({
17810            "main.ts": "a",
17811        }),
17812    )
17813    .await;
17814
17815    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17816    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17817    let typescript_language = Arc::new(Language::new(
17818        LanguageConfig {
17819            name: "TypeScript".into(),
17820            matcher: LanguageMatcher {
17821                path_suffixes: vec!["ts".to_string()],
17822                ..LanguageMatcher::default()
17823            },
17824            line_comments: vec!["// ".into()],
17825            ..LanguageConfig::default()
17826        },
17827        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17828    ));
17829    language_registry.add(typescript_language.clone());
17830    let mut fake_servers = language_registry.register_fake_lsp(
17831        "TypeScript",
17832        FakeLspAdapter {
17833            capabilities: lsp::ServerCapabilities {
17834                completion_provider: Some(lsp::CompletionOptions {
17835                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17836                    ..lsp::CompletionOptions::default()
17837                }),
17838                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17839                ..lsp::ServerCapabilities::default()
17840            },
17841            // Emulate vtsls label generation
17842            label_for_completion: Some(Box::new(|item, _| {
17843                let text = if let Some(description) = item
17844                    .label_details
17845                    .as_ref()
17846                    .and_then(|label_details| label_details.description.as_ref())
17847                {
17848                    format!("{} {}", item.label, description)
17849                } else if let Some(detail) = &item.detail {
17850                    format!("{} {}", item.label, detail)
17851                } else {
17852                    item.label.clone()
17853                };
17854                Some(language::CodeLabel::plain(text, None))
17855            })),
17856            ..FakeLspAdapter::default()
17857        },
17858    );
17859    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
17860    let workspace = window
17861        .read_with(cx, |mw, _| mw.workspace().clone())
17862        .unwrap();
17863    let cx = &mut VisualTestContext::from_window(*window, cx);
17864    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
17865        workspace.project().update(cx, |project, cx| {
17866            project.worktrees(cx).next().unwrap().read(cx).id()
17867        })
17868    });
17869
17870    let _buffer = project
17871        .update(cx, |project, cx| {
17872            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
17873        })
17874        .await
17875        .unwrap();
17876    let editor = workspace
17877        .update_in(cx, |workspace, window, cx| {
17878            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
17879        })
17880        .await
17881        .unwrap()
17882        .downcast::<Editor>()
17883        .unwrap();
17884    let fake_server = fake_servers.next().await.unwrap();
17885    cx.run_until_parked();
17886
17887    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
17888    let multiline_label_2 = "a\nb\nc\n";
17889    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
17890    let multiline_description = "d\ne\nf\n";
17891    let multiline_detail_2 = "g\nh\ni\n";
17892
17893    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
17894        move |params, _| async move {
17895            Ok(Some(lsp::CompletionResponse::Array(vec![
17896                lsp::CompletionItem {
17897                    label: multiline_label.to_string(),
17898                    text_edit: gen_text_edit(&params, "new_text_1"),
17899                    ..lsp::CompletionItem::default()
17900                },
17901                lsp::CompletionItem {
17902                    label: "single line label 1".to_string(),
17903                    detail: Some(multiline_detail.to_string()),
17904                    text_edit: gen_text_edit(&params, "new_text_2"),
17905                    ..lsp::CompletionItem::default()
17906                },
17907                lsp::CompletionItem {
17908                    label: "single line label 2".to_string(),
17909                    label_details: Some(lsp::CompletionItemLabelDetails {
17910                        description: Some(multiline_description.to_string()),
17911                        detail: None,
17912                    }),
17913                    text_edit: gen_text_edit(&params, "new_text_2"),
17914                    ..lsp::CompletionItem::default()
17915                },
17916                lsp::CompletionItem {
17917                    label: multiline_label_2.to_string(),
17918                    detail: Some(multiline_detail_2.to_string()),
17919                    text_edit: gen_text_edit(&params, "new_text_3"),
17920                    ..lsp::CompletionItem::default()
17921                },
17922                lsp::CompletionItem {
17923                    label: "Label with many     spaces and \t but without newlines".to_string(),
17924                    detail: Some(
17925                        "Details with many     spaces and \t but without newlines".to_string(),
17926                    ),
17927                    text_edit: gen_text_edit(&params, "new_text_4"),
17928                    ..lsp::CompletionItem::default()
17929                },
17930            ])))
17931        },
17932    );
17933
17934    editor.update_in(cx, |editor, window, cx| {
17935        cx.focus_self(window);
17936        editor.move_to_end(&MoveToEnd, window, cx);
17937        editor.handle_input(".", window, cx);
17938    });
17939    cx.run_until_parked();
17940    completion_handle.next().await.unwrap();
17941
17942    editor.update(cx, |editor, _| {
17943        assert!(editor.context_menu_visible());
17944        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17945        {
17946            let completion_labels = menu
17947                .completions
17948                .borrow()
17949                .iter()
17950                .map(|c| c.label.text.clone())
17951                .collect::<Vec<_>>();
17952            assert_eq!(
17953                completion_labels,
17954                &[
17955                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
17956                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
17957                    "single line label 2 d e f ",
17958                    "a b c g h i ",
17959                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
17960                ],
17961                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
17962            );
17963
17964            for completion in menu
17965                .completions
17966                .borrow()
17967                .iter() {
17968                    assert_eq!(
17969                        completion.label.filter_range,
17970                        0..completion.label.text.len(),
17971                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
17972                    );
17973                }
17974        } else {
17975            panic!("expected completion menu to be open");
17976        }
17977    });
17978}
17979
17980#[gpui::test]
17981async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
17982    init_test(cx, |_| {});
17983    let mut cx = EditorLspTestContext::new_rust(
17984        lsp::ServerCapabilities {
17985            completion_provider: Some(lsp::CompletionOptions {
17986                trigger_characters: Some(vec![".".to_string()]),
17987                ..Default::default()
17988            }),
17989            ..Default::default()
17990        },
17991        cx,
17992    )
17993    .await;
17994    cx.lsp
17995        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17996            Ok(Some(lsp::CompletionResponse::Array(vec![
17997                lsp::CompletionItem {
17998                    label: "first".into(),
17999                    ..Default::default()
18000                },
18001                lsp::CompletionItem {
18002                    label: "last".into(),
18003                    ..Default::default()
18004                },
18005            ])))
18006        });
18007    cx.set_state("variableˇ");
18008    cx.simulate_keystroke(".");
18009    cx.executor().run_until_parked();
18010
18011    cx.update_editor(|editor, _, _| {
18012        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18013        {
18014            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
18015        } else {
18016            panic!("expected completion menu to be open");
18017        }
18018    });
18019
18020    cx.update_editor(|editor, window, cx| {
18021        editor.move_page_down(&MovePageDown::default(), window, cx);
18022        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18023        {
18024            assert!(
18025                menu.selected_item == 1,
18026                "expected PageDown to select the last item from the context menu"
18027            );
18028        } else {
18029            panic!("expected completion menu to stay open after PageDown");
18030        }
18031    });
18032
18033    cx.update_editor(|editor, window, cx| {
18034        editor.move_page_up(&MovePageUp::default(), window, cx);
18035        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18036        {
18037            assert!(
18038                menu.selected_item == 0,
18039                "expected PageUp to select the first item from the context menu"
18040            );
18041        } else {
18042            panic!("expected completion menu to stay open after PageUp");
18043        }
18044    });
18045}
18046
18047#[gpui::test]
18048async fn test_as_is_completions(cx: &mut TestAppContext) {
18049    init_test(cx, |_| {});
18050    let mut cx = EditorLspTestContext::new_rust(
18051        lsp::ServerCapabilities {
18052            completion_provider: Some(lsp::CompletionOptions {
18053                ..Default::default()
18054            }),
18055            ..Default::default()
18056        },
18057        cx,
18058    )
18059    .await;
18060    cx.lsp
18061        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18062            Ok(Some(lsp::CompletionResponse::Array(vec![
18063                lsp::CompletionItem {
18064                    label: "unsafe".into(),
18065                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18066                        range: lsp::Range {
18067                            start: lsp::Position {
18068                                line: 1,
18069                                character: 2,
18070                            },
18071                            end: lsp::Position {
18072                                line: 1,
18073                                character: 3,
18074                            },
18075                        },
18076                        new_text: "unsafe".to_string(),
18077                    })),
18078                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
18079                    ..Default::default()
18080                },
18081            ])))
18082        });
18083    cx.set_state("fn a() {}\n");
18084    cx.executor().run_until_parked();
18085    cx.update_editor(|editor, window, cx| {
18086        editor.trigger_completion_on_input("n", true, window, cx)
18087    });
18088    cx.executor().run_until_parked();
18089
18090    cx.update_editor(|editor, window, cx| {
18091        editor.confirm_completion(&Default::default(), window, cx)
18092    });
18093    cx.executor().run_until_parked();
18094    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
18095}
18096
18097#[gpui::test]
18098async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
18099    init_test(cx, |_| {});
18100    let language =
18101        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
18102    let mut cx = EditorLspTestContext::new(
18103        language,
18104        lsp::ServerCapabilities {
18105            completion_provider: Some(lsp::CompletionOptions {
18106                ..lsp::CompletionOptions::default()
18107            }),
18108            ..lsp::ServerCapabilities::default()
18109        },
18110        cx,
18111    )
18112    .await;
18113
18114    cx.set_state(
18115        "#ifndef BAR_H
18116#define BAR_H
18117
18118#include <stdbool.h>
18119
18120int fn_branch(bool do_branch1, bool do_branch2);
18121
18122#endif // BAR_H
18123ˇ",
18124    );
18125    cx.executor().run_until_parked();
18126    cx.update_editor(|editor, window, cx| {
18127        editor.handle_input("#", window, cx);
18128    });
18129    cx.executor().run_until_parked();
18130    cx.update_editor(|editor, window, cx| {
18131        editor.handle_input("i", window, cx);
18132    });
18133    cx.executor().run_until_parked();
18134    cx.update_editor(|editor, window, cx| {
18135        editor.handle_input("n", window, cx);
18136    });
18137    cx.executor().run_until_parked();
18138    cx.assert_editor_state(
18139        "#ifndef BAR_H
18140#define BAR_H
18141
18142#include <stdbool.h>
18143
18144int fn_branch(bool do_branch1, bool do_branch2);
18145
18146#endif // BAR_H
18147#inˇ",
18148    );
18149
18150    cx.lsp
18151        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18152            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18153                is_incomplete: false,
18154                item_defaults: None,
18155                items: vec![lsp::CompletionItem {
18156                    kind: Some(lsp::CompletionItemKind::SNIPPET),
18157                    label_details: Some(lsp::CompletionItemLabelDetails {
18158                        detail: Some("header".to_string()),
18159                        description: None,
18160                    }),
18161                    label: " include".to_string(),
18162                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18163                        range: lsp::Range {
18164                            start: lsp::Position {
18165                                line: 8,
18166                                character: 1,
18167                            },
18168                            end: lsp::Position {
18169                                line: 8,
18170                                character: 1,
18171                            },
18172                        },
18173                        new_text: "include \"$0\"".to_string(),
18174                    })),
18175                    sort_text: Some("40b67681include".to_string()),
18176                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18177                    filter_text: Some("include".to_string()),
18178                    insert_text: Some("include \"$0\"".to_string()),
18179                    ..lsp::CompletionItem::default()
18180                }],
18181            })))
18182        });
18183    cx.update_editor(|editor, window, cx| {
18184        editor.show_completions(&ShowCompletions, window, cx);
18185    });
18186    cx.executor().run_until_parked();
18187    cx.update_editor(|editor, window, cx| {
18188        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
18189    });
18190    cx.executor().run_until_parked();
18191    cx.assert_editor_state(
18192        "#ifndef BAR_H
18193#define BAR_H
18194
18195#include <stdbool.h>
18196
18197int fn_branch(bool do_branch1, bool do_branch2);
18198
18199#endif // BAR_H
18200#include \"ˇ\"",
18201    );
18202
18203    cx.lsp
18204        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18205            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18206                is_incomplete: true,
18207                item_defaults: None,
18208                items: vec![lsp::CompletionItem {
18209                    kind: Some(lsp::CompletionItemKind::FILE),
18210                    label: "AGL/".to_string(),
18211                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18212                        range: lsp::Range {
18213                            start: lsp::Position {
18214                                line: 8,
18215                                character: 10,
18216                            },
18217                            end: lsp::Position {
18218                                line: 8,
18219                                character: 11,
18220                            },
18221                        },
18222                        new_text: "AGL/".to_string(),
18223                    })),
18224                    sort_text: Some("40b67681AGL/".to_string()),
18225                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18226                    filter_text: Some("AGL/".to_string()),
18227                    insert_text: Some("AGL/".to_string()),
18228                    ..lsp::CompletionItem::default()
18229                }],
18230            })))
18231        });
18232    cx.update_editor(|editor, window, cx| {
18233        editor.show_completions(&ShowCompletions, window, cx);
18234    });
18235    cx.executor().run_until_parked();
18236    cx.update_editor(|editor, window, cx| {
18237        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
18238    });
18239    cx.executor().run_until_parked();
18240    cx.assert_editor_state(
18241        r##"#ifndef BAR_H
18242#define BAR_H
18243
18244#include <stdbool.h>
18245
18246int fn_branch(bool do_branch1, bool do_branch2);
18247
18248#endif // BAR_H
18249#include "AGL/ˇ"##,
18250    );
18251
18252    cx.update_editor(|editor, window, cx| {
18253        editor.handle_input("\"", window, cx);
18254    });
18255    cx.executor().run_until_parked();
18256    cx.assert_editor_state(
18257        r##"#ifndef BAR_H
18258#define BAR_H
18259
18260#include <stdbool.h>
18261
18262int fn_branch(bool do_branch1, bool do_branch2);
18263
18264#endif // BAR_H
18265#include "AGL/"ˇ"##,
18266    );
18267}
18268
18269#[gpui::test]
18270async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
18271    init_test(cx, |_| {});
18272
18273    let mut cx = EditorLspTestContext::new_rust(
18274        lsp::ServerCapabilities {
18275            completion_provider: Some(lsp::CompletionOptions {
18276                trigger_characters: Some(vec![".".to_string()]),
18277                resolve_provider: Some(false),
18278                ..lsp::CompletionOptions::default()
18279            }),
18280            ..lsp::ServerCapabilities::default()
18281        },
18282        cx,
18283    )
18284    .await;
18285
18286    cx.set_state("fn main() { let a = 2ˇ; }");
18287    cx.simulate_keystroke(".");
18288    let completion_item = lsp::CompletionItem {
18289        label: "Some".into(),
18290        kind: Some(lsp::CompletionItemKind::SNIPPET),
18291        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18292        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18293            kind: lsp::MarkupKind::Markdown,
18294            value: "```rust\nSome(2)\n```".to_string(),
18295        })),
18296        deprecated: Some(false),
18297        sort_text: Some("Some".to_string()),
18298        filter_text: Some("Some".to_string()),
18299        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18300        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18301            range: lsp::Range {
18302                start: lsp::Position {
18303                    line: 0,
18304                    character: 22,
18305                },
18306                end: lsp::Position {
18307                    line: 0,
18308                    character: 22,
18309                },
18310            },
18311            new_text: "Some(2)".to_string(),
18312        })),
18313        additional_text_edits: Some(vec![lsp::TextEdit {
18314            range: lsp::Range {
18315                start: lsp::Position {
18316                    line: 0,
18317                    character: 20,
18318                },
18319                end: lsp::Position {
18320                    line: 0,
18321                    character: 22,
18322                },
18323            },
18324            new_text: "".to_string(),
18325        }]),
18326        ..Default::default()
18327    };
18328
18329    let closure_completion_item = completion_item.clone();
18330    let counter = Arc::new(AtomicUsize::new(0));
18331    let counter_clone = counter.clone();
18332    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18333        let task_completion_item = closure_completion_item.clone();
18334        counter_clone.fetch_add(1, atomic::Ordering::Release);
18335        async move {
18336            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18337                is_incomplete: true,
18338                item_defaults: None,
18339                items: vec![task_completion_item],
18340            })))
18341        }
18342    });
18343
18344    cx.executor().run_until_parked();
18345    cx.condition(|editor, _| editor.context_menu_visible())
18346        .await;
18347    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
18348    assert!(request.next().await.is_some());
18349    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
18350
18351    cx.simulate_keystrokes("S o m");
18352    cx.condition(|editor, _| editor.context_menu_visible())
18353        .await;
18354    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
18355    assert!(request.next().await.is_some());
18356    assert!(request.next().await.is_some());
18357    assert!(request.next().await.is_some());
18358    request.close();
18359    assert!(request.next().await.is_none());
18360    assert_eq!(
18361        counter.load(atomic::Ordering::Acquire),
18362        4,
18363        "With the completions menu open, only one LSP request should happen per input"
18364    );
18365}
18366
18367#[gpui::test]
18368async fn test_toggle_comment(cx: &mut TestAppContext) {
18369    init_test(cx, |_| {});
18370    let mut cx = EditorTestContext::new(cx).await;
18371    let language = Arc::new(Language::new(
18372        LanguageConfig {
18373            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
18374            ..Default::default()
18375        },
18376        Some(tree_sitter_rust::LANGUAGE.into()),
18377    ));
18378    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18379
18380    // If multiple selections intersect a line, the line is only toggled once.
18381    cx.set_state(indoc! {"
18382        fn a() {
18383            «//b();
18384            ˇ»// «c();
18385            //ˇ»  d();
18386        }
18387    "});
18388
18389    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18390
18391    cx.assert_editor_state(indoc! {"
18392        fn a() {
18393            «b();
18394            ˇ»«c();
18395            ˇ» d();
18396        }
18397    "});
18398
18399    // The comment prefix is inserted at the same column for every line in a
18400    // selection.
18401    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18402
18403    cx.assert_editor_state(indoc! {"
18404        fn a() {
18405            // «b();
18406            ˇ»// «c();
18407            ˇ» // d();
18408        }
18409    "});
18410
18411    // If a selection ends at the beginning of a line, that line is not toggled.
18412    cx.set_selections_state(indoc! {"
18413        fn a() {
18414            // b();
18415            «// c();
18416        ˇ»     // d();
18417        }
18418    "});
18419
18420    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18421
18422    cx.assert_editor_state(indoc! {"
18423        fn a() {
18424            // b();
18425            «c();
18426        ˇ»     // d();
18427        }
18428    "});
18429
18430    // If a selection span a single line and is empty, the line is toggled.
18431    cx.set_state(indoc! {"
18432        fn a() {
18433            a();
18434            b();
18435        ˇ
18436        }
18437    "});
18438
18439    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18440
18441    cx.assert_editor_state(indoc! {"
18442        fn a() {
18443            a();
18444            b();
18445        //•ˇ
18446        }
18447    "});
18448
18449    // If a selection span multiple lines, empty lines are not toggled.
18450    cx.set_state(indoc! {"
18451        fn a() {
18452            «a();
18453
18454            c();ˇ»
18455        }
18456    "});
18457
18458    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18459
18460    cx.assert_editor_state(indoc! {"
18461        fn a() {
18462            // «a();
18463
18464            // c();ˇ»
18465        }
18466    "});
18467
18468    // If a selection includes multiple comment prefixes, all lines are uncommented.
18469    cx.set_state(indoc! {"
18470        fn a() {
18471            «// a();
18472            /// b();
18473            //! c();ˇ»
18474        }
18475    "});
18476
18477    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
18478
18479    cx.assert_editor_state(indoc! {"
18480        fn a() {
18481            «a();
18482            b();
18483            c();ˇ»
18484        }
18485    "});
18486}
18487
18488#[gpui::test]
18489async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
18490    init_test(cx, |_| {});
18491    let mut cx = EditorTestContext::new(cx).await;
18492    let language = Arc::new(Language::new(
18493        LanguageConfig {
18494            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
18495            ..Default::default()
18496        },
18497        Some(tree_sitter_rust::LANGUAGE.into()),
18498    ));
18499    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
18500
18501    let toggle_comments = &ToggleComments {
18502        advance_downwards: false,
18503        ignore_indent: true,
18504    };
18505
18506    // If multiple selections intersect a line, the line is only toggled once.
18507    cx.set_state(indoc! {"
18508        fn a() {
18509        //    «b();
18510        //    c();
18511        //    ˇ» d();
18512        }
18513    "});
18514
18515    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18516
18517    cx.assert_editor_state(indoc! {"
18518        fn a() {
18519            «b();
18520            c();
18521            ˇ» d();
18522        }
18523    "});
18524
18525    // The comment prefix is inserted at the beginning of each line
18526    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18527
18528    cx.assert_editor_state(indoc! {"
18529        fn a() {
18530        //    «b();
18531        //    c();
18532        //    ˇ» d();
18533        }
18534    "});
18535
18536    // If a selection ends at the beginning of a line, that line is not toggled.
18537    cx.set_selections_state(indoc! {"
18538        fn a() {
18539        //    b();
18540        //    «c();
18541        ˇ»//     d();
18542        }
18543    "});
18544
18545    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18546
18547    cx.assert_editor_state(indoc! {"
18548        fn a() {
18549        //    b();
18550            «c();
18551        ˇ»//     d();
18552        }
18553    "});
18554
18555    // If a selection span a single line and is empty, the line is toggled.
18556    cx.set_state(indoc! {"
18557        fn a() {
18558            a();
18559            b();
18560        ˇ
18561        }
18562    "});
18563
18564    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18565
18566    cx.assert_editor_state(indoc! {"
18567        fn a() {
18568            a();
18569            b();
18570        //ˇ
18571        }
18572    "});
18573
18574    // If a selection span multiple lines, empty lines are not toggled.
18575    cx.set_state(indoc! {"
18576        fn a() {
18577            «a();
18578
18579            c();ˇ»
18580        }
18581    "});
18582
18583    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18584
18585    cx.assert_editor_state(indoc! {"
18586        fn a() {
18587        //    «a();
18588
18589        //    c();ˇ»
18590        }
18591    "});
18592
18593    // If a selection includes multiple comment prefixes, all lines are uncommented.
18594    cx.set_state(indoc! {"
18595        fn a() {
18596        //    «a();
18597        ///    b();
18598        //!    c();ˇ»
18599        }
18600    "});
18601
18602    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
18603
18604    cx.assert_editor_state(indoc! {"
18605        fn a() {
18606            «a();
18607            b();
18608            c();ˇ»
18609        }
18610    "});
18611}
18612
18613#[gpui::test]
18614async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
18615    init_test(cx, |_| {});
18616
18617    let language = Arc::new(Language::new(
18618        LanguageConfig {
18619            line_comments: vec!["// ".into()],
18620            ..Default::default()
18621        },
18622        Some(tree_sitter_rust::LANGUAGE.into()),
18623    ));
18624
18625    let mut cx = EditorTestContext::new(cx).await;
18626
18627    cx.language_registry().add(language.clone());
18628    cx.update_buffer(|buffer, cx| {
18629        buffer.set_language(Some(language), cx);
18630    });
18631
18632    let toggle_comments = &ToggleComments {
18633        advance_downwards: true,
18634        ignore_indent: false,
18635    };
18636
18637    // Single cursor on one line -> advance
18638    // Cursor moves horizontally 3 characters as well on non-blank line
18639    cx.set_state(indoc!(
18640        "fn a() {
18641             ˇdog();
18642             cat();
18643        }"
18644    ));
18645    cx.update_editor(|editor, window, cx| {
18646        editor.toggle_comments(toggle_comments, window, cx);
18647    });
18648    cx.assert_editor_state(indoc!(
18649        "fn a() {
18650             // dog();
18651             catˇ();
18652        }"
18653    ));
18654
18655    // Single selection on one line -> don't advance
18656    cx.set_state(indoc!(
18657        "fn a() {
18658             «dog()ˇ»;
18659             cat();
18660        }"
18661    ));
18662    cx.update_editor(|editor, window, cx| {
18663        editor.toggle_comments(toggle_comments, window, cx);
18664    });
18665    cx.assert_editor_state(indoc!(
18666        "fn a() {
18667             // «dog()ˇ»;
18668             cat();
18669        }"
18670    ));
18671
18672    // Multiple cursors on one line -> advance
18673    cx.set_state(indoc!(
18674        "fn a() {
18675             ˇdˇog();
18676             cat();
18677        }"
18678    ));
18679    cx.update_editor(|editor, window, cx| {
18680        editor.toggle_comments(toggle_comments, window, cx);
18681    });
18682    cx.assert_editor_state(indoc!(
18683        "fn a() {
18684             // dog();
18685             catˇ(ˇ);
18686        }"
18687    ));
18688
18689    // Multiple cursors on one line, with selection -> don't advance
18690    cx.set_state(indoc!(
18691        "fn a() {
18692             ˇdˇog«()ˇ»;
18693             cat();
18694        }"
18695    ));
18696    cx.update_editor(|editor, window, cx| {
18697        editor.toggle_comments(toggle_comments, window, cx);
18698    });
18699    cx.assert_editor_state(indoc!(
18700        "fn a() {
18701             // ˇdˇog«()ˇ»;
18702             cat();
18703        }"
18704    ));
18705
18706    // Single cursor on one line -> advance
18707    // Cursor moves to column 0 on blank line
18708    cx.set_state(indoc!(
18709        "fn a() {
18710             ˇdog();
18711
18712             cat();
18713        }"
18714    ));
18715    cx.update_editor(|editor, window, cx| {
18716        editor.toggle_comments(toggle_comments, window, cx);
18717    });
18718    cx.assert_editor_state(indoc!(
18719        "fn a() {
18720             // dog();
18721        ˇ
18722             cat();
18723        }"
18724    ));
18725
18726    // Single cursor on one line -> advance
18727    // Cursor starts and ends at column 0
18728    cx.set_state(indoc!(
18729        "fn a() {
18730         ˇ    dog();
18731             cat();
18732        }"
18733    ));
18734    cx.update_editor(|editor, window, cx| {
18735        editor.toggle_comments(toggle_comments, window, cx);
18736    });
18737    cx.assert_editor_state(indoc!(
18738        "fn a() {
18739             // dog();
18740         ˇ    cat();
18741        }"
18742    ));
18743}
18744
18745#[gpui::test]
18746async fn test_toggle_block_comment(cx: &mut TestAppContext) {
18747    init_test(cx, |_| {});
18748
18749    let mut cx = EditorTestContext::new(cx).await;
18750
18751    let html_language = Arc::new(
18752        Language::new(
18753            LanguageConfig {
18754                name: "HTML".into(),
18755                block_comment: Some(BlockCommentConfig {
18756                    start: "<!-- ".into(),
18757                    prefix: "".into(),
18758                    end: " -->".into(),
18759                    tab_size: 0,
18760                }),
18761                ..Default::default()
18762            },
18763            Some(tree_sitter_html::LANGUAGE.into()),
18764        )
18765        .with_injection_query(
18766            r#"
18767            (script_element
18768                (raw_text) @injection.content
18769                (#set! injection.language "javascript"))
18770            "#,
18771        )
18772        .unwrap(),
18773    );
18774
18775    let javascript_language = Arc::new(Language::new(
18776        LanguageConfig {
18777            name: "JavaScript".into(),
18778            line_comments: vec!["// ".into()],
18779            ..Default::default()
18780        },
18781        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18782    ));
18783
18784    cx.language_registry().add(html_language.clone());
18785    cx.language_registry().add(javascript_language);
18786    cx.update_buffer(|buffer, cx| {
18787        buffer.set_language(Some(html_language), cx);
18788    });
18789
18790    // Toggle comments for empty selections
18791    cx.set_state(
18792        &r#"
18793            <p>A</p>ˇ
18794            <p>B</p>ˇ
18795            <p>C</p>ˇ
18796        "#
18797        .unindent(),
18798    );
18799    cx.update_editor(|editor, window, cx| {
18800        editor.toggle_comments(&ToggleComments::default(), window, cx)
18801    });
18802    cx.assert_editor_state(
18803        &r#"
18804            <!-- <p>A</p>ˇ -->
18805            <!-- <p>B</p>ˇ -->
18806            <!-- <p>C</p>ˇ -->
18807        "#
18808        .unindent(),
18809    );
18810    cx.update_editor(|editor, window, cx| {
18811        editor.toggle_comments(&ToggleComments::default(), window, cx)
18812    });
18813    cx.assert_editor_state(
18814        &r#"
18815            <p>A</p>ˇ
18816            <p>B</p>ˇ
18817            <p>C</p>ˇ
18818        "#
18819        .unindent(),
18820    );
18821
18822    // Toggle comments for mixture of empty and non-empty selections, where
18823    // multiple selections occupy a given line.
18824    cx.set_state(
18825        &r#"
18826            <p>A«</p>
18827            <p>ˇ»B</p>ˇ
18828            <p>C«</p>
18829            <p>ˇ»D</p>ˇ
18830        "#
18831        .unindent(),
18832    );
18833
18834    cx.update_editor(|editor, window, cx| {
18835        editor.toggle_comments(&ToggleComments::default(), window, cx)
18836    });
18837    cx.assert_editor_state(
18838        &r#"
18839            <!-- <p>A«</p>
18840            <p>ˇ»B</p>ˇ -->
18841            <!-- <p>C«</p>
18842            <p>ˇ»D</p>ˇ -->
18843        "#
18844        .unindent(),
18845    );
18846    cx.update_editor(|editor, window, cx| {
18847        editor.toggle_comments(&ToggleComments::default(), window, cx)
18848    });
18849    cx.assert_editor_state(
18850        &r#"
18851            <p>A«</p>
18852            <p>ˇ»B</p>ˇ
18853            <p>C«</p>
18854            <p>ˇ»D</p>ˇ
18855        "#
18856        .unindent(),
18857    );
18858
18859    // Toggle comments when different languages are active for different
18860    // selections.
18861    cx.set_state(
18862        &r#"
18863            ˇ<script>
18864                ˇvar x = new Y();
18865            ˇ</script>
18866        "#
18867        .unindent(),
18868    );
18869    cx.executor().run_until_parked();
18870    cx.update_editor(|editor, window, cx| {
18871        editor.toggle_comments(&ToggleComments::default(), window, cx)
18872    });
18873    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
18874    // Uncommenting and commenting from this position brings in even more wrong artifacts.
18875    cx.assert_editor_state(
18876        &r#"
18877            <!-- ˇ<script> -->
18878                // ˇvar x = new Y();
18879            <!-- ˇ</script> -->
18880        "#
18881        .unindent(),
18882    );
18883}
18884
18885#[gpui::test]
18886fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
18887    init_test(cx, |_| {});
18888
18889    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 4, 'a'), cx));
18890    let multibuffer = cx.new(|cx| {
18891        let mut multibuffer = MultiBuffer::new(ReadWrite);
18892        multibuffer.set_excerpts_for_path(
18893            PathKey::sorted(0),
18894            buffer.clone(),
18895            [
18896                Point::new(0, 0)..Point::new(0, 4),
18897                Point::new(5, 0)..Point::new(5, 4),
18898            ],
18899            0,
18900            cx,
18901        );
18902        assert_eq!(multibuffer.read(cx).text(), "aaaa\nffff");
18903        multibuffer
18904    });
18905
18906    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
18907    editor.update_in(cx, |editor, window, cx| {
18908        assert_eq!(editor.text(cx), "aaaa\nffff");
18909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18910            s.select_ranges([
18911                Point::new(0, 0)..Point::new(0, 0),
18912                Point::new(1, 0)..Point::new(1, 0),
18913            ])
18914        });
18915
18916        editor.handle_input("X", window, cx);
18917        assert_eq!(editor.text(cx), "Xaaaa\nXffff");
18918        assert_eq!(
18919            editor.selections.ranges(&editor.display_snapshot(cx)),
18920            [
18921                Point::new(0, 1)..Point::new(0, 1),
18922                Point::new(1, 1)..Point::new(1, 1),
18923            ]
18924        );
18925
18926        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
18927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18928            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
18929        });
18930        editor.backspace(&Default::default(), window, cx);
18931        assert_eq!(editor.text(cx), "Xa\nfff");
18932        assert_eq!(
18933            editor.selections.ranges(&editor.display_snapshot(cx)),
18934            [Point::new(1, 0)..Point::new(1, 0)]
18935        );
18936
18937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18938            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
18939        });
18940        editor.backspace(&Default::default(), window, cx);
18941        assert_eq!(editor.text(cx), "X\nff");
18942        assert_eq!(
18943            editor.selections.ranges(&editor.display_snapshot(cx)),
18944            [Point::new(0, 1)..Point::new(0, 1)]
18945        );
18946    });
18947}
18948
18949#[gpui::test]
18950async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
18951    init_test(cx, |_| {});
18952
18953    let language = Arc::new(
18954        Language::new(
18955            LanguageConfig {
18956                brackets: BracketPairConfig {
18957                    pairs: vec![
18958                        BracketPair {
18959                            start: "{".to_string(),
18960                            end: "}".to_string(),
18961                            close: true,
18962                            surround: true,
18963                            newline: true,
18964                        },
18965                        BracketPair {
18966                            start: "/* ".to_string(),
18967                            end: " */".to_string(),
18968                            close: true,
18969                            surround: true,
18970                            newline: true,
18971                        },
18972                    ],
18973                    ..Default::default()
18974                },
18975                ..Default::default()
18976            },
18977            Some(tree_sitter_rust::LANGUAGE.into()),
18978        )
18979        .with_indents_query("")
18980        .unwrap(),
18981    );
18982
18983    let text = concat!(
18984        "{   }\n",     //
18985        "  x\n",       //
18986        "  /*   */\n", //
18987        "x\n",         //
18988        "{{} }\n",     //
18989    );
18990
18991    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18992    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18993    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18994    editor
18995        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
18996        .await;
18997
18998    editor.update_in(cx, |editor, window, cx| {
18999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19000            s.select_display_ranges([
19001                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
19002                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
19003                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
19004            ])
19005        });
19006        editor.newline(&Newline, window, cx);
19007
19008        assert_eq!(
19009            editor.buffer().read(cx).read(cx).text(),
19010            concat!(
19011                "{ \n",    // Suppress rustfmt
19012                "\n",      //
19013                "}\n",     //
19014                "  x\n",   //
19015                "  /* \n", //
19016                "  \n",    //
19017                "  */\n",  //
19018                "x\n",     //
19019                "{{} \n",  //
19020                "}\n",     //
19021            )
19022        );
19023    });
19024}
19025
19026#[gpui::test]
19027fn test_highlighted_ranges(cx: &mut TestAppContext) {
19028    init_test(cx, |_| {});
19029
19030    let editor = cx.add_window(|window, cx| {
19031        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
19032        build_editor(buffer, window, cx)
19033    });
19034
19035    _ = editor.update(cx, |editor, window, cx| {
19036        let buffer = editor.buffer.read(cx).snapshot(cx);
19037
19038        let anchor_range =
19039            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
19040
19041        editor.highlight_background(
19042            HighlightKey::ColorizeBracket(0),
19043            &[
19044                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
19045                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
19046                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
19047                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
19048            ],
19049            |_, _| Hsla::red(),
19050            cx,
19051        );
19052        editor.highlight_background(
19053            HighlightKey::ColorizeBracket(1),
19054            &[
19055                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
19056                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
19057                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
19058                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
19059            ],
19060            |_, _| Hsla::green(),
19061            cx,
19062        );
19063
19064        let snapshot = editor.snapshot(window, cx);
19065        let highlighted_ranges = editor.sorted_background_highlights_in_range(
19066            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
19067            &snapshot,
19068            cx.theme(),
19069        );
19070        assert_eq!(
19071            highlighted_ranges,
19072            &[
19073                (
19074                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
19075                    Hsla::green(),
19076                ),
19077                (
19078                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
19079                    Hsla::red(),
19080                ),
19081                (
19082                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
19083                    Hsla::green(),
19084                ),
19085                (
19086                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
19087                    Hsla::red(),
19088                ),
19089            ]
19090        );
19091        assert_eq!(
19092            editor.sorted_background_highlights_in_range(
19093                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
19094                &snapshot,
19095                cx.theme(),
19096            ),
19097            &[(
19098                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
19099                Hsla::red(),
19100            )]
19101        );
19102    });
19103}
19104
19105#[gpui::test]
19106async fn test_copy_highlight_json(cx: &mut TestAppContext) {
19107    init_test(cx, |_| {});
19108
19109    let mut cx = EditorTestContext::new(cx).await;
19110    cx.set_state(indoc! {"
19111        fn main() {
19112            let x = 1;ˇ
19113        }
19114    "});
19115    setup_rust_syntax_highlighting(&mut cx);
19116
19117    cx.update_editor(|editor, window, cx| {
19118        editor.copy_highlight_json(&CopyHighlightJson, window, cx);
19119    });
19120
19121    let clipboard_json: serde_json::Value =
19122        serde_json::from_str(&cx.read_from_clipboard().unwrap().text().unwrap()).unwrap();
19123    assert_eq!(
19124        clipboard_json,
19125        json!([
19126            [
19127                {"text": "fn", "highlight": "keyword"},
19128                {"text": " ", "highlight": null},
19129                {"text": "main", "highlight": "function"},
19130                {"text": "()", "highlight": "punctuation.bracket"},
19131                {"text": " ", "highlight": null},
19132                {"text": "{", "highlight": "punctuation.bracket"},
19133            ],
19134            [
19135                {"text": "    ", "highlight": null},
19136                {"text": "let", "highlight": "keyword"},
19137                {"text": " ", "highlight": null},
19138                {"text": "x", "highlight": "variable"},
19139                {"text": " ", "highlight": null},
19140                {"text": "=", "highlight": "operator"},
19141                {"text": " ", "highlight": null},
19142                {"text": "1", "highlight": "number"},
19143                {"text": ";", "highlight": "punctuation.delimiter"},
19144            ],
19145            [
19146                {"text": "}", "highlight": "punctuation.bracket"},
19147            ],
19148        ])
19149    );
19150}
19151
19152#[gpui::test]
19153async fn test_copy_highlight_json_selected_range(cx: &mut TestAppContext) {
19154    init_test(cx, |_| {});
19155
19156    let mut cx = EditorTestContext::new(cx).await;
19157    cx.set_state(indoc! {"
19158        fn main() {
19159            «let x = 1;
19160            let yˇ» = 2;
19161        }
19162    "});
19163    setup_rust_syntax_highlighting(&mut cx);
19164
19165    cx.update_editor(|editor, window, cx| {
19166        editor.copy_highlight_json(&CopyHighlightJson, window, cx);
19167    });
19168
19169    let clipboard_json: serde_json::Value =
19170        serde_json::from_str(&cx.read_from_clipboard().unwrap().text().unwrap()).unwrap();
19171    assert_eq!(
19172        clipboard_json,
19173        json!([
19174            [
19175                {"text": "let", "highlight": "keyword"},
19176                {"text": " ", "highlight": null},
19177                {"text": "x", "highlight": "variable"},
19178                {"text": " ", "highlight": null},
19179                {"text": "=", "highlight": "operator"},
19180                {"text": " ", "highlight": null},
19181                {"text": "1", "highlight": "number"},
19182                {"text": ";", "highlight": "punctuation.delimiter"},
19183            ],
19184            [
19185                {"text": "    ", "highlight": null},
19186                {"text": "let", "highlight": "keyword"},
19187                {"text": " ", "highlight": null},
19188                {"text": "y", "highlight": "variable"},
19189            ],
19190        ])
19191    );
19192}
19193
19194#[gpui::test]
19195async fn test_copy_highlight_json_selected_line_range(cx: &mut TestAppContext) {
19196    init_test(cx, |_| {});
19197
19198    let mut cx = EditorTestContext::new(cx).await;
19199
19200    cx.set_state(indoc! {"
19201        fn main() {
19202            «let x = 1;
19203            let yˇ» = 2;
19204        }
19205    "});
19206    setup_rust_syntax_highlighting(&mut cx);
19207
19208    cx.update_editor(|editor, window, cx| {
19209        editor.selections.set_line_mode(true);
19210        editor.copy_highlight_json(&CopyHighlightJson, window, cx);
19211    });
19212
19213    let clipboard_json: serde_json::Value =
19214        serde_json::from_str(&cx.read_from_clipboard().unwrap().text().unwrap()).unwrap();
19215    assert_eq!(
19216        clipboard_json,
19217        json!([
19218            [
19219                {"text": "    ", "highlight": null},
19220                {"text": "let", "highlight": "keyword"},
19221                {"text": " ", "highlight": null},
19222                {"text": "x", "highlight": "variable"},
19223                {"text": " ", "highlight": null},
19224                {"text": "=", "highlight": "operator"},
19225                {"text": " ", "highlight": null},
19226                {"text": "1", "highlight": "number"},
19227                {"text": ";", "highlight": "punctuation.delimiter"},
19228            ],
19229            [
19230                {"text": "    ", "highlight": null},
19231                {"text": "let", "highlight": "keyword"},
19232                {"text": " ", "highlight": null},
19233                {"text": "y", "highlight": "variable"},
19234                {"text": " ", "highlight": null},
19235                {"text": "=", "highlight": "operator"},
19236                {"text": " ", "highlight": null},
19237                {"text": "2", "highlight": "number"},
19238                {"text": ";", "highlight": "punctuation.delimiter"},
19239            ],
19240        ])
19241    );
19242}
19243
19244#[gpui::test]
19245async fn test_copy_highlight_json_single_line(cx: &mut TestAppContext) {
19246    init_test(cx, |_| {});
19247
19248    let mut cx = EditorTestContext::new(cx).await;
19249
19250    cx.set_state(indoc! {"
19251        fn main() {
19252            let ˇx = 1;
19253            let y = 2;
19254        }
19255    "});
19256    setup_rust_syntax_highlighting(&mut cx);
19257
19258    cx.update_editor(|editor, window, cx| {
19259        editor.selections.set_line_mode(true);
19260        editor.copy_highlight_json(&CopyHighlightJson, window, cx);
19261    });
19262
19263    let clipboard_json: serde_json::Value =
19264        serde_json::from_str(&cx.read_from_clipboard().unwrap().text().unwrap()).unwrap();
19265    assert_eq!(
19266        clipboard_json,
19267        json!([
19268            [
19269                {"text": "    ", "highlight": null},
19270                {"text": "let", "highlight": "keyword"},
19271                {"text": " ", "highlight": null},
19272                {"text": "x", "highlight": "variable"},
19273                {"text": " ", "highlight": null},
19274                {"text": "=", "highlight": "operator"},
19275                {"text": " ", "highlight": null},
19276                {"text": "1", "highlight": "number"},
19277                {"text": ";", "highlight": "punctuation.delimiter"},
19278            ]
19279        ])
19280    );
19281}
19282
19283fn setup_rust_syntax_highlighting(cx: &mut EditorTestContext) {
19284    let syntax = SyntaxTheme::new_test(vec![
19285        ("keyword", Hsla::red()),
19286        ("function", Hsla::blue()),
19287        ("variable", Hsla::green()),
19288        ("number", Hsla::default()),
19289        ("operator", Hsla::default()),
19290        ("punctuation.bracket", Hsla::default()),
19291        ("punctuation.delimiter", Hsla::default()),
19292    ]);
19293
19294    let language = rust_lang();
19295    language.set_theme(&syntax);
19296
19297    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
19298    cx.executor().run_until_parked();
19299    cx.update_editor(|editor, window, cx| {
19300        editor.set_style(
19301            EditorStyle {
19302                syntax: Arc::new(syntax),
19303                ..Default::default()
19304            },
19305            window,
19306            cx,
19307        );
19308    });
19309}
19310
19311#[gpui::test]
19312async fn test_following(cx: &mut TestAppContext) {
19313    init_test(cx, |_| {});
19314
19315    let fs = FakeFs::new(cx.executor());
19316    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
19317
19318    let buffer = project.update(cx, |project, cx| {
19319        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
19320        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
19321    });
19322    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
19323    let follower = cx.update(|cx| {
19324        cx.open_window(
19325            WindowOptions {
19326                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
19327                    gpui::Point::new(px(0.), px(0.)),
19328                    gpui::Point::new(px(10.), px(80.)),
19329                ))),
19330                ..Default::default()
19331            },
19332            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
19333        )
19334        .unwrap()
19335    });
19336
19337    let is_still_following = Rc::new(RefCell::new(true));
19338    let follower_edit_event_count = Rc::new(RefCell::new(0));
19339    let pending_update = Rc::new(RefCell::new(None));
19340    let leader_entity = leader.root(cx).unwrap();
19341    let follower_entity = follower.root(cx).unwrap();
19342    _ = follower.update(cx, {
19343        let update = pending_update.clone();
19344        let is_still_following = is_still_following.clone();
19345        let follower_edit_event_count = follower_edit_event_count.clone();
19346        |_, window, cx| {
19347            cx.subscribe_in(
19348                &leader_entity,
19349                window,
19350                move |_, leader, event, window, cx| {
19351                    leader.update(cx, |leader, cx| {
19352                        leader.add_event_to_update_proto(
19353                            event,
19354                            &mut update.borrow_mut(),
19355                            window,
19356                            cx,
19357                        );
19358                    });
19359                },
19360            )
19361            .detach();
19362
19363            cx.subscribe_in(
19364                &follower_entity,
19365                window,
19366                move |_, _, event: &EditorEvent, _window, _cx| {
19367                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
19368                        *is_still_following.borrow_mut() = false;
19369                    }
19370
19371                    if let EditorEvent::BufferEdited = event {
19372                        *follower_edit_event_count.borrow_mut() += 1;
19373                    }
19374                },
19375            )
19376            .detach();
19377        }
19378    });
19379
19380    // Update the selections only
19381    _ = leader.update(cx, |leader, window, cx| {
19382        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19383            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
19384        });
19385    });
19386    follower
19387        .update(cx, |follower, window, cx| {
19388            follower.apply_update_proto(
19389                &project,
19390                pending_update.borrow_mut().take().unwrap(),
19391                window,
19392                cx,
19393            )
19394        })
19395        .unwrap()
19396        .await
19397        .unwrap();
19398    _ = follower.update(cx, |follower, _, cx| {
19399        assert_eq!(
19400            follower.selections.ranges(&follower.display_snapshot(cx)),
19401            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
19402        );
19403    });
19404    assert!(*is_still_following.borrow());
19405    assert_eq!(*follower_edit_event_count.borrow(), 0);
19406
19407    // Update the scroll position only
19408    _ = leader.update(cx, |leader, window, cx| {
19409        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
19410    });
19411    follower
19412        .update(cx, |follower, window, cx| {
19413            follower.apply_update_proto(
19414                &project,
19415                pending_update.borrow_mut().take().unwrap(),
19416                window,
19417                cx,
19418            )
19419        })
19420        .unwrap()
19421        .await
19422        .unwrap();
19423    assert_eq!(
19424        follower
19425            .update(cx, |follower, _, cx| follower.scroll_position(cx))
19426            .unwrap(),
19427        gpui::Point::new(1.5, 3.5)
19428    );
19429    assert!(*is_still_following.borrow());
19430    assert_eq!(*follower_edit_event_count.borrow(), 0);
19431
19432    // Update the selections and scroll position. The follower's scroll position is updated
19433    // via autoscroll, not via the leader's exact scroll position.
19434    _ = leader.update(cx, |leader, window, cx| {
19435        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19436            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
19437        });
19438        leader.request_autoscroll(Autoscroll::newest(), cx);
19439        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
19440    });
19441    follower
19442        .update(cx, |follower, window, cx| {
19443            follower.apply_update_proto(
19444                &project,
19445                pending_update.borrow_mut().take().unwrap(),
19446                window,
19447                cx,
19448            )
19449        })
19450        .unwrap()
19451        .await
19452        .unwrap();
19453    _ = follower.update(cx, |follower, _, cx| {
19454        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
19455        assert_eq!(
19456            follower.selections.ranges(&follower.display_snapshot(cx)),
19457            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
19458        );
19459    });
19460    assert!(*is_still_following.borrow());
19461
19462    // Creating a pending selection that precedes another selection
19463    _ = leader.update(cx, |leader, window, cx| {
19464        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19465            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
19466        });
19467        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
19468    });
19469    follower
19470        .update(cx, |follower, window, cx| {
19471            follower.apply_update_proto(
19472                &project,
19473                pending_update.borrow_mut().take().unwrap(),
19474                window,
19475                cx,
19476            )
19477        })
19478        .unwrap()
19479        .await
19480        .unwrap();
19481    _ = follower.update(cx, |follower, _, cx| {
19482        assert_eq!(
19483            follower.selections.ranges(&follower.display_snapshot(cx)),
19484            vec![
19485                MultiBufferOffset(0)..MultiBufferOffset(0),
19486                MultiBufferOffset(1)..MultiBufferOffset(1)
19487            ]
19488        );
19489    });
19490    assert!(*is_still_following.borrow());
19491
19492    // Extend the pending selection so that it surrounds another selection
19493    _ = leader.update(cx, |leader, window, cx| {
19494        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
19495    });
19496    follower
19497        .update(cx, |follower, window, cx| {
19498            follower.apply_update_proto(
19499                &project,
19500                pending_update.borrow_mut().take().unwrap(),
19501                window,
19502                cx,
19503            )
19504        })
19505        .unwrap()
19506        .await
19507        .unwrap();
19508    _ = follower.update(cx, |follower, _, cx| {
19509        assert_eq!(
19510            follower.selections.ranges(&follower.display_snapshot(cx)),
19511            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
19512        );
19513    });
19514
19515    // Scrolling locally breaks the follow
19516    _ = follower.update(cx, |follower, window, cx| {
19517        let top_anchor = follower
19518            .buffer()
19519            .read(cx)
19520            .read(cx)
19521            .anchor_after(MultiBufferOffset(0));
19522        follower.set_scroll_anchor(
19523            ScrollAnchor {
19524                anchor: top_anchor,
19525                offset: gpui::Point::new(0.0, 0.5),
19526            },
19527            window,
19528            cx,
19529        );
19530    });
19531    assert!(!(*is_still_following.borrow()));
19532}
19533
19534#[gpui::test]
19535async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
19536    init_test(cx, |_| {});
19537
19538    let fs = FakeFs::new(cx.executor());
19539    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
19540    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19541    let workspace = window
19542        .read_with(cx, |mw, _| mw.workspace().clone())
19543        .unwrap();
19544    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
19545
19546    let cx = &mut VisualTestContext::from_window(*window, cx);
19547
19548    let leader = pane.update_in(cx, |_, window, cx| {
19549        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
19550        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
19551    });
19552
19553    // Start following the editor when it has no excerpts.
19554    let mut state_message =
19555        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
19556    let workspace_entity = workspace.clone();
19557    let follower_1 = cx
19558        .update_window(*window, |_, window, cx| {
19559            Editor::from_state_proto(
19560                workspace_entity,
19561                ViewId {
19562                    creator: CollaboratorId::PeerId(PeerId::default()),
19563                    id: 0,
19564                },
19565                &mut state_message,
19566                window,
19567                cx,
19568            )
19569        })
19570        .unwrap()
19571        .unwrap()
19572        .await
19573        .unwrap();
19574
19575    let update_message = Rc::new(RefCell::new(None));
19576    follower_1.update_in(cx, {
19577        let update = update_message.clone();
19578        |_, window, cx| {
19579            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
19580                leader.update(cx, |leader, cx| {
19581                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
19582                });
19583            })
19584            .detach();
19585        }
19586    });
19587
19588    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
19589        (
19590            project.create_local_buffer("abc\ndef\nghi\njkl\nmno\npqr\nstu\nvwx\nyza\nbcd\nefg\nhij\nklm\nnop\nqrs\ntuv\nwxy\nzab\ncde\nfgh\n", None, false, cx),
19591            project.create_local_buffer("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\n", None, false, cx),
19592        )
19593    });
19594
19595    // Insert some excerpts.
19596    leader.update(cx, |leader, cx| {
19597        leader.buffer.update(cx, |multibuffer, cx| {
19598            multibuffer.set_excerpts_for_path(
19599                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
19600                buffer_1.clone(),
19601                vec![
19602                    Point::row_range(0..3),
19603                    Point::row_range(1..6),
19604                    Point::row_range(12..15),
19605                ],
19606                0,
19607                cx,
19608            );
19609            multibuffer.set_excerpts_for_path(
19610                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
19611                buffer_2.clone(),
19612                vec![Point::row_range(0..6), Point::row_range(8..12)],
19613                0,
19614                cx,
19615            );
19616        });
19617    });
19618
19619    // Apply the update of adding the excerpts.
19620    follower_1
19621        .update_in(cx, |follower, window, cx| {
19622            follower.apply_update_proto(
19623                &project,
19624                update_message.borrow().clone().unwrap(),
19625                window,
19626                cx,
19627            )
19628        })
19629        .await
19630        .unwrap();
19631    assert_eq!(
19632        follower_1.update(cx, |editor, cx| editor.text(cx)),
19633        leader.update(cx, |editor, cx| editor.text(cx))
19634    );
19635    update_message.borrow_mut().take();
19636
19637    // Start following separately after it already has excerpts.
19638    let mut state_message =
19639        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
19640    let workspace_entity = workspace.clone();
19641    let follower_2 = cx
19642        .update_window(*window, |_, window, cx| {
19643            Editor::from_state_proto(
19644                workspace_entity,
19645                ViewId {
19646                    creator: CollaboratorId::PeerId(PeerId::default()),
19647                    id: 0,
19648                },
19649                &mut state_message,
19650                window,
19651                cx,
19652            )
19653        })
19654        .unwrap()
19655        .unwrap()
19656        .await
19657        .unwrap();
19658    assert_eq!(
19659        follower_2.update(cx, |editor, cx| editor.text(cx)),
19660        leader.update(cx, |editor, cx| editor.text(cx))
19661    );
19662
19663    // Remove some excerpts.
19664    leader.update(cx, |leader, cx| {
19665        leader.buffer.update(cx, |multibuffer, cx| {
19666            multibuffer.remove_excerpts(
19667                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
19668                cx,
19669            );
19670        });
19671    });
19672
19673    // Apply the update of removing the excerpts.
19674    follower_1
19675        .update_in(cx, |follower, window, cx| {
19676            follower.apply_update_proto(
19677                &project,
19678                update_message.borrow().clone().unwrap(),
19679                window,
19680                cx,
19681            )
19682        })
19683        .await
19684        .unwrap();
19685    follower_2
19686        .update_in(cx, |follower, window, cx| {
19687            follower.apply_update_proto(
19688                &project,
19689                update_message.borrow().clone().unwrap(),
19690                window,
19691                cx,
19692            )
19693        })
19694        .await
19695        .unwrap();
19696    update_message.borrow_mut().take();
19697    assert_eq!(
19698        follower_1.update(cx, |editor, cx| editor.text(cx)),
19699        leader.update(cx, |editor, cx| editor.text(cx))
19700    );
19701}
19702
19703#[gpui::test]
19704async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19705    init_test(cx, |_| {});
19706
19707    let mut cx = EditorTestContext::new(cx).await;
19708    let lsp_store =
19709        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
19710
19711    cx.set_state(indoc! {"
19712        ˇfn func(abc def: i32) -> u32 {
19713        }
19714    "});
19715
19716    cx.update(|_, cx| {
19717        lsp_store.update(cx, |lsp_store, cx| {
19718            lsp_store
19719                .update_diagnostics(
19720                    LanguageServerId(0),
19721                    lsp::PublishDiagnosticsParams {
19722                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
19723                        version: None,
19724                        diagnostics: vec![
19725                            lsp::Diagnostic {
19726                                range: lsp::Range::new(
19727                                    lsp::Position::new(0, 11),
19728                                    lsp::Position::new(0, 12),
19729                                ),
19730                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19731                                ..Default::default()
19732                            },
19733                            lsp::Diagnostic {
19734                                range: lsp::Range::new(
19735                                    lsp::Position::new(0, 12),
19736                                    lsp::Position::new(0, 15),
19737                                ),
19738                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19739                                ..Default::default()
19740                            },
19741                            lsp::Diagnostic {
19742                                range: lsp::Range::new(
19743                                    lsp::Position::new(0, 25),
19744                                    lsp::Position::new(0, 28),
19745                                ),
19746                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19747                                ..Default::default()
19748                            },
19749                        ],
19750                    },
19751                    None,
19752                    DiagnosticSourceKind::Pushed,
19753                    &[],
19754                    cx,
19755                )
19756                .unwrap()
19757        });
19758    });
19759
19760    executor.run_until_parked();
19761
19762    cx.update_editor(|editor, window, cx| {
19763        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19764    });
19765
19766    cx.assert_editor_state(indoc! {"
19767        fn func(abc def: i32) -> ˇu32 {
19768        }
19769    "});
19770
19771    cx.update_editor(|editor, window, cx| {
19772        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19773    });
19774
19775    cx.assert_editor_state(indoc! {"
19776        fn func(abc ˇdef: i32) -> u32 {
19777        }
19778    "});
19779
19780    cx.update_editor(|editor, window, cx| {
19781        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19782    });
19783
19784    cx.assert_editor_state(indoc! {"
19785        fn func(abcˇ def: i32) -> u32 {
19786        }
19787    "});
19788
19789    cx.update_editor(|editor, window, cx| {
19790        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19791    });
19792
19793    cx.assert_editor_state(indoc! {"
19794        fn func(abc def: i32) -> ˇu32 {
19795        }
19796    "});
19797}
19798
19799#[gpui::test]
19800async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19801    init_test(cx, |_| {});
19802
19803    let mut cx = EditorTestContext::new(cx).await;
19804
19805    let diff_base = r#"
19806        use some::mod;
19807
19808        const A: u32 = 42;
19809
19810        fn main() {
19811            println!("hello");
19812
19813            println!("world");
19814        }
19815        "#
19816    .unindent();
19817
19818    // Edits are modified, removed, modified, added
19819    cx.set_state(
19820        &r#"
19821        use some::modified;
19822
19823        ˇ
19824        fn main() {
19825            println!("hello there");
19826
19827            println!("around the");
19828            println!("world");
19829        }
19830        "#
19831        .unindent(),
19832    );
19833
19834    cx.set_head_text(&diff_base);
19835    executor.run_until_parked();
19836
19837    cx.update_editor(|editor, window, cx| {
19838        //Wrap around the bottom of the buffer
19839        for _ in 0..3 {
19840            editor.go_to_next_hunk(&GoToHunk, window, cx);
19841        }
19842    });
19843
19844    cx.assert_editor_state(
19845        &r#"
19846        ˇuse some::modified;
19847
19848
19849        fn main() {
19850            println!("hello there");
19851
19852            println!("around the");
19853            println!("world");
19854        }
19855        "#
19856        .unindent(),
19857    );
19858
19859    cx.update_editor(|editor, window, cx| {
19860        //Wrap around the top of the buffer
19861        for _ in 0..2 {
19862            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19863        }
19864    });
19865
19866    cx.assert_editor_state(
19867        &r#"
19868        use some::modified;
19869
19870
19871        fn main() {
19872        ˇ    println!("hello there");
19873
19874            println!("around the");
19875            println!("world");
19876        }
19877        "#
19878        .unindent(),
19879    );
19880
19881    cx.update_editor(|editor, window, cx| {
19882        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19883    });
19884
19885    cx.assert_editor_state(
19886        &r#"
19887        use some::modified;
19888
19889        ˇ
19890        fn main() {
19891            println!("hello there");
19892
19893            println!("around the");
19894            println!("world");
19895        }
19896        "#
19897        .unindent(),
19898    );
19899
19900    cx.update_editor(|editor, window, cx| {
19901        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19902    });
19903
19904    cx.assert_editor_state(
19905        &r#"
19906        ˇuse some::modified;
19907
19908
19909        fn main() {
19910            println!("hello there");
19911
19912            println!("around the");
19913            println!("world");
19914        }
19915        "#
19916        .unindent(),
19917    );
19918
19919    cx.update_editor(|editor, window, cx| {
19920        for _ in 0..2 {
19921            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19922        }
19923    });
19924
19925    cx.assert_editor_state(
19926        &r#"
19927        use some::modified;
19928
19929
19930        fn main() {
19931        ˇ    println!("hello there");
19932
19933            println!("around the");
19934            println!("world");
19935        }
19936        "#
19937        .unindent(),
19938    );
19939
19940    cx.update_editor(|editor, window, cx| {
19941        editor.fold(&Fold, window, cx);
19942    });
19943
19944    cx.update_editor(|editor, window, cx| {
19945        editor.go_to_next_hunk(&GoToHunk, window, cx);
19946    });
19947
19948    cx.assert_editor_state(
19949        &r#"
19950        ˇuse some::modified;
19951
19952
19953        fn main() {
19954            println!("hello there");
19955
19956            println!("around the");
19957            println!("world");
19958        }
19959        "#
19960        .unindent(),
19961    );
19962}
19963
19964#[test]
19965fn test_split_words() {
19966    fn split(text: &str) -> Vec<&str> {
19967        split_words(text).collect()
19968    }
19969
19970    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
19971    assert_eq!(split("hello_world"), &["hello_", "world"]);
19972    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
19973    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
19974    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
19975    assert_eq!(split("helloworld"), &["helloworld"]);
19976
19977    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
19978}
19979
19980#[test]
19981fn test_split_words_for_snippet_prefix() {
19982    fn split(text: &str) -> Vec<&str> {
19983        snippet_candidate_suffixes(text, &|c| c.is_alphanumeric() || c == '_').collect()
19984    }
19985
19986    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
19987    assert_eq!(split("hello_world"), &["hello_world"]);
19988    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
19989    assert_eq!(split("Hello_World"), &["Hello_World"]);
19990    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
19991    assert_eq!(split("helloworld"), &["helloworld"]);
19992    assert_eq!(
19993        split("this@is!@#$^many   . symbols"),
19994        &[
19995            "symbols",
19996            " symbols",
19997            ". symbols",
19998            " . symbols",
19999            "  . symbols",
20000            "   . symbols",
20001            "many   . symbols",
20002            "^many   . symbols",
20003            "$^many   . symbols",
20004            "#$^many   . symbols",
20005            "@#$^many   . symbols",
20006            "!@#$^many   . symbols",
20007            "is!@#$^many   . symbols",
20008            "@is!@#$^many   . symbols",
20009            "this@is!@#$^many   . symbols",
20010        ],
20011    );
20012    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
20013}
20014
20015#[gpui::test]
20016async fn test_move_to_syntax_node_relative_jumps(tcx: &mut TestAppContext) {
20017    init_test(tcx, |_| {});
20018
20019    let mut cx = EditorLspTestContext::new(
20020        Arc::into_inner(markdown_lang()).unwrap(),
20021        Default::default(),
20022        tcx,
20023    )
20024    .await;
20025
20026    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
20027        let _state_context = cx.set_state(before);
20028        cx.run_until_parked();
20029        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
20030            .await
20031            .unwrap();
20032        cx.run_until_parked();
20033        cx.assert_editor_state(after);
20034    }
20035
20036    const ABOVE: i8 = -1;
20037    const BELOW: i8 = 1;
20038
20039    assert(
20040        ABOVE,
20041        indoc! {"
20042        # Foo
20043
20044        ˇFoo foo foo
20045
20046        # Bar
20047
20048        Bar bar bar
20049    "},
20050        indoc! {"
20051        ˇ# Foo
20052
20053        Foo foo foo
20054
20055        # Bar
20056
20057        Bar bar bar
20058    "},
20059        &mut cx,
20060    )
20061    .await;
20062
20063    assert(
20064        ABOVE,
20065        indoc! {"
20066        ˇ# Foo
20067
20068        Foo foo foo
20069
20070        # Bar
20071
20072        Bar bar bar
20073    "},
20074        indoc! {"
20075        ˇ# Foo
20076
20077        Foo foo foo
20078
20079        # Bar
20080
20081        Bar bar bar
20082    "},
20083        &mut cx,
20084    )
20085    .await;
20086
20087    assert(
20088        BELOW,
20089        indoc! {"
20090        ˇ# Foo
20091
20092        Foo foo foo
20093
20094        # Bar
20095
20096        Bar bar bar
20097    "},
20098        indoc! {"
20099        # Foo
20100
20101        Foo foo foo
20102
20103        ˇ# Bar
20104
20105        Bar bar bar
20106    "},
20107        &mut cx,
20108    )
20109    .await;
20110
20111    assert(
20112        BELOW,
20113        indoc! {"
20114        # Foo
20115
20116        ˇFoo foo foo
20117
20118        # Bar
20119
20120        Bar bar bar
20121    "},
20122        indoc! {"
20123        # Foo
20124
20125        Foo foo foo
20126
20127        ˇ# Bar
20128
20129        Bar bar bar
20130    "},
20131        &mut cx,
20132    )
20133    .await;
20134
20135    assert(
20136        BELOW,
20137        indoc! {"
20138        # Foo
20139
20140        Foo foo foo
20141
20142        ˇ# Bar
20143
20144        Bar bar bar
20145    "},
20146        indoc! {"
20147        # Foo
20148
20149        Foo foo foo
20150
20151        ˇ# Bar
20152
20153        Bar bar bar
20154    "},
20155        &mut cx,
20156    )
20157    .await;
20158
20159    assert(
20160        BELOW,
20161        indoc! {"
20162        # Foo
20163
20164        Foo foo foo
20165
20166        # Bar
20167        ˇ
20168        Bar bar bar
20169    "},
20170        indoc! {"
20171        # Foo
20172
20173        Foo foo foo
20174
20175        # Bar
20176        ˇ
20177        Bar bar bar
20178    "},
20179        &mut cx,
20180    )
20181    .await;
20182}
20183
20184#[gpui::test]
20185async fn test_move_to_syntax_node_relative_dead_zone(tcx: &mut TestAppContext) {
20186    init_test(tcx, |_| {});
20187
20188    let mut cx = EditorLspTestContext::new(
20189        Arc::into_inner(rust_lang()).unwrap(),
20190        Default::default(),
20191        tcx,
20192    )
20193    .await;
20194
20195    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
20196        let _state_context = cx.set_state(before);
20197        cx.run_until_parked();
20198        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
20199            .await
20200            .unwrap();
20201        cx.run_until_parked();
20202        cx.assert_editor_state(after);
20203    }
20204
20205    const ABOVE: i8 = -1;
20206    const BELOW: i8 = 1;
20207
20208    assert(
20209        ABOVE,
20210        indoc! {"
20211        fn foo() {
20212            // foo fn
20213        }
20214
20215        ˇ// this zone is not inside any top level outline node
20216
20217        fn bar() {
20218            // bar fn
20219            let _ = 2;
20220        }
20221    "},
20222        indoc! {"
20223        ˇfn foo() {
20224            // foo fn
20225        }
20226
20227        // this zone is not inside any top level outline node
20228
20229        fn bar() {
20230            // bar fn
20231            let _ = 2;
20232        }
20233    "},
20234        &mut cx,
20235    )
20236    .await;
20237
20238    assert(
20239        BELOW,
20240        indoc! {"
20241        fn foo() {
20242            // foo fn
20243        }
20244
20245        ˇ// this zone is not inside any top level outline node
20246
20247        fn bar() {
20248            // bar fn
20249            let _ = 2;
20250        }
20251    "},
20252        indoc! {"
20253        fn foo() {
20254            // foo fn
20255        }
20256
20257        // this zone is not inside any top level outline node
20258
20259        ˇfn bar() {
20260            // bar fn
20261            let _ = 2;
20262        }
20263    "},
20264        &mut cx,
20265    )
20266    .await;
20267}
20268
20269#[gpui::test]
20270async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
20271    init_test(cx, |_| {});
20272
20273    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
20274
20275    #[track_caller]
20276    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
20277        let _state_context = cx.set_state(before);
20278        cx.run_until_parked();
20279        cx.update_editor(|editor, window, cx| {
20280            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
20281        });
20282        cx.run_until_parked();
20283        cx.assert_editor_state(after);
20284    }
20285
20286    // Outside bracket jumps to outside of matching bracket
20287    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
20288    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
20289
20290    // Inside bracket jumps to inside of matching bracket
20291    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
20292    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
20293
20294    // When outside a bracket and inside, favor jumping to the inside bracket
20295    assert(
20296        "console.log('foo', [1, 2, 3]ˇ);",
20297        "console.log('foo', ˇ[1, 2, 3]);",
20298        &mut cx,
20299    );
20300    assert(
20301        "console.log(ˇ'foo', [1, 2, 3]);",
20302        "console.log('foo'ˇ, [1, 2, 3]);",
20303        &mut cx,
20304    );
20305
20306    // Bias forward if two options are equally likely
20307    assert(
20308        "let result = curried_fun()ˇ();",
20309        "let result = curried_fun()()ˇ;",
20310        &mut cx,
20311    );
20312
20313    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
20314    assert(
20315        indoc! {"
20316            function test() {
20317                console.log('test')ˇ
20318            }"},
20319        indoc! {"
20320            function test() {
20321                console.logˇ('test')
20322            }"},
20323        &mut cx,
20324    );
20325}
20326
20327#[gpui::test]
20328async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
20329    init_test(cx, |_| {});
20330    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
20331    language_registry.add(markdown_lang());
20332    language_registry.add(rust_lang());
20333    let buffer = cx.new(|cx| {
20334        let mut buffer = language::Buffer::local(
20335            indoc! {"
20336            ```rs
20337            impl Worktree {
20338                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20339                }
20340            }
20341            ```
20342        "},
20343            cx,
20344        );
20345        buffer.set_language_registry(language_registry.clone());
20346        buffer.set_language(Some(markdown_lang()), cx);
20347        buffer
20348    });
20349    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20350    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
20351    cx.executor().run_until_parked();
20352    _ = editor.update(cx, |editor, window, cx| {
20353        // Case 1: Test outer enclosing brackets
20354        select_ranges(
20355            editor,
20356            &indoc! {"
20357                ```rs
20358                impl Worktree {
20359                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20360                    }
2036120362                ```
20363            "},
20364            window,
20365            cx,
20366        );
20367        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
20368        assert_text_with_selections(
20369            editor,
20370            &indoc! {"
20371                ```rs
20372                impl Worktree ˇ{
20373                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20374                    }
20375                }
20376                ```
20377            "},
20378            cx,
20379        );
20380        // Case 2: Test inner enclosing brackets
20381        select_ranges(
20382            editor,
20383            &indoc! {"
20384                ```rs
20385                impl Worktree {
20386                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
2038720388                }
20389                ```
20390            "},
20391            window,
20392            cx,
20393        );
20394        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
20395        assert_text_with_selections(
20396            editor,
20397            &indoc! {"
20398                ```rs
20399                impl Worktree {
20400                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
20401                    }
20402                }
20403                ```
20404            "},
20405            cx,
20406        );
20407    });
20408}
20409
20410#[gpui::test]
20411async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
20412    init_test(cx, |_| {});
20413
20414    let fs = FakeFs::new(cx.executor());
20415    fs.insert_tree(
20416        path!("/a"),
20417        json!({
20418            "main.rs": "fn main() { let a = 5; }",
20419            "other.rs": "// Test file",
20420        }),
20421    )
20422    .await;
20423    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20424
20425    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20426    language_registry.add(Arc::new(Language::new(
20427        LanguageConfig {
20428            name: "Rust".into(),
20429            matcher: LanguageMatcher {
20430                path_suffixes: vec!["rs".to_string()],
20431                ..Default::default()
20432            },
20433            brackets: BracketPairConfig {
20434                pairs: vec![BracketPair {
20435                    start: "{".to_string(),
20436                    end: "}".to_string(),
20437                    close: true,
20438                    surround: true,
20439                    newline: true,
20440                }],
20441                disabled_scopes_by_bracket_ix: Vec::new(),
20442            },
20443            ..Default::default()
20444        },
20445        Some(tree_sitter_rust::LANGUAGE.into()),
20446    )));
20447    let mut fake_servers = language_registry.register_fake_lsp(
20448        "Rust",
20449        FakeLspAdapter {
20450            capabilities: lsp::ServerCapabilities {
20451                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
20452                    first_trigger_character: "{".to_string(),
20453                    more_trigger_character: None,
20454                }),
20455                ..Default::default()
20456            },
20457            ..Default::default()
20458        },
20459    );
20460
20461    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
20462    let workspace = window
20463        .read_with(cx, |mw, _| mw.workspace().clone())
20464        .unwrap();
20465
20466    let cx = &mut VisualTestContext::from_window(*window, cx);
20467
20468    let worktree_id = workspace.update_in(cx, |workspace, _, cx| {
20469        workspace.project().update(cx, |project, cx| {
20470            project.worktrees(cx).next().unwrap().read(cx).id()
20471        })
20472    });
20473
20474    let buffer = project
20475        .update(cx, |project, cx| {
20476            project.open_local_buffer(path!("/a/main.rs"), cx)
20477        })
20478        .await
20479        .unwrap();
20480    let editor_handle = workspace
20481        .update_in(cx, |workspace, window, cx| {
20482            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
20483        })
20484        .await
20485        .unwrap()
20486        .downcast::<Editor>()
20487        .unwrap();
20488
20489    let fake_server = fake_servers.next().await.unwrap();
20490
20491    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
20492        |params, _| async move {
20493            assert_eq!(
20494                params.text_document_position.text_document.uri,
20495                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
20496            );
20497            assert_eq!(
20498                params.text_document_position.position,
20499                lsp::Position::new(0, 21),
20500            );
20501
20502            Ok(Some(vec![lsp::TextEdit {
20503                new_text: "]".to_string(),
20504                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20505            }]))
20506        },
20507    );
20508
20509    editor_handle.update_in(cx, |editor, window, cx| {
20510        window.focus(&editor.focus_handle(cx), cx);
20511        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20512            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
20513        });
20514        editor.handle_input("{", window, cx);
20515    });
20516
20517    cx.executor().run_until_parked();
20518
20519    buffer.update(cx, |buffer, _| {
20520        assert_eq!(
20521            buffer.text(),
20522            "fn main() { let a = {5}; }",
20523            "No extra braces from on type formatting should appear in the buffer"
20524        )
20525    });
20526}
20527
20528#[gpui::test(iterations = 20, seeds(31))]
20529async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
20530    init_test(cx, |_| {});
20531
20532    let mut cx = EditorLspTestContext::new_rust(
20533        lsp::ServerCapabilities {
20534            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
20535                first_trigger_character: ".".to_string(),
20536                more_trigger_character: None,
20537            }),
20538            ..Default::default()
20539        },
20540        cx,
20541    )
20542    .await;
20543
20544    cx.update_buffer(|buffer, _| {
20545        // This causes autoindent to be async.
20546        buffer.set_sync_parse_timeout(None)
20547    });
20548
20549    cx.set_state("fn c() {\n    d()ˇ\n}\n");
20550    cx.simulate_keystroke("\n");
20551    cx.run_until_parked();
20552
20553    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
20554    let mut request =
20555        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
20556            let buffer_cloned = buffer_cloned.clone();
20557            async move {
20558                buffer_cloned.update(&mut cx, |buffer, _| {
20559                    assert_eq!(
20560                        buffer.text(),
20561                        "fn c() {\n    d()\n        .\n}\n",
20562                        "OnTypeFormatting should triggered after autoindent applied"
20563                    )
20564                });
20565
20566                Ok(Some(vec![]))
20567            }
20568        });
20569
20570    cx.simulate_keystroke(".");
20571    cx.run_until_parked();
20572
20573    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
20574    assert!(request.next().await.is_some());
20575    request.close();
20576    assert!(request.next().await.is_none());
20577}
20578
20579#[gpui::test]
20580async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
20581    init_test(cx, |_| {});
20582
20583    let fs = FakeFs::new(cx.executor());
20584    fs.insert_tree(
20585        path!("/a"),
20586        json!({
20587            "main.rs": "fn main() { let a = 5; }",
20588            "other.rs": "// Test file",
20589        }),
20590    )
20591    .await;
20592
20593    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20594
20595    let server_restarts = Arc::new(AtomicUsize::new(0));
20596    let closure_restarts = Arc::clone(&server_restarts);
20597    let language_server_name = "test language server";
20598    let language_name: LanguageName = "Rust".into();
20599
20600    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20601    language_registry.add(Arc::new(Language::new(
20602        LanguageConfig {
20603            name: language_name.clone(),
20604            matcher: LanguageMatcher {
20605                path_suffixes: vec!["rs".to_string()],
20606                ..Default::default()
20607            },
20608            ..Default::default()
20609        },
20610        Some(tree_sitter_rust::LANGUAGE.into()),
20611    )));
20612    let mut fake_servers = language_registry.register_fake_lsp(
20613        "Rust",
20614        FakeLspAdapter {
20615            name: language_server_name,
20616            initialization_options: Some(json!({
20617                "testOptionValue": true
20618            })),
20619            initializer: Some(Box::new(move |fake_server| {
20620                let task_restarts = Arc::clone(&closure_restarts);
20621                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
20622                    task_restarts.fetch_add(1, atomic::Ordering::Release);
20623                    futures::future::ready(Ok(()))
20624                });
20625            })),
20626            ..Default::default()
20627        },
20628    );
20629
20630    let _window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
20631    let _buffer = project
20632        .update(cx, |project, cx| {
20633            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
20634        })
20635        .await
20636        .unwrap();
20637    let _fake_server = fake_servers.next().await.unwrap();
20638    update_test_language_settings(cx, &|language_settings| {
20639        language_settings.languages.0.insert(
20640            language_name.clone().0.to_string(),
20641            LanguageSettingsContent {
20642                tab_size: NonZeroU32::new(8),
20643                ..Default::default()
20644            },
20645        );
20646    });
20647    cx.executor().run_until_parked();
20648    assert_eq!(
20649        server_restarts.load(atomic::Ordering::Acquire),
20650        0,
20651        "Should not restart LSP server on an unrelated change"
20652    );
20653
20654    update_test_project_settings(cx, &|project_settings| {
20655        project_settings.lsp.0.insert(
20656            "Some other server name".into(),
20657            LspSettings {
20658                binary: None,
20659                settings: None,
20660                initialization_options: Some(json!({
20661                    "some other init value": false
20662                })),
20663                enable_lsp_tasks: false,
20664                fetch: None,
20665            },
20666        );
20667    });
20668    cx.executor().run_until_parked();
20669    assert_eq!(
20670        server_restarts.load(atomic::Ordering::Acquire),
20671        0,
20672        "Should not restart LSP server on an unrelated LSP settings change"
20673    );
20674
20675    update_test_project_settings(cx, &|project_settings| {
20676        project_settings.lsp.0.insert(
20677            language_server_name.into(),
20678            LspSettings {
20679                binary: None,
20680                settings: None,
20681                initialization_options: Some(json!({
20682                    "anotherInitValue": false
20683                })),
20684                enable_lsp_tasks: false,
20685                fetch: None,
20686            },
20687        );
20688    });
20689    cx.executor().run_until_parked();
20690    assert_eq!(
20691        server_restarts.load(atomic::Ordering::Acquire),
20692        1,
20693        "Should restart LSP server on a related LSP settings change"
20694    );
20695
20696    update_test_project_settings(cx, &|project_settings| {
20697        project_settings.lsp.0.insert(
20698            language_server_name.into(),
20699            LspSettings {
20700                binary: None,
20701                settings: None,
20702                initialization_options: Some(json!({
20703                    "anotherInitValue": false
20704                })),
20705                enable_lsp_tasks: false,
20706                fetch: None,
20707            },
20708        );
20709    });
20710    cx.executor().run_until_parked();
20711    assert_eq!(
20712        server_restarts.load(atomic::Ordering::Acquire),
20713        1,
20714        "Should not restart LSP server on a related LSP settings change that is the same"
20715    );
20716
20717    update_test_project_settings(cx, &|project_settings| {
20718        project_settings.lsp.0.insert(
20719            language_server_name.into(),
20720            LspSettings {
20721                binary: None,
20722                settings: None,
20723                initialization_options: None,
20724                enable_lsp_tasks: false,
20725                fetch: None,
20726            },
20727        );
20728    });
20729    cx.executor().run_until_parked();
20730    assert_eq!(
20731        server_restarts.load(atomic::Ordering::Acquire),
20732        2,
20733        "Should restart LSP server on another related LSP settings change"
20734    );
20735}
20736
20737#[gpui::test]
20738async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
20739    init_test(cx, |_| {});
20740
20741    let mut cx = EditorLspTestContext::new_rust(
20742        lsp::ServerCapabilities {
20743            completion_provider: Some(lsp::CompletionOptions {
20744                trigger_characters: Some(vec![".".to_string()]),
20745                resolve_provider: Some(true),
20746                ..Default::default()
20747            }),
20748            ..Default::default()
20749        },
20750        cx,
20751    )
20752    .await;
20753
20754    cx.set_state("fn main() { let a = 2ˇ; }");
20755    cx.simulate_keystroke(".");
20756    let completion_item = lsp::CompletionItem {
20757        label: "some".into(),
20758        kind: Some(lsp::CompletionItemKind::SNIPPET),
20759        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
20760        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
20761            kind: lsp::MarkupKind::Markdown,
20762            value: "```rust\nSome(2)\n```".to_string(),
20763        })),
20764        deprecated: Some(false),
20765        sort_text: Some("fffffff2".to_string()),
20766        filter_text: Some("some".to_string()),
20767        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
20768        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20769            range: lsp::Range {
20770                start: lsp::Position {
20771                    line: 0,
20772                    character: 22,
20773                },
20774                end: lsp::Position {
20775                    line: 0,
20776                    character: 22,
20777                },
20778            },
20779            new_text: "Some(2)".to_string(),
20780        })),
20781        additional_text_edits: Some(vec![lsp::TextEdit {
20782            range: lsp::Range {
20783                start: lsp::Position {
20784                    line: 0,
20785                    character: 20,
20786                },
20787                end: lsp::Position {
20788                    line: 0,
20789                    character: 22,
20790                },
20791            },
20792            new_text: "".to_string(),
20793        }]),
20794        ..Default::default()
20795    };
20796
20797    let closure_completion_item = completion_item.clone();
20798    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20799        let task_completion_item = closure_completion_item.clone();
20800        async move {
20801            Ok(Some(lsp::CompletionResponse::Array(vec![
20802                task_completion_item,
20803            ])))
20804        }
20805    });
20806
20807    request.next().await;
20808
20809    cx.condition(|editor, _| editor.context_menu_visible())
20810        .await;
20811    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20812        editor
20813            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20814            .unwrap()
20815    });
20816    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
20817
20818    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20819        let task_completion_item = completion_item.clone();
20820        async move { Ok(task_completion_item) }
20821    })
20822    .next()
20823    .await
20824    .unwrap();
20825    apply_additional_edits.await.unwrap();
20826    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
20827}
20828
20829#[gpui::test]
20830async fn test_completions_with_additional_edits_undo(cx: &mut TestAppContext) {
20831    init_test(cx, |_| {});
20832
20833    let mut cx = EditorLspTestContext::new_rust(
20834        lsp::ServerCapabilities {
20835            completion_provider: Some(lsp::CompletionOptions {
20836                trigger_characters: Some(vec![".".to_string()]),
20837                resolve_provider: Some(true),
20838                ..Default::default()
20839            }),
20840            ..Default::default()
20841        },
20842        cx,
20843    )
20844    .await;
20845
20846    cx.set_state("fn main() { let a = 2ˇ; }");
20847    cx.simulate_keystroke(".");
20848    let completion_item = lsp::CompletionItem {
20849        label: "some".into(),
20850        kind: Some(lsp::CompletionItemKind::SNIPPET),
20851        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
20852        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
20853            kind: lsp::MarkupKind::Markdown,
20854            value: "```rust\nSome(2)\n```".to_string(),
20855        })),
20856        deprecated: Some(false),
20857        sort_text: Some("fffffff2".to_string()),
20858        filter_text: Some("some".to_string()),
20859        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
20860        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20861            range: lsp::Range {
20862                start: lsp::Position {
20863                    line: 0,
20864                    character: 22,
20865                },
20866                end: lsp::Position {
20867                    line: 0,
20868                    character: 22,
20869                },
20870            },
20871            new_text: "Some(2)".to_string(),
20872        })),
20873        additional_text_edits: Some(vec![lsp::TextEdit {
20874            range: lsp::Range {
20875                start: lsp::Position {
20876                    line: 0,
20877                    character: 20,
20878                },
20879                end: lsp::Position {
20880                    line: 0,
20881                    character: 22,
20882                },
20883            },
20884            new_text: "".to_string(),
20885        }]),
20886        ..Default::default()
20887    };
20888
20889    let closure_completion_item = completion_item.clone();
20890    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20891        let task_completion_item = closure_completion_item.clone();
20892        async move {
20893            Ok(Some(lsp::CompletionResponse::Array(vec![
20894                task_completion_item,
20895            ])))
20896        }
20897    });
20898
20899    request.next().await;
20900
20901    cx.condition(|editor, _| editor.context_menu_visible())
20902        .await;
20903    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20904        editor
20905            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20906            .unwrap()
20907    });
20908    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
20909
20910    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20911        let task_completion_item = completion_item.clone();
20912        async move { Ok(task_completion_item) }
20913    })
20914    .next()
20915    .await
20916    .unwrap();
20917    apply_additional_edits.await.unwrap();
20918    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
20919
20920    cx.update_editor(|editor, window, cx| {
20921        editor.undo(&crate::Undo, window, cx);
20922    });
20923    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
20924}
20925
20926#[gpui::test]
20927async fn test_completions_with_additional_edits_and_multiple_cursors(cx: &mut TestAppContext) {
20928    init_test(cx, |_| {});
20929
20930    let mut cx = EditorLspTestContext::new_typescript(
20931        lsp::ServerCapabilities {
20932            completion_provider: Some(lsp::CompletionOptions {
20933                resolve_provider: Some(true),
20934                ..Default::default()
20935            }),
20936            ..Default::default()
20937        },
20938        cx,
20939    )
20940    .await;
20941
20942    cx.set_state(
20943        "import { «Fooˇ» } from './types';\n\nclass Bar {\n    method(): «Fooˇ» { return new Foo(); }\n}",
20944    );
20945
20946    cx.simulate_keystroke("F");
20947    cx.simulate_keystroke("o");
20948
20949    let completion_item = lsp::CompletionItem {
20950        label: "FooBar".into(),
20951        kind: Some(lsp::CompletionItemKind::CLASS),
20952        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20953            range: lsp::Range {
20954                start: lsp::Position {
20955                    line: 3,
20956                    character: 14,
20957                },
20958                end: lsp::Position {
20959                    line: 3,
20960                    character: 16,
20961                },
20962            },
20963            new_text: "FooBar".to_string(),
20964        })),
20965        additional_text_edits: Some(vec![lsp::TextEdit {
20966            range: lsp::Range {
20967                start: lsp::Position {
20968                    line: 0,
20969                    character: 9,
20970                },
20971                end: lsp::Position {
20972                    line: 0,
20973                    character: 11,
20974                },
20975            },
20976            new_text: "FooBar".to_string(),
20977        }]),
20978        ..Default::default()
20979    };
20980
20981    let closure_completion_item = completion_item.clone();
20982    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20983        let task_completion_item = closure_completion_item.clone();
20984        async move {
20985            Ok(Some(lsp::CompletionResponse::Array(vec![
20986                task_completion_item,
20987            ])))
20988        }
20989    });
20990
20991    request.next().await;
20992
20993    cx.condition(|editor, _| editor.context_menu_visible())
20994        .await;
20995    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20996        editor
20997            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20998            .unwrap()
20999    });
21000
21001    cx.assert_editor_state(
21002        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
21003    );
21004
21005    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
21006        let task_completion_item = completion_item.clone();
21007        async move { Ok(task_completion_item) }
21008    })
21009    .next()
21010    .await
21011    .unwrap();
21012
21013    apply_additional_edits.await.unwrap();
21014
21015    cx.assert_editor_state(
21016        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
21017    );
21018}
21019
21020#[gpui::test]
21021async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
21022    init_test(cx, |_| {});
21023
21024    let mut cx = EditorLspTestContext::new_rust(
21025        lsp::ServerCapabilities {
21026            completion_provider: Some(lsp::CompletionOptions {
21027                trigger_characters: Some(vec![".".to_string()]),
21028                resolve_provider: Some(true),
21029                ..Default::default()
21030            }),
21031            ..Default::default()
21032        },
21033        cx,
21034    )
21035    .await;
21036
21037    cx.set_state("fn main() { let a = 2ˇ; }");
21038    cx.simulate_keystroke(".");
21039
21040    let item1 = lsp::CompletionItem {
21041        label: "method id()".to_string(),
21042        filter_text: Some("id".to_string()),
21043        detail: None,
21044        documentation: None,
21045        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21046            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21047            new_text: ".id".to_string(),
21048        })),
21049        ..lsp::CompletionItem::default()
21050    };
21051
21052    let item2 = lsp::CompletionItem {
21053        label: "other".to_string(),
21054        filter_text: Some("other".to_string()),
21055        detail: None,
21056        documentation: None,
21057        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21058            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21059            new_text: ".other".to_string(),
21060        })),
21061        ..lsp::CompletionItem::default()
21062    };
21063
21064    let item1 = item1.clone();
21065    cx.set_request_handler::<lsp::request::Completion, _, _>({
21066        let item1 = item1.clone();
21067        move |_, _, _| {
21068            let item1 = item1.clone();
21069            let item2 = item2.clone();
21070            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
21071        }
21072    })
21073    .next()
21074    .await;
21075
21076    cx.condition(|editor, _| editor.context_menu_visible())
21077        .await;
21078    cx.update_editor(|editor, _, _| {
21079        let context_menu = editor.context_menu.borrow_mut();
21080        let context_menu = context_menu
21081            .as_ref()
21082            .expect("Should have the context menu deployed");
21083        match context_menu {
21084            CodeContextMenu::Completions(completions_menu) => {
21085                let completions = completions_menu.completions.borrow_mut();
21086                assert_eq!(
21087                    completions
21088                        .iter()
21089                        .map(|completion| &completion.label.text)
21090                        .collect::<Vec<_>>(),
21091                    vec!["method id()", "other"]
21092                )
21093            }
21094            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21095        }
21096    });
21097
21098    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
21099        let item1 = item1.clone();
21100        move |_, item_to_resolve, _| {
21101            let item1 = item1.clone();
21102            async move {
21103                if item1 == item_to_resolve {
21104                    Ok(lsp::CompletionItem {
21105                        label: "method id()".to_string(),
21106                        filter_text: Some("id".to_string()),
21107                        detail: Some("Now resolved!".to_string()),
21108                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
21109                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21110                            range: lsp::Range::new(
21111                                lsp::Position::new(0, 22),
21112                                lsp::Position::new(0, 22),
21113                            ),
21114                            new_text: ".id".to_string(),
21115                        })),
21116                        ..lsp::CompletionItem::default()
21117                    })
21118                } else {
21119                    Ok(item_to_resolve)
21120                }
21121            }
21122        }
21123    })
21124    .next()
21125    .await
21126    .unwrap();
21127    cx.run_until_parked();
21128
21129    cx.update_editor(|editor, window, cx| {
21130        editor.context_menu_next(&Default::default(), window, cx);
21131    });
21132    cx.run_until_parked();
21133
21134    cx.update_editor(|editor, _, _| {
21135        let context_menu = editor.context_menu.borrow_mut();
21136        let context_menu = context_menu
21137            .as_ref()
21138            .expect("Should have the context menu deployed");
21139        match context_menu {
21140            CodeContextMenu::Completions(completions_menu) => {
21141                let completions = completions_menu.completions.borrow_mut();
21142                assert_eq!(
21143                    completions
21144                        .iter()
21145                        .map(|completion| &completion.label.text)
21146                        .collect::<Vec<_>>(),
21147                    vec!["method id() Now resolved!", "other"],
21148                    "Should update first completion label, but not second as the filter text did not match."
21149                );
21150            }
21151            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21152        }
21153    });
21154}
21155
21156#[gpui::test]
21157async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
21158    init_test(cx, |_| {});
21159    let mut cx = EditorLspTestContext::new_rust(
21160        lsp::ServerCapabilities {
21161            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
21162            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
21163            completion_provider: Some(lsp::CompletionOptions {
21164                resolve_provider: Some(true),
21165                ..Default::default()
21166            }),
21167            ..Default::default()
21168        },
21169        cx,
21170    )
21171    .await;
21172    cx.set_state(indoc! {"
21173        struct TestStruct {
21174            field: i32
21175        }
21176
21177        fn mainˇ() {
21178            let unused_var = 42;
21179            let test_struct = TestStruct { field: 42 };
21180        }
21181    "});
21182    let symbol_range = cx.lsp_range(indoc! {"
21183        struct TestStruct {
21184            field: i32
21185        }
21186
21187        «fn main»() {
21188            let unused_var = 42;
21189            let test_struct = TestStruct { field: 42 };
21190        }
21191    "});
21192    let mut hover_requests =
21193        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
21194            Ok(Some(lsp::Hover {
21195                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
21196                    kind: lsp::MarkupKind::Markdown,
21197                    value: "Function documentation".to_string(),
21198                }),
21199                range: Some(symbol_range),
21200            }))
21201        });
21202
21203    // Case 1: Test that code action menu hide hover popover
21204    cx.dispatch_action(Hover);
21205    hover_requests.next().await;
21206    cx.condition(|editor, _| editor.hover_state.visible()).await;
21207    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
21208        move |_, _, _| async move {
21209            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
21210                lsp::CodeAction {
21211                    title: "Remove unused variable".to_string(),
21212                    kind: Some(CodeActionKind::QUICKFIX),
21213                    edit: Some(lsp::WorkspaceEdit {
21214                        changes: Some(
21215                            [(
21216                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
21217                                vec![lsp::TextEdit {
21218                                    range: lsp::Range::new(
21219                                        lsp::Position::new(5, 4),
21220                                        lsp::Position::new(5, 27),
21221                                    ),
21222                                    new_text: "".to_string(),
21223                                }],
21224                            )]
21225                            .into_iter()
21226                            .collect(),
21227                        ),
21228                        ..Default::default()
21229                    }),
21230                    ..Default::default()
21231                },
21232            )]))
21233        },
21234    );
21235    cx.update_editor(|editor, window, cx| {
21236        editor.toggle_code_actions(
21237            &ToggleCodeActions {
21238                deployed_from: None,
21239                quick_launch: false,
21240            },
21241            window,
21242            cx,
21243        );
21244    });
21245    code_action_requests.next().await;
21246    cx.run_until_parked();
21247    cx.condition(|editor, _| editor.context_menu_visible())
21248        .await;
21249    cx.update_editor(|editor, _, _| {
21250        assert!(
21251            !editor.hover_state.visible(),
21252            "Hover popover should be hidden when code action menu is shown"
21253        );
21254        // Hide code actions
21255        editor.context_menu.take();
21256    });
21257
21258    // Case 2: Test that code completions hide hover popover
21259    cx.dispatch_action(Hover);
21260    hover_requests.next().await;
21261    cx.condition(|editor, _| editor.hover_state.visible()).await;
21262    let counter = Arc::new(AtomicUsize::new(0));
21263    let mut completion_requests =
21264        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21265            let counter = counter.clone();
21266            async move {
21267                counter.fetch_add(1, atomic::Ordering::Release);
21268                Ok(Some(lsp::CompletionResponse::Array(vec![
21269                    lsp::CompletionItem {
21270                        label: "main".into(),
21271                        kind: Some(lsp::CompletionItemKind::FUNCTION),
21272                        detail: Some("() -> ()".to_string()),
21273                        ..Default::default()
21274                    },
21275                    lsp::CompletionItem {
21276                        label: "TestStruct".into(),
21277                        kind: Some(lsp::CompletionItemKind::STRUCT),
21278                        detail: Some("struct TestStruct".to_string()),
21279                        ..Default::default()
21280                    },
21281                ])))
21282            }
21283        });
21284    cx.update_editor(|editor, window, cx| {
21285        editor.show_completions(&ShowCompletions, window, cx);
21286    });
21287    completion_requests.next().await;
21288    cx.condition(|editor, _| editor.context_menu_visible())
21289        .await;
21290    cx.update_editor(|editor, _, _| {
21291        assert!(
21292            !editor.hover_state.visible(),
21293            "Hover popover should be hidden when completion menu is shown"
21294        );
21295    });
21296}
21297
21298#[gpui::test]
21299async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
21300    init_test(cx, |_| {});
21301
21302    let mut cx = EditorLspTestContext::new_rust(
21303        lsp::ServerCapabilities {
21304            completion_provider: Some(lsp::CompletionOptions {
21305                trigger_characters: Some(vec![".".to_string()]),
21306                resolve_provider: Some(true),
21307                ..Default::default()
21308            }),
21309            ..Default::default()
21310        },
21311        cx,
21312    )
21313    .await;
21314
21315    cx.set_state("fn main() { let a = 2ˇ; }");
21316    cx.simulate_keystroke(".");
21317
21318    let unresolved_item_1 = lsp::CompletionItem {
21319        label: "id".to_string(),
21320        filter_text: Some("id".to_string()),
21321        detail: None,
21322        documentation: None,
21323        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21324            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21325            new_text: ".id".to_string(),
21326        })),
21327        ..lsp::CompletionItem::default()
21328    };
21329    let resolved_item_1 = lsp::CompletionItem {
21330        additional_text_edits: Some(vec![lsp::TextEdit {
21331            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
21332            new_text: "!!".to_string(),
21333        }]),
21334        ..unresolved_item_1.clone()
21335    };
21336    let unresolved_item_2 = lsp::CompletionItem {
21337        label: "other".to_string(),
21338        filter_text: Some("other".to_string()),
21339        detail: None,
21340        documentation: None,
21341        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21342            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21343            new_text: ".other".to_string(),
21344        })),
21345        ..lsp::CompletionItem::default()
21346    };
21347    let resolved_item_2 = lsp::CompletionItem {
21348        additional_text_edits: Some(vec![lsp::TextEdit {
21349            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
21350            new_text: "??".to_string(),
21351        }]),
21352        ..unresolved_item_2.clone()
21353    };
21354
21355    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
21356    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
21357    cx.lsp
21358        .server
21359        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
21360            let unresolved_item_1 = unresolved_item_1.clone();
21361            let resolved_item_1 = resolved_item_1.clone();
21362            let unresolved_item_2 = unresolved_item_2.clone();
21363            let resolved_item_2 = resolved_item_2.clone();
21364            let resolve_requests_1 = resolve_requests_1.clone();
21365            let resolve_requests_2 = resolve_requests_2.clone();
21366            move |unresolved_request, _| {
21367                let unresolved_item_1 = unresolved_item_1.clone();
21368                let resolved_item_1 = resolved_item_1.clone();
21369                let unresolved_item_2 = unresolved_item_2.clone();
21370                let resolved_item_2 = resolved_item_2.clone();
21371                let resolve_requests_1 = resolve_requests_1.clone();
21372                let resolve_requests_2 = resolve_requests_2.clone();
21373                async move {
21374                    if unresolved_request == unresolved_item_1 {
21375                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
21376                        Ok(resolved_item_1.clone())
21377                    } else if unresolved_request == unresolved_item_2 {
21378                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
21379                        Ok(resolved_item_2.clone())
21380                    } else {
21381                        panic!("Unexpected completion item {unresolved_request:?}")
21382                    }
21383                }
21384            }
21385        })
21386        .detach();
21387
21388    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21389        let unresolved_item_1 = unresolved_item_1.clone();
21390        let unresolved_item_2 = unresolved_item_2.clone();
21391        async move {
21392            Ok(Some(lsp::CompletionResponse::Array(vec![
21393                unresolved_item_1,
21394                unresolved_item_2,
21395            ])))
21396        }
21397    })
21398    .next()
21399    .await;
21400
21401    cx.condition(|editor, _| editor.context_menu_visible())
21402        .await;
21403    cx.update_editor(|editor, _, _| {
21404        let context_menu = editor.context_menu.borrow_mut();
21405        let context_menu = context_menu
21406            .as_ref()
21407            .expect("Should have the context menu deployed");
21408        match context_menu {
21409            CodeContextMenu::Completions(completions_menu) => {
21410                let completions = completions_menu.completions.borrow_mut();
21411                assert_eq!(
21412                    completions
21413                        .iter()
21414                        .map(|completion| &completion.label.text)
21415                        .collect::<Vec<_>>(),
21416                    vec!["id", "other"]
21417                )
21418            }
21419            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21420        }
21421    });
21422    cx.run_until_parked();
21423
21424    cx.update_editor(|editor, window, cx| {
21425        editor.context_menu_next(&ContextMenuNext, window, cx);
21426    });
21427    cx.run_until_parked();
21428    cx.update_editor(|editor, window, cx| {
21429        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
21430    });
21431    cx.run_until_parked();
21432    cx.update_editor(|editor, window, cx| {
21433        editor.context_menu_next(&ContextMenuNext, window, cx);
21434    });
21435    cx.run_until_parked();
21436    cx.update_editor(|editor, window, cx| {
21437        editor
21438            .compose_completion(&ComposeCompletion::default(), window, cx)
21439            .expect("No task returned")
21440    })
21441    .await
21442    .expect("Completion failed");
21443    cx.run_until_parked();
21444
21445    cx.update_editor(|editor, _, cx| {
21446        assert_eq!(
21447            resolve_requests_1.load(atomic::Ordering::Acquire),
21448            1,
21449            "Should always resolve once despite multiple selections"
21450        );
21451        assert_eq!(
21452            resolve_requests_2.load(atomic::Ordering::Acquire),
21453            1,
21454            "Should always resolve once after multiple selections and applying the completion"
21455        );
21456        assert_eq!(
21457            editor.text(cx),
21458            "fn main() { let a = ??.other; }",
21459            "Should use resolved data when applying the completion"
21460        );
21461    });
21462}
21463
21464#[gpui::test]
21465async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
21466    init_test(cx, |_| {});
21467
21468    let item_0 = lsp::CompletionItem {
21469        label: "abs".into(),
21470        insert_text: Some("abs".into()),
21471        data: Some(json!({ "very": "special"})),
21472        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
21473        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21474            lsp::InsertReplaceEdit {
21475                new_text: "abs".to_string(),
21476                insert: lsp::Range::default(),
21477                replace: lsp::Range::default(),
21478            },
21479        )),
21480        ..lsp::CompletionItem::default()
21481    };
21482    let items = iter::once(item_0.clone())
21483        .chain((11..51).map(|i| lsp::CompletionItem {
21484            label: format!("item_{}", i),
21485            insert_text: Some(format!("item_{}", i)),
21486            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
21487            ..lsp::CompletionItem::default()
21488        }))
21489        .collect::<Vec<_>>();
21490
21491    let default_commit_characters = vec!["?".to_string()];
21492    let default_data = json!({ "default": "data"});
21493    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
21494    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
21495    let default_edit_range = lsp::Range {
21496        start: lsp::Position {
21497            line: 0,
21498            character: 5,
21499        },
21500        end: lsp::Position {
21501            line: 0,
21502            character: 5,
21503        },
21504    };
21505
21506    let mut cx = EditorLspTestContext::new_rust(
21507        lsp::ServerCapabilities {
21508            completion_provider: Some(lsp::CompletionOptions {
21509                trigger_characters: Some(vec![".".to_string()]),
21510                resolve_provider: Some(true),
21511                ..Default::default()
21512            }),
21513            ..Default::default()
21514        },
21515        cx,
21516    )
21517    .await;
21518
21519    cx.set_state("fn main() { let a = 2ˇ; }");
21520    cx.simulate_keystroke(".");
21521
21522    let completion_data = default_data.clone();
21523    let completion_characters = default_commit_characters.clone();
21524    let completion_items = items.clone();
21525    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21526        let default_data = completion_data.clone();
21527        let default_commit_characters = completion_characters.clone();
21528        let items = completion_items.clone();
21529        async move {
21530            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21531                items,
21532                item_defaults: Some(lsp::CompletionListItemDefaults {
21533                    data: Some(default_data.clone()),
21534                    commit_characters: Some(default_commit_characters.clone()),
21535                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
21536                        default_edit_range,
21537                    )),
21538                    insert_text_format: Some(default_insert_text_format),
21539                    insert_text_mode: Some(default_insert_text_mode),
21540                }),
21541                ..lsp::CompletionList::default()
21542            })))
21543        }
21544    })
21545    .next()
21546    .await;
21547
21548    let resolved_items = Arc::new(Mutex::new(Vec::new()));
21549    cx.lsp
21550        .server
21551        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
21552            let closure_resolved_items = resolved_items.clone();
21553            move |item_to_resolve, _| {
21554                let closure_resolved_items = closure_resolved_items.clone();
21555                async move {
21556                    closure_resolved_items.lock().push(item_to_resolve.clone());
21557                    Ok(item_to_resolve)
21558                }
21559            }
21560        })
21561        .detach();
21562
21563    cx.condition(|editor, _| editor.context_menu_visible())
21564        .await;
21565    cx.run_until_parked();
21566    cx.update_editor(|editor, _, _| {
21567        let menu = editor.context_menu.borrow_mut();
21568        match menu.as_ref().expect("should have the completions menu") {
21569            CodeContextMenu::Completions(completions_menu) => {
21570                assert_eq!(
21571                    completions_menu
21572                        .entries
21573                        .borrow()
21574                        .iter()
21575                        .map(|mat| mat.string.clone())
21576                        .collect::<Vec<String>>(),
21577                    items
21578                        .iter()
21579                        .map(|completion| completion.label.clone())
21580                        .collect::<Vec<String>>()
21581                );
21582            }
21583            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
21584        }
21585    });
21586    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
21587    // with 4 from the end.
21588    assert_eq!(
21589        *resolved_items.lock(),
21590        [&items[0..16], &items[items.len() - 4..items.len()]]
21591            .concat()
21592            .iter()
21593            .cloned()
21594            .map(|mut item| {
21595                if item.data.is_none() {
21596                    item.data = Some(default_data.clone());
21597                }
21598                item
21599            })
21600            .collect::<Vec<lsp::CompletionItem>>(),
21601        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
21602    );
21603    resolved_items.lock().clear();
21604
21605    cx.update_editor(|editor, window, cx| {
21606        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
21607    });
21608    cx.run_until_parked();
21609    // Completions that have already been resolved are skipped.
21610    assert_eq!(
21611        *resolved_items.lock(),
21612        items[items.len() - 17..items.len() - 4]
21613            .iter()
21614            .cloned()
21615            .map(|mut item| {
21616                if item.data.is_none() {
21617                    item.data = Some(default_data.clone());
21618                }
21619                item
21620            })
21621            .collect::<Vec<lsp::CompletionItem>>()
21622    );
21623    resolved_items.lock().clear();
21624}
21625
21626#[gpui::test]
21627async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
21628    init_test(cx, |_| {});
21629
21630    let mut cx = EditorLspTestContext::new(
21631        Language::new(
21632            LanguageConfig {
21633                matcher: LanguageMatcher {
21634                    path_suffixes: vec!["jsx".into()],
21635                    ..Default::default()
21636                },
21637                overrides: [(
21638                    "element".into(),
21639                    LanguageConfigOverride {
21640                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
21641                        ..Default::default()
21642                    },
21643                )]
21644                .into_iter()
21645                .collect(),
21646                ..Default::default()
21647            },
21648            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
21649        )
21650        .with_override_query("(jsx_self_closing_element) @element")
21651        .unwrap(),
21652        lsp::ServerCapabilities {
21653            completion_provider: Some(lsp::CompletionOptions {
21654                trigger_characters: Some(vec![":".to_string()]),
21655                ..Default::default()
21656            }),
21657            ..Default::default()
21658        },
21659        cx,
21660    )
21661    .await;
21662
21663    cx.lsp
21664        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21665            Ok(Some(lsp::CompletionResponse::Array(vec![
21666                lsp::CompletionItem {
21667                    label: "bg-blue".into(),
21668                    ..Default::default()
21669                },
21670                lsp::CompletionItem {
21671                    label: "bg-red".into(),
21672                    ..Default::default()
21673                },
21674                lsp::CompletionItem {
21675                    label: "bg-yellow".into(),
21676                    ..Default::default()
21677                },
21678            ])))
21679        });
21680
21681    cx.set_state(r#"<p class="bgˇ" />"#);
21682
21683    // Trigger completion when typing a dash, because the dash is an extra
21684    // word character in the 'element' scope, which contains the cursor.
21685    cx.simulate_keystroke("-");
21686    cx.executor().run_until_parked();
21687    cx.update_editor(|editor, _, _| {
21688        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
21689        {
21690            assert_eq!(
21691                completion_menu_entries(menu),
21692                &["bg-blue", "bg-red", "bg-yellow"]
21693            );
21694        } else {
21695            panic!("expected completion menu to be open");
21696        }
21697    });
21698
21699    cx.simulate_keystroke("l");
21700    cx.executor().run_until_parked();
21701    cx.update_editor(|editor, _, _| {
21702        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
21703        {
21704            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
21705        } else {
21706            panic!("expected completion menu to be open");
21707        }
21708    });
21709
21710    // When filtering completions, consider the character after the '-' to
21711    // be the start of a subword.
21712    cx.set_state(r#"<p class="yelˇ" />"#);
21713    cx.simulate_keystroke("l");
21714    cx.executor().run_until_parked();
21715    cx.update_editor(|editor, _, _| {
21716        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
21717        {
21718            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
21719        } else {
21720            panic!("expected completion menu to be open");
21721        }
21722    });
21723}
21724
21725fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
21726    let entries = menu.entries.borrow();
21727    entries.iter().map(|mat| mat.string.clone()).collect()
21728}
21729
21730#[gpui::test]
21731async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
21732    init_test(cx, |settings| {
21733        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21734    });
21735
21736    let fs = FakeFs::new(cx.executor());
21737    fs.insert_file(path!("/file.ts"), Default::default()).await;
21738
21739    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
21740    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21741
21742    language_registry.add(Arc::new(Language::new(
21743        LanguageConfig {
21744            name: "TypeScript".into(),
21745            matcher: LanguageMatcher {
21746                path_suffixes: vec!["ts".to_string()],
21747                ..Default::default()
21748            },
21749            ..Default::default()
21750        },
21751        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21752    )));
21753    update_test_language_settings(cx, &|settings| {
21754        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21755    });
21756
21757    let test_plugin = "test_plugin";
21758    let _ = language_registry.register_fake_lsp(
21759        "TypeScript",
21760        FakeLspAdapter {
21761            prettier_plugins: vec![test_plugin],
21762            ..Default::default()
21763        },
21764    );
21765
21766    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21767    let buffer = project
21768        .update(cx, |project, cx| {
21769            project.open_local_buffer(path!("/file.ts"), cx)
21770        })
21771        .await
21772        .unwrap();
21773
21774    let buffer_text = "one\ntwo\nthree\n";
21775    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21776    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21777    editor.update_in(cx, |editor, window, cx| {
21778        editor.set_text(buffer_text, window, cx)
21779    });
21780
21781    editor
21782        .update_in(cx, |editor, window, cx| {
21783            editor.perform_format(
21784                project.clone(),
21785                FormatTrigger::Manual,
21786                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21787                window,
21788                cx,
21789            )
21790        })
21791        .unwrap()
21792        .await;
21793    assert_eq!(
21794        editor.update(cx, |editor, cx| editor.text(cx)),
21795        buffer_text.to_string() + prettier_format_suffix,
21796        "Test prettier formatting was not applied to the original buffer text",
21797    );
21798
21799    update_test_language_settings(cx, &|settings| {
21800        settings.defaults.formatter = Some(FormatterList::default())
21801    });
21802    let format = editor.update_in(cx, |editor, window, cx| {
21803        editor.perform_format(
21804            project.clone(),
21805            FormatTrigger::Manual,
21806            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21807            window,
21808            cx,
21809        )
21810    });
21811    format.await.unwrap();
21812    assert_eq!(
21813        editor.update(cx, |editor, cx| editor.text(cx)),
21814        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
21815        "Autoformatting (via test prettier) was not applied to the original buffer text",
21816    );
21817}
21818
21819#[gpui::test]
21820async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
21821    init_test(cx, |settings| {
21822        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21823    });
21824
21825    let fs = FakeFs::new(cx.executor());
21826    fs.insert_file(path!("/file.settings"), Default::default())
21827        .await;
21828
21829    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
21830    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21831
21832    let ts_lang = Arc::new(Language::new(
21833        LanguageConfig {
21834            name: "TypeScript".into(),
21835            matcher: LanguageMatcher {
21836                path_suffixes: vec!["ts".to_string()],
21837                ..LanguageMatcher::default()
21838            },
21839            prettier_parser_name: Some("typescript".to_string()),
21840            ..LanguageConfig::default()
21841        },
21842        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21843    ));
21844
21845    language_registry.add(ts_lang.clone());
21846
21847    update_test_language_settings(cx, &|settings| {
21848        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21849    });
21850
21851    let test_plugin = "test_plugin";
21852    let _ = language_registry.register_fake_lsp(
21853        "TypeScript",
21854        FakeLspAdapter {
21855            prettier_plugins: vec![test_plugin],
21856            ..Default::default()
21857        },
21858    );
21859
21860    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21861    let buffer = project
21862        .update(cx, |project, cx| {
21863            project.open_local_buffer(path!("/file.settings"), cx)
21864        })
21865        .await
21866        .unwrap();
21867
21868    project.update(cx, |project, cx| {
21869        project.set_language_for_buffer(&buffer, ts_lang, cx)
21870    });
21871
21872    let buffer_text = "one\ntwo\nthree\n";
21873    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21874    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21875    editor.update_in(cx, |editor, window, cx| {
21876        editor.set_text(buffer_text, window, cx)
21877    });
21878
21879    editor
21880        .update_in(cx, |editor, window, cx| {
21881            editor.perform_format(
21882                project.clone(),
21883                FormatTrigger::Manual,
21884                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21885                window,
21886                cx,
21887            )
21888        })
21889        .unwrap()
21890        .await;
21891    assert_eq!(
21892        editor.update(cx, |editor, cx| editor.text(cx)),
21893        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
21894        "Test prettier formatting was not applied to the original buffer text",
21895    );
21896
21897    update_test_language_settings(cx, &|settings| {
21898        settings.defaults.formatter = Some(FormatterList::default())
21899    });
21900    let format = editor.update_in(cx, |editor, window, cx| {
21901        editor.perform_format(
21902            project.clone(),
21903            FormatTrigger::Manual,
21904            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21905            window,
21906            cx,
21907        )
21908    });
21909    format.await.unwrap();
21910
21911    assert_eq!(
21912        editor.update(cx, |editor, cx| editor.text(cx)),
21913        buffer_text.to_string()
21914            + prettier_format_suffix
21915            + "\ntypescript\n"
21916            + prettier_format_suffix
21917            + "\ntypescript",
21918        "Autoformatting (via test prettier) was not applied to the original buffer text",
21919    );
21920}
21921
21922#[gpui::test]
21923async fn test_range_format_with_prettier(cx: &mut TestAppContext) {
21924    init_test(cx, |settings| {
21925        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21926    });
21927
21928    let fs = FakeFs::new(cx.executor());
21929    fs.insert_file(path!("/file.ts"), Default::default()).await;
21930
21931    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
21932    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21933
21934    language_registry.add(Arc::new(Language::new(
21935        LanguageConfig {
21936            name: "TypeScript".into(),
21937            matcher: LanguageMatcher {
21938                path_suffixes: vec!["ts".to_string()],
21939                ..Default::default()
21940            },
21941            ..Default::default()
21942        },
21943        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21944    )));
21945    update_test_language_settings(cx, &|settings| {
21946        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21947    });
21948
21949    let test_plugin = "test_plugin";
21950    let _ = language_registry.register_fake_lsp(
21951        "TypeScript",
21952        FakeLspAdapter {
21953            prettier_plugins: vec![test_plugin],
21954            ..Default::default()
21955        },
21956    );
21957
21958    let prettier_range_format_suffix = project::TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
21959    let buffer = project
21960        .update(cx, |project, cx| {
21961            project.open_local_buffer(path!("/file.ts"), cx)
21962        })
21963        .await
21964        .unwrap();
21965
21966    let buffer_text = "one\ntwo\nthree\nfour\nfive\n";
21967    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21968    let (editor, cx) = cx.add_window_view(|window, cx| {
21969        build_editor_with_project(project.clone(), buffer, window, cx)
21970    });
21971    editor.update_in(cx, |editor, window, cx| {
21972        editor.set_text(buffer_text, window, cx)
21973    });
21974
21975    cx.executor().run_until_parked();
21976
21977    editor.update_in(cx, |editor, window, cx| {
21978        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
21979            s.select_ranges([Point::new(1, 0)..Point::new(3, 0)])
21980        });
21981    });
21982
21983    let format = editor
21984        .update_in(cx, |editor, window, cx| {
21985            editor.format_selections(&FormatSelections, window, cx)
21986        })
21987        .unwrap();
21988    format.await.unwrap();
21989
21990    assert_eq!(
21991        editor.update(cx, |editor, cx| editor.text(cx)),
21992        format!("one\ntwo{prettier_range_format_suffix}\nthree\nfour\nfive\n"),
21993        "Range formatting (via test prettier) was not applied to the buffer text",
21994    );
21995}
21996
21997#[gpui::test]
21998async fn test_range_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
21999    init_test(cx, |settings| {
22000        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
22001    });
22002
22003    let fs = FakeFs::new(cx.executor());
22004    fs.insert_file(path!("/file.settings"), Default::default())
22005        .await;
22006
22007    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
22008    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22009
22010    let ts_lang = Arc::new(Language::new(
22011        LanguageConfig {
22012            name: "TypeScript".into(),
22013            matcher: LanguageMatcher {
22014                path_suffixes: vec!["ts".to_string()],
22015                ..LanguageMatcher::default()
22016            },
22017            prettier_parser_name: Some("typescript".to_string()),
22018            ..LanguageConfig::default()
22019        },
22020        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22021    ));
22022
22023    language_registry.add(ts_lang.clone());
22024
22025    update_test_language_settings(cx, &|settings| {
22026        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
22027    });
22028
22029    let test_plugin = "test_plugin";
22030    let _ = language_registry.register_fake_lsp(
22031        "TypeScript",
22032        FakeLspAdapter {
22033            prettier_plugins: vec![test_plugin],
22034            ..Default::default()
22035        },
22036    );
22037
22038    let prettier_range_format_suffix = project::TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
22039    let buffer = project
22040        .update(cx, |project, cx| {
22041            project.open_local_buffer(path!("/file.settings"), cx)
22042        })
22043        .await
22044        .unwrap();
22045
22046    project.update(cx, |project, cx| {
22047        project.set_language_for_buffer(&buffer, ts_lang, cx)
22048    });
22049
22050    let buffer_text = "one\ntwo\nthree\nfour\nfive\n";
22051    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22052    let (editor, cx) = cx.add_window_view(|window, cx| {
22053        build_editor_with_project(project.clone(), buffer, window, cx)
22054    });
22055    editor.update_in(cx, |editor, window, cx| {
22056        editor.set_text(buffer_text, window, cx)
22057    });
22058
22059    cx.executor().run_until_parked();
22060
22061    editor.update_in(cx, |editor, window, cx| {
22062        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
22063            s.select_ranges([Point::new(1, 0)..Point::new(3, 0)])
22064        });
22065    });
22066
22067    let format = editor
22068        .update_in(cx, |editor, window, cx| {
22069            editor.format_selections(&FormatSelections, window, cx)
22070        })
22071        .unwrap();
22072    format.await.unwrap();
22073
22074    assert_eq!(
22075        editor.update(cx, |editor, cx| editor.text(cx)),
22076        format!("one\ntwo{prettier_range_format_suffix}\ntypescript\nthree\nfour\nfive\n"),
22077        "Range formatting (via test prettier) was not applied with explicit language",
22078    );
22079}
22080
22081#[gpui::test]
22082async fn test_addition_reverts(cx: &mut TestAppContext) {
22083    init_test(cx, |_| {});
22084    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22085    let base_text = indoc! {r#"
22086        struct Row;
22087        struct Row1;
22088        struct Row2;
22089
22090        struct Row4;
22091        struct Row5;
22092        struct Row6;
22093
22094        struct Row8;
22095        struct Row9;
22096        struct Row10;"#};
22097
22098    // When addition hunks are not adjacent to carets, no hunk revert is performed
22099    assert_hunk_revert(
22100        indoc! {r#"struct Row;
22101                   struct Row1;
22102                   struct Row1.1;
22103                   struct Row1.2;
22104                   struct Row2;ˇ
22105
22106                   struct Row4;
22107                   struct Row5;
22108                   struct Row6;
22109
22110                   struct Row8;
22111                   ˇstruct Row9;
22112                   struct Row9.1;
22113                   struct Row9.2;
22114                   struct Row9.3;
22115                   struct Row10;"#},
22116        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
22117        indoc! {r#"struct Row;
22118                   struct Row1;
22119                   struct Row1.1;
22120                   struct Row1.2;
22121                   struct Row2;ˇ
22122
22123                   struct Row4;
22124                   struct Row5;
22125                   struct Row6;
22126
22127                   struct Row8;
22128                   ˇstruct Row9;
22129                   struct Row9.1;
22130                   struct Row9.2;
22131                   struct Row9.3;
22132                   struct Row10;"#},
22133        base_text,
22134        &mut cx,
22135    );
22136    // Same for selections
22137    assert_hunk_revert(
22138        indoc! {r#"struct Row;
22139                   struct Row1;
22140                   struct Row2;
22141                   struct Row2.1;
22142                   struct Row2.2;
22143                   «ˇ
22144                   struct Row4;
22145                   struct» Row5;
22146                   «struct Row6;
22147                   ˇ»
22148                   struct Row9.1;
22149                   struct Row9.2;
22150                   struct Row9.3;
22151                   struct Row8;
22152                   struct Row9;
22153                   struct Row10;"#},
22154        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
22155        indoc! {r#"struct Row;
22156                   struct Row1;
22157                   struct Row2;
22158                   struct Row2.1;
22159                   struct Row2.2;
22160                   «ˇ
22161                   struct Row4;
22162                   struct» Row5;
22163                   «struct Row6;
22164                   ˇ»
22165                   struct Row9.1;
22166                   struct Row9.2;
22167                   struct Row9.3;
22168                   struct Row8;
22169                   struct Row9;
22170                   struct Row10;"#},
22171        base_text,
22172        &mut cx,
22173    );
22174
22175    // When carets and selections intersect the addition hunks, those are reverted.
22176    // Adjacent carets got merged.
22177    assert_hunk_revert(
22178        indoc! {r#"struct Row;
22179                   ˇ// something on the top
22180                   struct Row1;
22181                   struct Row2;
22182                   struct Roˇw3.1;
22183                   struct Row2.2;
22184                   struct Row2.3;ˇ
22185
22186                   struct Row4;
22187                   struct ˇRow5.1;
22188                   struct Row5.2;
22189                   struct «Rowˇ»5.3;
22190                   struct Row5;
22191                   struct Row6;
22192                   ˇ
22193                   struct Row9.1;
22194                   struct «Rowˇ»9.2;
22195                   struct «ˇRow»9.3;
22196                   struct Row8;
22197                   struct Row9;
22198                   «ˇ// something on bottom»
22199                   struct Row10;"#},
22200        vec![
22201            DiffHunkStatusKind::Added,
22202            DiffHunkStatusKind::Added,
22203            DiffHunkStatusKind::Added,
22204            DiffHunkStatusKind::Added,
22205            DiffHunkStatusKind::Added,
22206        ],
22207        indoc! {r#"struct Row;
22208                   ˇstruct Row1;
22209                   struct Row2;
22210                   ˇ
22211                   struct Row4;
22212                   ˇstruct Row5;
22213                   struct Row6;
22214                   ˇ
22215                   ˇstruct Row8;
22216                   struct Row9;
22217                   ˇstruct Row10;"#},
22218        base_text,
22219        &mut cx,
22220    );
22221}
22222
22223#[gpui::test]
22224async fn test_modification_reverts(cx: &mut TestAppContext) {
22225    init_test(cx, |_| {});
22226    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22227    let base_text = indoc! {r#"
22228        struct Row;
22229        struct Row1;
22230        struct Row2;
22231
22232        struct Row4;
22233        struct Row5;
22234        struct Row6;
22235
22236        struct Row8;
22237        struct Row9;
22238        struct Row10;"#};
22239
22240    // Modification hunks behave the same as the addition ones.
22241    assert_hunk_revert(
22242        indoc! {r#"struct Row;
22243                   struct Row1;
22244                   struct Row33;
22245                   ˇ
22246                   struct Row4;
22247                   struct Row5;
22248                   struct Row6;
22249                   ˇ
22250                   struct Row99;
22251                   struct Row9;
22252                   struct Row10;"#},
22253        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
22254        indoc! {r#"struct Row;
22255                   struct Row1;
22256                   struct Row33;
22257                   ˇ
22258                   struct Row4;
22259                   struct Row5;
22260                   struct Row6;
22261                   ˇ
22262                   struct Row99;
22263                   struct Row9;
22264                   struct Row10;"#},
22265        base_text,
22266        &mut cx,
22267    );
22268    assert_hunk_revert(
22269        indoc! {r#"struct Row;
22270                   struct Row1;
22271                   struct Row33;
22272                   «ˇ
22273                   struct Row4;
22274                   struct» Row5;
22275                   «struct Row6;
22276                   ˇ»
22277                   struct Row99;
22278                   struct Row9;
22279                   struct Row10;"#},
22280        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
22281        indoc! {r#"struct Row;
22282                   struct Row1;
22283                   struct Row33;
22284                   «ˇ
22285                   struct Row4;
22286                   struct» Row5;
22287                   «struct Row6;
22288                   ˇ»
22289                   struct Row99;
22290                   struct Row9;
22291                   struct Row10;"#},
22292        base_text,
22293        &mut cx,
22294    );
22295
22296    assert_hunk_revert(
22297        indoc! {r#"ˇstruct Row1.1;
22298                   struct Row1;
22299                   «ˇstr»uct Row22;
22300
22301                   struct ˇRow44;
22302                   struct Row5;
22303                   struct «Rˇ»ow66;ˇ
22304
22305                   «struˇ»ct Row88;
22306                   struct Row9;
22307                   struct Row1011;ˇ"#},
22308        vec![
22309            DiffHunkStatusKind::Modified,
22310            DiffHunkStatusKind::Modified,
22311            DiffHunkStatusKind::Modified,
22312            DiffHunkStatusKind::Modified,
22313            DiffHunkStatusKind::Modified,
22314            DiffHunkStatusKind::Modified,
22315        ],
22316        indoc! {r#"struct Row;
22317                   ˇstruct Row1;
22318                   struct Row2;
22319                   ˇ
22320                   struct Row4;
22321                   ˇstruct Row5;
22322                   struct Row6;
22323                   ˇ
22324                   struct Row8;
22325                   ˇstruct Row9;
22326                   struct Row10;ˇ"#},
22327        base_text,
22328        &mut cx,
22329    );
22330}
22331
22332#[gpui::test]
22333async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
22334    init_test(cx, |_| {});
22335    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22336    let base_text = indoc! {r#"
22337        one
22338
22339        two
22340        three
22341        "#};
22342
22343    cx.set_head_text(base_text);
22344    cx.set_state("\nˇ\n");
22345    cx.executor().run_until_parked();
22346    cx.update_editor(|editor, _window, cx| {
22347        editor.expand_selected_diff_hunks(cx);
22348    });
22349    cx.executor().run_until_parked();
22350    cx.update_editor(|editor, window, cx| {
22351        editor.backspace(&Default::default(), window, cx);
22352    });
22353    cx.run_until_parked();
22354    cx.assert_state_with_diff(
22355        indoc! {r#"
22356
22357        - two
22358        - threeˇ
22359        +
22360        "#}
22361        .to_string(),
22362    );
22363}
22364
22365#[gpui::test]
22366async fn test_deletion_reverts(cx: &mut TestAppContext) {
22367    init_test(cx, |_| {});
22368    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22369    let base_text = indoc! {r#"struct Row;
22370struct Row1;
22371struct Row2;
22372
22373struct Row4;
22374struct Row5;
22375struct Row6;
22376
22377struct Row8;
22378struct Row9;
22379struct Row10;"#};
22380
22381    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
22382    assert_hunk_revert(
22383        indoc! {r#"struct Row;
22384                   struct Row2;
22385
22386                   ˇstruct Row4;
22387                   struct Row5;
22388                   struct Row6;
22389                   ˇ
22390                   struct Row8;
22391                   struct Row10;"#},
22392        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22393        indoc! {r#"struct Row;
22394                   struct Row2;
22395
22396                   ˇstruct Row4;
22397                   struct Row5;
22398                   struct Row6;
22399                   ˇ
22400                   struct Row8;
22401                   struct Row10;"#},
22402        base_text,
22403        &mut cx,
22404    );
22405    assert_hunk_revert(
22406        indoc! {r#"struct Row;
22407                   struct Row2;
22408
22409                   «ˇstruct Row4;
22410                   struct» Row5;
22411                   «struct Row6;
22412                   ˇ»
22413                   struct Row8;
22414                   struct Row10;"#},
22415        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22416        indoc! {r#"struct Row;
22417                   struct Row2;
22418
22419                   «ˇstruct Row4;
22420                   struct» Row5;
22421                   «struct Row6;
22422                   ˇ»
22423                   struct Row8;
22424                   struct Row10;"#},
22425        base_text,
22426        &mut cx,
22427    );
22428
22429    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
22430    assert_hunk_revert(
22431        indoc! {r#"struct Row;
22432                   ˇstruct Row2;
22433
22434                   struct Row4;
22435                   struct Row5;
22436                   struct Row6;
22437
22438                   struct Row8;ˇ
22439                   struct Row10;"#},
22440        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22441        indoc! {r#"struct Row;
22442                   struct Row1;
22443                   ˇstruct Row2;
22444
22445                   struct Row4;
22446                   struct Row5;
22447                   struct Row6;
22448
22449                   struct Row8;ˇ
22450                   struct Row9;
22451                   struct Row10;"#},
22452        base_text,
22453        &mut cx,
22454    );
22455    assert_hunk_revert(
22456        indoc! {r#"struct Row;
22457                   struct Row2«ˇ;
22458                   struct Row4;
22459                   struct» Row5;
22460                   «struct Row6;
22461
22462                   struct Row8;ˇ»
22463                   struct Row10;"#},
22464        vec![
22465            DiffHunkStatusKind::Deleted,
22466            DiffHunkStatusKind::Deleted,
22467            DiffHunkStatusKind::Deleted,
22468        ],
22469        indoc! {r#"struct Row;
22470                   struct Row1;
22471                   struct Row2«ˇ;
22472
22473                   struct Row4;
22474                   struct» Row5;
22475                   «struct Row6;
22476
22477                   struct Row8;ˇ»
22478                   struct Row9;
22479                   struct Row10;"#},
22480        base_text,
22481        &mut cx,
22482    );
22483}
22484
22485#[gpui::test]
22486async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
22487    init_test(cx, |_| {});
22488
22489    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
22490    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
22491    let base_text_3 =
22492        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
22493
22494    let text_1 = edit_first_char_of_every_line(base_text_1);
22495    let text_2 = edit_first_char_of_every_line(base_text_2);
22496    let text_3 = edit_first_char_of_every_line(base_text_3);
22497
22498    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
22499    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
22500    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
22501
22502    let multibuffer = cx.new(|cx| {
22503        let mut multibuffer = MultiBuffer::new(ReadWrite);
22504        multibuffer.set_excerpts_for_path(
22505            PathKey::sorted(0),
22506            buffer_1.clone(),
22507            [
22508                Point::new(0, 0)..Point::new(2, 0),
22509                Point::new(5, 0)..Point::new(6, 0),
22510                Point::new(9, 0)..Point::new(9, 4),
22511            ],
22512            0,
22513            cx,
22514        );
22515        multibuffer.set_excerpts_for_path(
22516            PathKey::sorted(1),
22517            buffer_2.clone(),
22518            [
22519                Point::new(0, 0)..Point::new(2, 0),
22520                Point::new(5, 0)..Point::new(6, 0),
22521                Point::new(9, 0)..Point::new(9, 4),
22522            ],
22523            0,
22524            cx,
22525        );
22526        multibuffer.set_excerpts_for_path(
22527            PathKey::sorted(2),
22528            buffer_3.clone(),
22529            [
22530                Point::new(0, 0)..Point::new(2, 0),
22531                Point::new(5, 0)..Point::new(6, 0),
22532                Point::new(9, 0)..Point::new(9, 4),
22533            ],
22534            0,
22535            cx,
22536        );
22537        multibuffer
22538    });
22539
22540    let fs = FakeFs::new(cx.executor());
22541    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22542    let (editor, cx) = cx
22543        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
22544    editor.update_in(cx, |editor, _window, cx| {
22545        for (buffer, diff_base) in [
22546            (buffer_1.clone(), base_text_1),
22547            (buffer_2.clone(), base_text_2),
22548            (buffer_3.clone(), base_text_3),
22549        ] {
22550            let diff = cx.new(|cx| {
22551                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
22552            });
22553            editor
22554                .buffer
22555                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
22556        }
22557    });
22558    cx.executor().run_until_parked();
22559
22560    editor.update_in(cx, |editor, window, cx| {
22561        assert_eq!(editor.display_text(cx), "\n\nXaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\n\n\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
22562        editor.select_all(&SelectAll, window, cx);
22563        editor.git_restore(&Default::default(), window, cx);
22564    });
22565    cx.executor().run_until_parked();
22566
22567    // When all ranges are selected, all buffer hunks are reverted.
22568    editor.update(cx, |editor, cx| {
22569        assert_eq!(editor.display_text(cx), "\n\naaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\n\n\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n\n\n");
22570    });
22571    buffer_1.update(cx, |buffer, _| {
22572        assert_eq!(buffer.text(), base_text_1);
22573    });
22574    buffer_2.update(cx, |buffer, _| {
22575        assert_eq!(buffer.text(), base_text_2);
22576    });
22577    buffer_3.update(cx, |buffer, _| {
22578        assert_eq!(buffer.text(), base_text_3);
22579    });
22580
22581    editor.update_in(cx, |editor, window, cx| {
22582        editor.undo(&Default::default(), window, cx);
22583    });
22584
22585    editor.update_in(cx, |editor, window, cx| {
22586        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22587            s.select_ranges(Some(Point::new(0, 0)..Point::new(5, 0)));
22588        });
22589        editor.git_restore(&Default::default(), window, cx);
22590    });
22591
22592    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
22593    // but not affect buffer_2 and its related excerpts.
22594    editor.update(cx, |editor, cx| {
22595        assert_eq!(
22596            editor.display_text(cx),
22597            "\n\naaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\n\n\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\n\n\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
22598        );
22599    });
22600    buffer_1.update(cx, |buffer, _| {
22601        assert_eq!(buffer.text(), base_text_1);
22602    });
22603    buffer_2.update(cx, |buffer, _| {
22604        assert_eq!(
22605            buffer.text(),
22606            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
22607        );
22608    });
22609    buffer_3.update(cx, |buffer, _| {
22610        assert_eq!(
22611            buffer.text(),
22612            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
22613        );
22614    });
22615
22616    fn edit_first_char_of_every_line(text: &str) -> String {
22617        text.split('\n')
22618            .map(|line| format!("X{}", &line[1..]))
22619            .collect::<Vec<_>>()
22620            .join("\n")
22621    }
22622}
22623
22624#[gpui::test]
22625async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
22626    init_test(cx, |_| {});
22627
22628    let cols = 4;
22629    let rows = 10;
22630    let sample_text_1 = sample_text(rows, cols, 'a');
22631    assert_eq!(
22632        sample_text_1,
22633        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
22634    );
22635    let sample_text_2 = sample_text(rows, cols, 'l');
22636    assert_eq!(
22637        sample_text_2,
22638        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
22639    );
22640    let sample_text_3 = sample_text(rows, cols, 'v');
22641    assert_eq!(
22642        sample_text_3,
22643        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
22644    );
22645
22646    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
22647    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
22648    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
22649
22650    let multi_buffer = cx.new(|cx| {
22651        let mut multibuffer = MultiBuffer::new(ReadWrite);
22652        multibuffer.set_excerpts_for_path(
22653            PathKey::sorted(0),
22654            buffer_1.clone(),
22655            [
22656                Point::new(0, 0)..Point::new(2, 0),
22657                Point::new(5, 0)..Point::new(6, 0),
22658                Point::new(9, 0)..Point::new(9, 4),
22659            ],
22660            0,
22661            cx,
22662        );
22663        multibuffer.set_excerpts_for_path(
22664            PathKey::sorted(1),
22665            buffer_2.clone(),
22666            [
22667                Point::new(0, 0)..Point::new(2, 0),
22668                Point::new(5, 0)..Point::new(6, 0),
22669                Point::new(9, 0)..Point::new(9, 4),
22670            ],
22671            0,
22672            cx,
22673        );
22674        multibuffer.set_excerpts_for_path(
22675            PathKey::sorted(2),
22676            buffer_3.clone(),
22677            [
22678                Point::new(0, 0)..Point::new(2, 0),
22679                Point::new(5, 0)..Point::new(6, 0),
22680                Point::new(9, 0)..Point::new(9, 4),
22681            ],
22682            0,
22683            cx,
22684        );
22685        multibuffer
22686    });
22687
22688    let fs = FakeFs::new(cx.executor());
22689    fs.insert_tree(
22690        "/a",
22691        json!({
22692            "main.rs": sample_text_1,
22693            "other.rs": sample_text_2,
22694            "lib.rs": sample_text_3,
22695        }),
22696    )
22697    .await;
22698    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22699    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
22700    let workspace = window
22701        .read_with(cx, |mw, _| mw.workspace().clone())
22702        .unwrap();
22703    let cx = &mut VisualTestContext::from_window(*window, cx);
22704    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22705        Editor::new(
22706            EditorMode::full(),
22707            multi_buffer,
22708            Some(project.clone()),
22709            window,
22710            cx,
22711        )
22712    });
22713    let multibuffer_item_id = workspace.update_in(cx, |workspace, window, cx| {
22714        assert!(
22715            workspace.active_item(cx).is_none(),
22716            "active item should be None before the first item is added"
22717        );
22718        workspace.add_item_to_active_pane(
22719            Box::new(multi_buffer_editor.clone()),
22720            None,
22721            true,
22722            window,
22723            cx,
22724        );
22725        let active_item = workspace
22726            .active_item(cx)
22727            .expect("should have an active item after adding the multi buffer");
22728        assert_eq!(
22729            active_item.buffer_kind(cx),
22730            ItemBufferKind::Multibuffer,
22731            "A multi buffer was expected to active after adding"
22732        );
22733        active_item.item_id()
22734    });
22735
22736    cx.executor().run_until_parked();
22737
22738    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22739        editor.change_selections(
22740            SelectionEffects::scroll(Autoscroll::Next),
22741            window,
22742            cx,
22743            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
22744        );
22745        editor.open_excerpts(&OpenExcerpts, window, cx);
22746    });
22747    cx.executor().run_until_parked();
22748    let first_item_id = workspace.update_in(cx, |workspace, window, cx| {
22749        let active_item = workspace
22750            .active_item(cx)
22751            .expect("should have an active item after navigating into the 1st buffer");
22752        let first_item_id = active_item.item_id();
22753        assert_ne!(
22754            first_item_id, multibuffer_item_id,
22755            "Should navigate into the 1st buffer and activate it"
22756        );
22757        assert_eq!(
22758            active_item.buffer_kind(cx),
22759            ItemBufferKind::Singleton,
22760            "New active item should be a singleton buffer"
22761        );
22762        assert_eq!(
22763            active_item
22764                .act_as::<Editor>(cx)
22765                .expect("should have navigated into an editor for the 1st buffer")
22766                .read(cx)
22767                .text(cx),
22768            sample_text_1
22769        );
22770
22771        workspace
22772            .go_back(workspace.active_pane().downgrade(), window, cx)
22773            .detach_and_log_err(cx);
22774
22775        first_item_id
22776    });
22777
22778    cx.executor().run_until_parked();
22779    workspace.update_in(cx, |workspace, _, cx| {
22780        let active_item = workspace
22781            .active_item(cx)
22782            .expect("should have an active item after navigating back");
22783        assert_eq!(
22784            active_item.item_id(),
22785            multibuffer_item_id,
22786            "Should navigate back to the multi buffer"
22787        );
22788        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22789    });
22790
22791    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22792        editor.change_selections(
22793            SelectionEffects::scroll(Autoscroll::Next),
22794            window,
22795            cx,
22796            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
22797        );
22798        editor.open_excerpts(&OpenExcerpts, window, cx);
22799    });
22800    cx.executor().run_until_parked();
22801    let second_item_id = workspace.update_in(cx, |workspace, window, cx| {
22802        let active_item = workspace
22803            .active_item(cx)
22804            .expect("should have an active item after navigating into the 2nd buffer");
22805        let second_item_id = active_item.item_id();
22806        assert_ne!(
22807            second_item_id, multibuffer_item_id,
22808            "Should navigate away from the multibuffer"
22809        );
22810        assert_ne!(
22811            second_item_id, first_item_id,
22812            "Should navigate into the 2nd buffer and activate it"
22813        );
22814        assert_eq!(
22815            active_item.buffer_kind(cx),
22816            ItemBufferKind::Singleton,
22817            "New active item should be a singleton buffer"
22818        );
22819        assert_eq!(
22820            active_item
22821                .act_as::<Editor>(cx)
22822                .expect("should have navigated into an editor")
22823                .read(cx)
22824                .text(cx),
22825            sample_text_2
22826        );
22827
22828        workspace
22829            .go_back(workspace.active_pane().downgrade(), window, cx)
22830            .detach_and_log_err(cx);
22831
22832        second_item_id
22833    });
22834
22835    cx.executor().run_until_parked();
22836    workspace.update_in(cx, |workspace, _, cx| {
22837        let active_item = workspace
22838            .active_item(cx)
22839            .expect("should have an active item after navigating back from the 2nd buffer");
22840        assert_eq!(
22841            active_item.item_id(),
22842            multibuffer_item_id,
22843            "Should navigate back from the 2nd buffer to the multi buffer"
22844        );
22845        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22846    });
22847
22848    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22849        editor.change_selections(
22850            SelectionEffects::scroll(Autoscroll::Next),
22851            window,
22852            cx,
22853            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
22854        );
22855        editor.open_excerpts(&OpenExcerpts, window, cx);
22856    });
22857    cx.executor().run_until_parked();
22858    workspace.update_in(cx, |workspace, window, cx| {
22859        let active_item = workspace
22860            .active_item(cx)
22861            .expect("should have an active item after navigating into the 3rd buffer");
22862        let third_item_id = active_item.item_id();
22863        assert_ne!(
22864            third_item_id, multibuffer_item_id,
22865            "Should navigate into the 3rd buffer and activate it"
22866        );
22867        assert_ne!(third_item_id, first_item_id);
22868        assert_ne!(third_item_id, second_item_id);
22869        assert_eq!(
22870            active_item.buffer_kind(cx),
22871            ItemBufferKind::Singleton,
22872            "New active item should be a singleton buffer"
22873        );
22874        assert_eq!(
22875            active_item
22876                .act_as::<Editor>(cx)
22877                .expect("should have navigated into an editor")
22878                .read(cx)
22879                .text(cx),
22880            sample_text_3
22881        );
22882
22883        workspace
22884            .go_back(workspace.active_pane().downgrade(), window, cx)
22885            .detach_and_log_err(cx);
22886    });
22887
22888    cx.executor().run_until_parked();
22889    workspace.update_in(cx, |workspace, _, cx| {
22890        let active_item = workspace
22891            .active_item(cx)
22892            .expect("should have an active item after navigating back from the 3rd buffer");
22893        assert_eq!(
22894            active_item.item_id(),
22895            multibuffer_item_id,
22896            "Should navigate back from the 3rd buffer to the multi buffer"
22897        );
22898        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22899    });
22900}
22901
22902#[gpui::test]
22903async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22904    init_test(cx, |_| {});
22905
22906    let mut cx = EditorTestContext::new(cx).await;
22907
22908    let diff_base = r#"
22909        use some::mod;
22910
22911        const A: u32 = 42;
22912
22913        fn main() {
22914            println!("hello");
22915
22916            println!("world");
22917        }
22918        "#
22919    .unindent();
22920
22921    cx.set_state(
22922        &r#"
22923        use some::modified;
22924
22925        ˇ
22926        fn main() {
22927            println!("hello there");
22928
22929            println!("around the");
22930            println!("world");
22931        }
22932        "#
22933        .unindent(),
22934    );
22935
22936    cx.set_head_text(&diff_base);
22937    executor.run_until_parked();
22938
22939    cx.update_editor(|editor, window, cx| {
22940        editor.go_to_next_hunk(&GoToHunk, window, cx);
22941        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22942    });
22943    executor.run_until_parked();
22944    cx.assert_state_with_diff(
22945        r#"
22946          use some::modified;
22947
22948
22949          fn main() {
22950        -     println!("hello");
22951        + ˇ    println!("hello there");
22952
22953              println!("around the");
22954              println!("world");
22955          }
22956        "#
22957        .unindent(),
22958    );
22959
22960    cx.update_editor(|editor, window, cx| {
22961        for _ in 0..2 {
22962            editor.go_to_next_hunk(&GoToHunk, window, cx);
22963            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22964        }
22965    });
22966    executor.run_until_parked();
22967    cx.assert_state_with_diff(
22968        r#"
22969        - use some::mod;
22970        + ˇuse some::modified;
22971
22972
22973          fn main() {
22974        -     println!("hello");
22975        +     println!("hello there");
22976
22977        +     println!("around the");
22978              println!("world");
22979          }
22980        "#
22981        .unindent(),
22982    );
22983
22984    cx.update_editor(|editor, window, cx| {
22985        editor.go_to_next_hunk(&GoToHunk, window, cx);
22986        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22987    });
22988    executor.run_until_parked();
22989    cx.assert_state_with_diff(
22990        r#"
22991        - use some::mod;
22992        + use some::modified;
22993
22994        - const A: u32 = 42;
22995          ˇ
22996          fn main() {
22997        -     println!("hello");
22998        +     println!("hello there");
22999
23000        +     println!("around the");
23001              println!("world");
23002          }
23003        "#
23004        .unindent(),
23005    );
23006
23007    cx.update_editor(|editor, window, cx| {
23008        editor.cancel(&Cancel, window, cx);
23009    });
23010
23011    cx.assert_state_with_diff(
23012        r#"
23013          use some::modified;
23014
23015          ˇ
23016          fn main() {
23017              println!("hello there");
23018
23019              println!("around the");
23020              println!("world");
23021          }
23022        "#
23023        .unindent(),
23024    );
23025}
23026
23027#[gpui::test]
23028async fn test_diff_base_change_with_expanded_diff_hunks(
23029    executor: BackgroundExecutor,
23030    cx: &mut TestAppContext,
23031) {
23032    init_test(cx, |_| {});
23033
23034    let mut cx = EditorTestContext::new(cx).await;
23035
23036    let diff_base = r#"
23037        use some::mod1;
23038        use some::mod2;
23039
23040        const A: u32 = 42;
23041        const B: u32 = 42;
23042        const C: u32 = 42;
23043
23044        fn main() {
23045            println!("hello");
23046
23047            println!("world");
23048        }
23049        "#
23050    .unindent();
23051
23052    cx.set_state(
23053        &r#"
23054        use some::mod2;
23055
23056        const A: u32 = 42;
23057        const C: u32 = 42;
23058
23059        fn main(ˇ) {
23060            //println!("hello");
23061
23062            println!("world");
23063            //
23064            //
23065        }
23066        "#
23067        .unindent(),
23068    );
23069
23070    cx.set_head_text(&diff_base);
23071    executor.run_until_parked();
23072
23073    cx.update_editor(|editor, window, cx| {
23074        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23075    });
23076    executor.run_until_parked();
23077    cx.assert_state_with_diff(
23078        r#"
23079        - use some::mod1;
23080          use some::mod2;
23081
23082          const A: u32 = 42;
23083        - const B: u32 = 42;
23084          const C: u32 = 42;
23085
23086          fn main(ˇ) {
23087        -     println!("hello");
23088        +     //println!("hello");
23089
23090              println!("world");
23091        +     //
23092        +     //
23093          }
23094        "#
23095        .unindent(),
23096    );
23097
23098    cx.set_head_text("new diff base!");
23099    executor.run_until_parked();
23100    cx.assert_state_with_diff(
23101        r#"
23102        - new diff base!
23103        + use some::mod2;
23104        +
23105        + const A: u32 = 42;
23106        + const C: u32 = 42;
23107        +
23108        + fn main(ˇ) {
23109        +     //println!("hello");
23110        +
23111        +     println!("world");
23112        +     //
23113        +     //
23114        + }
23115        "#
23116        .unindent(),
23117    );
23118}
23119
23120#[gpui::test]
23121async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
23122    init_test(cx, |_| {});
23123
23124    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
23125    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
23126    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
23127    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
23128    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
23129    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
23130
23131    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
23132    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
23133    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
23134
23135    let multi_buffer = cx.new(|cx| {
23136        let mut multibuffer = MultiBuffer::new(ReadWrite);
23137        multibuffer.set_excerpts_for_path(
23138            PathKey::sorted(0),
23139            buffer_1.clone(),
23140            [
23141                Point::new(0, 0)..Point::new(2, 3),
23142                Point::new(5, 0)..Point::new(6, 3),
23143                Point::new(9, 0)..Point::new(10, 3),
23144            ],
23145            0,
23146            cx,
23147        );
23148        multibuffer.set_excerpts_for_path(
23149            PathKey::sorted(1),
23150            buffer_2.clone(),
23151            [
23152                Point::new(0, 0)..Point::new(2, 3),
23153                Point::new(5, 0)..Point::new(6, 3),
23154                Point::new(9, 0)..Point::new(10, 3),
23155            ],
23156            0,
23157            cx,
23158        );
23159        multibuffer.set_excerpts_for_path(
23160            PathKey::sorted(2),
23161            buffer_3.clone(),
23162            [
23163                Point::new(0, 0)..Point::new(2, 3),
23164                Point::new(5, 0)..Point::new(6, 3),
23165                Point::new(9, 0)..Point::new(10, 3),
23166            ],
23167            0,
23168            cx,
23169        );
23170        assert_eq!(multibuffer.read(cx).excerpts().count(), 9);
23171        multibuffer
23172    });
23173
23174    let editor =
23175        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
23176    editor
23177        .update(cx, |editor, _window, cx| {
23178            for (buffer, diff_base) in [
23179                (buffer_1.clone(), file_1_old),
23180                (buffer_2.clone(), file_2_old),
23181                (buffer_3.clone(), file_3_old),
23182            ] {
23183                let diff = cx.new(|cx| {
23184                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
23185                });
23186                editor
23187                    .buffer
23188                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
23189            }
23190        })
23191        .unwrap();
23192
23193    let mut cx = EditorTestContext::for_editor(editor, cx).await;
23194    cx.run_until_parked();
23195
23196    cx.assert_editor_state(
23197        &"
23198            ˇaaa
23199            ccc
23200            ddd
23201            ggg
23202            hhh
23203
23204            lll
23205            mmm
23206            NNN
23207            qqq
23208            rrr
23209            uuu
23210            111
23211            222
23212            333
23213            666
23214            777
23215            000
23216            !!!"
23217        .unindent(),
23218    );
23219
23220    cx.update_editor(|editor, window, cx| {
23221        editor.select_all(&SelectAll, window, cx);
23222        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23223    });
23224    cx.executor().run_until_parked();
23225
23226    cx.assert_state_with_diff(
23227        "
23228            «aaa
23229          - bbb
23230            ccc
23231            ddd
23232            ggg
23233            hhh
23234
23235            lll
23236            mmm
23237          - nnn
23238          + NNN
23239            qqq
23240            rrr
23241            uuu
23242            111
23243            222
23244            333
23245          + 666
23246            777
23247            000
23248            !!!ˇ»"
23249            .unindent(),
23250    );
23251}
23252
23253#[gpui::test]
23254async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
23255    init_test(cx, |_| {});
23256
23257    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
23258    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
23259
23260    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
23261    let multi_buffer = cx.new(|cx| {
23262        let mut multibuffer = MultiBuffer::new(ReadWrite);
23263        multibuffer.set_excerpts_for_path(
23264            PathKey::sorted(0),
23265            buffer.clone(),
23266            [
23267                Point::new(0, 0)..Point::new(1, 3),
23268                Point::new(4, 0)..Point::new(6, 3),
23269                Point::new(9, 0)..Point::new(9, 3),
23270            ],
23271            0,
23272            cx,
23273        );
23274        assert_eq!(multibuffer.read(cx).excerpts().count(), 3);
23275        multibuffer
23276    });
23277
23278    let editor =
23279        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
23280    editor
23281        .update(cx, |editor, _window, cx| {
23282            let diff = cx.new(|cx| {
23283                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
23284            });
23285            editor
23286                .buffer
23287                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
23288        })
23289        .unwrap();
23290
23291    let mut cx = EditorTestContext::for_editor(editor, cx).await;
23292    cx.run_until_parked();
23293
23294    cx.update_editor(|editor, window, cx| {
23295        editor.expand_all_diff_hunks(&Default::default(), window, cx)
23296    });
23297    cx.executor().run_until_parked();
23298
23299    // When the start of a hunk coincides with the start of its excerpt,
23300    // the hunk is expanded. When the start of a hunk is earlier than
23301    // the start of its excerpt, the hunk is not expanded.
23302    cx.assert_state_with_diff(
23303        "
23304            ˇaaa
23305          - bbb
23306          + BBB
23307          - ddd
23308          - eee
23309          + DDD
23310          + EEE
23311            fff
23312            iii"
23313        .unindent(),
23314    );
23315}
23316
23317#[gpui::test]
23318async fn test_edits_around_expanded_insertion_hunks(
23319    executor: BackgroundExecutor,
23320    cx: &mut TestAppContext,
23321) {
23322    init_test(cx, |_| {});
23323
23324    let mut cx = EditorTestContext::new(cx).await;
23325
23326    let diff_base = r#"
23327        use some::mod1;
23328        use some::mod2;
23329
23330        const A: u32 = 42;
23331
23332        fn main() {
23333            println!("hello");
23334
23335            println!("world");
23336        }
23337        "#
23338    .unindent();
23339    executor.run_until_parked();
23340    cx.set_state(
23341        &r#"
23342        use some::mod1;
23343        use some::mod2;
23344
23345        const A: u32 = 42;
23346        const B: u32 = 42;
23347        const C: u32 = 42;
23348        ˇ
23349
23350        fn main() {
23351            println!("hello");
23352
23353            println!("world");
23354        }
23355        "#
23356        .unindent(),
23357    );
23358
23359    cx.set_head_text(&diff_base);
23360    executor.run_until_parked();
23361
23362    cx.update_editor(|editor, window, cx| {
23363        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23364    });
23365    executor.run_until_parked();
23366
23367    cx.assert_state_with_diff(
23368        r#"
23369        use some::mod1;
23370        use some::mod2;
23371
23372        const A: u32 = 42;
23373      + const B: u32 = 42;
23374      + const C: u32 = 42;
23375      + ˇ
23376
23377        fn main() {
23378            println!("hello");
23379
23380            println!("world");
23381        }
23382      "#
23383        .unindent(),
23384    );
23385
23386    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
23387    executor.run_until_parked();
23388
23389    cx.assert_state_with_diff(
23390        r#"
23391        use some::mod1;
23392        use some::mod2;
23393
23394        const A: u32 = 42;
23395      + const B: u32 = 42;
23396      + const C: u32 = 42;
23397      + const D: u32 = 42;
23398      + ˇ
23399
23400        fn main() {
23401            println!("hello");
23402
23403            println!("world");
23404        }
23405      "#
23406        .unindent(),
23407    );
23408
23409    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
23410    executor.run_until_parked();
23411
23412    cx.assert_state_with_diff(
23413        r#"
23414        use some::mod1;
23415        use some::mod2;
23416
23417        const A: u32 = 42;
23418      + const B: u32 = 42;
23419      + const C: u32 = 42;
23420      + const D: u32 = 42;
23421      + const E: u32 = 42;
23422      + ˇ
23423
23424        fn main() {
23425            println!("hello");
23426
23427            println!("world");
23428        }
23429      "#
23430        .unindent(),
23431    );
23432
23433    cx.update_editor(|editor, window, cx| {
23434        editor.delete_line(&DeleteLine, window, cx);
23435    });
23436    executor.run_until_parked();
23437
23438    cx.assert_state_with_diff(
23439        r#"
23440        use some::mod1;
23441        use some::mod2;
23442
23443        const A: u32 = 42;
23444      + const B: u32 = 42;
23445      + const C: u32 = 42;
23446      + const D: u32 = 42;
23447      + const E: u32 = 42;
23448        ˇ
23449        fn main() {
23450            println!("hello");
23451
23452            println!("world");
23453        }
23454      "#
23455        .unindent(),
23456    );
23457
23458    cx.update_editor(|editor, window, cx| {
23459        editor.move_up(&MoveUp, window, cx);
23460        editor.delete_line(&DeleteLine, window, cx);
23461        editor.move_up(&MoveUp, window, cx);
23462        editor.delete_line(&DeleteLine, window, cx);
23463        editor.move_up(&MoveUp, window, cx);
23464        editor.delete_line(&DeleteLine, window, cx);
23465    });
23466    executor.run_until_parked();
23467    cx.assert_state_with_diff(
23468        r#"
23469        use some::mod1;
23470        use some::mod2;
23471
23472        const A: u32 = 42;
23473      + const B: u32 = 42;
23474        ˇ
23475        fn main() {
23476            println!("hello");
23477
23478            println!("world");
23479        }
23480      "#
23481        .unindent(),
23482    );
23483
23484    cx.update_editor(|editor, window, cx| {
23485        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
23486        editor.delete_line(&DeleteLine, window, cx);
23487    });
23488    executor.run_until_parked();
23489    cx.assert_state_with_diff(
23490        r#"
23491        ˇ
23492        fn main() {
23493            println!("hello");
23494
23495            println!("world");
23496        }
23497      "#
23498        .unindent(),
23499    );
23500}
23501
23502#[gpui::test]
23503async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
23504    init_test(cx, |_| {});
23505
23506    let mut cx = EditorTestContext::new(cx).await;
23507    cx.set_head_text(indoc! { "
23508        one
23509        two
23510        three
23511        four
23512        five
23513        "
23514    });
23515    cx.set_state(indoc! { "
23516        one
23517        ˇthree
23518        five
23519    "});
23520    cx.run_until_parked();
23521    cx.update_editor(|editor, window, cx| {
23522        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23523    });
23524    cx.assert_state_with_diff(
23525        indoc! { "
23526        one
23527      - two
23528        ˇthree
23529      - four
23530        five
23531    "}
23532        .to_string(),
23533    );
23534    cx.update_editor(|editor, window, cx| {
23535        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23536    });
23537
23538    cx.assert_state_with_diff(
23539        indoc! { "
23540        one
23541        ˇthree
23542        five
23543    "}
23544        .to_string(),
23545    );
23546
23547    cx.update_editor(|editor, window, cx| {
23548        editor.move_up(&MoveUp, window, cx);
23549        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23550    });
23551    cx.assert_state_with_diff(
23552        indoc! { "
23553        ˇone
23554      - two
23555        three
23556        five
23557    "}
23558        .to_string(),
23559    );
23560
23561    cx.update_editor(|editor, window, cx| {
23562        editor.move_down(&MoveDown, window, cx);
23563        editor.move_down(&MoveDown, window, cx);
23564        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23565    });
23566    cx.assert_state_with_diff(
23567        indoc! { "
23568        one
23569      - two
23570        ˇthree
23571      - four
23572        five
23573    "}
23574        .to_string(),
23575    );
23576
23577    cx.set_state(indoc! { "
23578        one
23579        ˇTWO
23580        three
23581        four
23582        five
23583    "});
23584    cx.run_until_parked();
23585    cx.update_editor(|editor, window, cx| {
23586        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23587    });
23588
23589    cx.assert_state_with_diff(
23590        indoc! { "
23591            one
23592          - two
23593          + ˇTWO
23594            three
23595            four
23596            five
23597        "}
23598        .to_string(),
23599    );
23600    cx.update_editor(|editor, window, cx| {
23601        editor.move_up(&Default::default(), window, cx);
23602        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23603    });
23604    cx.assert_state_with_diff(
23605        indoc! { "
23606            one
23607            ˇTWO
23608            three
23609            four
23610            five
23611        "}
23612        .to_string(),
23613    );
23614}
23615
23616#[gpui::test]
23617async fn test_toggling_adjacent_diff_hunks_2(
23618    executor: BackgroundExecutor,
23619    cx: &mut TestAppContext,
23620) {
23621    init_test(cx, |_| {});
23622
23623    let mut cx = EditorTestContext::new(cx).await;
23624
23625    let diff_base = r#"
23626        lineA
23627        lineB
23628        lineC
23629        lineD
23630        "#
23631    .unindent();
23632
23633    cx.set_state(
23634        &r#"
23635        ˇlineA1
23636        lineB
23637        lineD
23638        "#
23639        .unindent(),
23640    );
23641    cx.set_head_text(&diff_base);
23642    executor.run_until_parked();
23643
23644    cx.update_editor(|editor, window, cx| {
23645        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23646    });
23647    executor.run_until_parked();
23648    cx.assert_state_with_diff(
23649        r#"
23650        - lineA
23651        + ˇlineA1
23652          lineB
23653          lineD
23654        "#
23655        .unindent(),
23656    );
23657
23658    cx.update_editor(|editor, window, cx| {
23659        editor.move_down(&MoveDown, window, cx);
23660        editor.move_right(&MoveRight, window, cx);
23661        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23662    });
23663    executor.run_until_parked();
23664    cx.assert_state_with_diff(
23665        r#"
23666        - lineA
23667        + lineA1
23668          lˇineB
23669        - lineC
23670          lineD
23671        "#
23672        .unindent(),
23673    );
23674}
23675
23676#[gpui::test]
23677async fn test_edits_around_expanded_deletion_hunks(
23678    executor: BackgroundExecutor,
23679    cx: &mut TestAppContext,
23680) {
23681    init_test(cx, |_| {});
23682
23683    let mut cx = EditorTestContext::new(cx).await;
23684
23685    let diff_base = r#"
23686        use some::mod1;
23687        use some::mod2;
23688
23689        const A: u32 = 42;
23690        const B: u32 = 42;
23691        const C: u32 = 42;
23692
23693
23694        fn main() {
23695            println!("hello");
23696
23697            println!("world");
23698        }
23699    "#
23700    .unindent();
23701    executor.run_until_parked();
23702    cx.set_state(
23703        &r#"
23704        use some::mod1;
23705        use some::mod2;
23706
23707        ˇconst B: u32 = 42;
23708        const C: u32 = 42;
23709
23710
23711        fn main() {
23712            println!("hello");
23713
23714            println!("world");
23715        }
23716        "#
23717        .unindent(),
23718    );
23719
23720    cx.set_head_text(&diff_base);
23721    executor.run_until_parked();
23722
23723    cx.update_editor(|editor, window, cx| {
23724        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23725    });
23726    executor.run_until_parked();
23727
23728    cx.assert_state_with_diff(
23729        r#"
23730        use some::mod1;
23731        use some::mod2;
23732
23733      - const A: u32 = 42;
23734        ˇconst B: u32 = 42;
23735        const C: u32 = 42;
23736
23737
23738        fn main() {
23739            println!("hello");
23740
23741            println!("world");
23742        }
23743      "#
23744        .unindent(),
23745    );
23746
23747    cx.update_editor(|editor, window, cx| {
23748        editor.delete_line(&DeleteLine, window, cx);
23749    });
23750    executor.run_until_parked();
23751    cx.assert_state_with_diff(
23752        r#"
23753        use some::mod1;
23754        use some::mod2;
23755
23756      - const A: u32 = 42;
23757      - const B: u32 = 42;
23758        ˇconst C: u32 = 42;
23759
23760
23761        fn main() {
23762            println!("hello");
23763
23764            println!("world");
23765        }
23766      "#
23767        .unindent(),
23768    );
23769
23770    cx.update_editor(|editor, window, cx| {
23771        editor.delete_line(&DeleteLine, window, cx);
23772    });
23773    executor.run_until_parked();
23774    cx.assert_state_with_diff(
23775        r#"
23776        use some::mod1;
23777        use some::mod2;
23778
23779      - const A: u32 = 42;
23780      - const B: u32 = 42;
23781      - const C: u32 = 42;
23782        ˇ
23783
23784        fn main() {
23785            println!("hello");
23786
23787            println!("world");
23788        }
23789      "#
23790        .unindent(),
23791    );
23792
23793    cx.update_editor(|editor, window, cx| {
23794        editor.handle_input("replacement", window, cx);
23795    });
23796    executor.run_until_parked();
23797    cx.assert_state_with_diff(
23798        r#"
23799        use some::mod1;
23800        use some::mod2;
23801
23802      - const A: u32 = 42;
23803      - const B: u32 = 42;
23804      - const C: u32 = 42;
23805      -
23806      + replacementˇ
23807
23808        fn main() {
23809            println!("hello");
23810
23811            println!("world");
23812        }
23813      "#
23814        .unindent(),
23815    );
23816}
23817
23818#[gpui::test]
23819async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
23820    init_test(cx, |_| {});
23821
23822    let mut cx = EditorTestContext::new(cx).await;
23823
23824    let base_text = r#"
23825        one
23826        two
23827        three
23828        four
23829        five
23830    "#
23831    .unindent();
23832    executor.run_until_parked();
23833    cx.set_state(
23834        &r#"
23835        one
23836        two
23837        fˇour
23838        five
23839        "#
23840        .unindent(),
23841    );
23842
23843    cx.set_head_text(&base_text);
23844    executor.run_until_parked();
23845
23846    cx.update_editor(|editor, window, cx| {
23847        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23848    });
23849    executor.run_until_parked();
23850
23851    cx.assert_state_with_diff(
23852        r#"
23853          one
23854          two
23855        - three
23856          fˇour
23857          five
23858        "#
23859        .unindent(),
23860    );
23861
23862    cx.update_editor(|editor, window, cx| {
23863        editor.backspace(&Backspace, window, cx);
23864        editor.backspace(&Backspace, window, cx);
23865    });
23866    executor.run_until_parked();
23867    cx.assert_state_with_diff(
23868        r#"
23869          one
23870          two
23871        - threeˇ
23872        - four
23873        + our
23874          five
23875        "#
23876        .unindent(),
23877    );
23878}
23879
23880#[gpui::test]
23881async fn test_edit_after_expanded_modification_hunk(
23882    executor: BackgroundExecutor,
23883    cx: &mut TestAppContext,
23884) {
23885    init_test(cx, |_| {});
23886
23887    let mut cx = EditorTestContext::new(cx).await;
23888
23889    let diff_base = r#"
23890        use some::mod1;
23891        use some::mod2;
23892
23893        const A: u32 = 42;
23894        const B: u32 = 42;
23895        const C: u32 = 42;
23896        const D: u32 = 42;
23897
23898
23899        fn main() {
23900            println!("hello");
23901
23902            println!("world");
23903        }"#
23904    .unindent();
23905
23906    cx.set_state(
23907        &r#"
23908        use some::mod1;
23909        use some::mod2;
23910
23911        const A: u32 = 42;
23912        const B: u32 = 42;
23913        const C: u32 = 43ˇ
23914        const D: u32 = 42;
23915
23916
23917        fn main() {
23918            println!("hello");
23919
23920            println!("world");
23921        }"#
23922        .unindent(),
23923    );
23924
23925    cx.set_head_text(&diff_base);
23926    executor.run_until_parked();
23927    cx.update_editor(|editor, window, cx| {
23928        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23929    });
23930    executor.run_until_parked();
23931
23932    cx.assert_state_with_diff(
23933        r#"
23934        use some::mod1;
23935        use some::mod2;
23936
23937        const A: u32 = 42;
23938        const B: u32 = 42;
23939      - const C: u32 = 42;
23940      + const C: u32 = 43ˇ
23941        const D: u32 = 42;
23942
23943
23944        fn main() {
23945            println!("hello");
23946
23947            println!("world");
23948        }"#
23949        .unindent(),
23950    );
23951
23952    cx.update_editor(|editor, window, cx| {
23953        editor.handle_input("\nnew_line\n", window, cx);
23954    });
23955    executor.run_until_parked();
23956
23957    cx.assert_state_with_diff(
23958        r#"
23959        use some::mod1;
23960        use some::mod2;
23961
23962        const A: u32 = 42;
23963        const B: u32 = 42;
23964      - const C: u32 = 42;
23965      + const C: u32 = 43
23966      + new_line
23967      + ˇ
23968        const D: u32 = 42;
23969
23970
23971        fn main() {
23972            println!("hello");
23973
23974            println!("world");
23975        }"#
23976        .unindent(),
23977    );
23978}
23979
23980#[gpui::test]
23981async fn test_stage_and_unstage_added_file_hunk(
23982    executor: BackgroundExecutor,
23983    cx: &mut TestAppContext,
23984) {
23985    init_test(cx, |_| {});
23986
23987    let mut cx = EditorTestContext::new(cx).await;
23988    cx.update_editor(|editor, _, cx| {
23989        editor.set_expand_all_diff_hunks(cx);
23990    });
23991
23992    let working_copy = r#"
23993            ˇfn main() {
23994                println!("hello, world!");
23995            }
23996        "#
23997    .unindent();
23998
23999    cx.set_state(&working_copy);
24000    executor.run_until_parked();
24001
24002    cx.assert_state_with_diff(
24003        r#"
24004            + ˇfn main() {
24005            +     println!("hello, world!");
24006            + }
24007        "#
24008        .unindent(),
24009    );
24010    cx.assert_index_text(None);
24011
24012    cx.update_editor(|editor, window, cx| {
24013        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
24014    });
24015    executor.run_until_parked();
24016    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
24017    cx.assert_state_with_diff(
24018        r#"
24019            + ˇfn main() {
24020            +     println!("hello, world!");
24021            + }
24022        "#
24023        .unindent(),
24024    );
24025
24026    cx.update_editor(|editor, window, cx| {
24027        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
24028    });
24029    executor.run_until_parked();
24030    cx.assert_index_text(None);
24031}
24032
24033async fn setup_indent_guides_editor(
24034    text: &str,
24035    cx: &mut TestAppContext,
24036) -> (BufferId, EditorTestContext) {
24037    init_test(cx, |_| {});
24038
24039    let mut cx = EditorTestContext::new(cx).await;
24040
24041    let buffer_id = cx.update_editor(|editor, window, cx| {
24042        editor.set_text(text, window, cx);
24043        editor
24044            .buffer()
24045            .read(cx)
24046            .as_singleton()
24047            .unwrap()
24048            .read(cx)
24049            .remote_id()
24050    });
24051
24052    (buffer_id, cx)
24053}
24054
24055fn assert_indent_guides(
24056    range: Range<u32>,
24057    expected: Vec<IndentGuide>,
24058    active_indices: Option<Vec<usize>>,
24059    cx: &mut EditorTestContext,
24060) {
24061    let indent_guides = cx.update_editor(|editor, window, cx| {
24062        let snapshot = editor.snapshot(window, cx).display_snapshot;
24063        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
24064            editor,
24065            MultiBufferRow(range.start)..MultiBufferRow(range.end),
24066            true,
24067            &snapshot,
24068            cx,
24069        );
24070
24071        indent_guides.sort_by(|a, b| {
24072            a.depth.cmp(&b.depth).then(
24073                a.start_row
24074                    .cmp(&b.start_row)
24075                    .then(a.end_row.cmp(&b.end_row)),
24076            )
24077        });
24078        indent_guides
24079    });
24080
24081    if let Some(expected) = active_indices {
24082        let active_indices = cx.update_editor(|editor, window, cx| {
24083            let snapshot = editor.snapshot(window, cx).display_snapshot;
24084            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
24085        });
24086
24087        assert_eq!(
24088            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
24089            expected,
24090            "Active indent guide indices do not match"
24091        );
24092    }
24093
24094    assert_eq!(indent_guides, expected, "Indent guides do not match");
24095}
24096
24097fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
24098    IndentGuide {
24099        buffer_id,
24100        start_row: MultiBufferRow(start_row),
24101        end_row: MultiBufferRow(end_row),
24102        depth,
24103        tab_size: 4,
24104        settings: IndentGuideSettings {
24105            enabled: true,
24106            line_width: 1,
24107            active_line_width: 1,
24108            coloring: IndentGuideColoring::default(),
24109            background_coloring: IndentGuideBackgroundColoring::default(),
24110        },
24111    }
24112}
24113
24114#[gpui::test]
24115async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
24116    let (buffer_id, mut cx) = setup_indent_guides_editor(
24117        &"
24118        fn main() {
24119            let a = 1;
24120        }"
24121        .unindent(),
24122        cx,
24123    )
24124    .await;
24125
24126    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
24127}
24128
24129#[gpui::test]
24130async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
24131    let (buffer_id, mut cx) = setup_indent_guides_editor(
24132        &"
24133        fn main() {
24134            let a = 1;
24135            let b = 2;
24136        }"
24137        .unindent(),
24138        cx,
24139    )
24140    .await;
24141
24142    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
24143}
24144
24145#[gpui::test]
24146async fn test_indent_guide_nested(cx: &mut TestAppContext) {
24147    let (buffer_id, mut cx) = setup_indent_guides_editor(
24148        &"
24149        fn main() {
24150            let a = 1;
24151            if a == 3 {
24152                let b = 2;
24153            } else {
24154                let c = 3;
24155            }
24156        }"
24157        .unindent(),
24158        cx,
24159    )
24160    .await;
24161
24162    assert_indent_guides(
24163        0..8,
24164        vec![
24165            indent_guide(buffer_id, 1, 6, 0),
24166            indent_guide(buffer_id, 3, 3, 1),
24167            indent_guide(buffer_id, 5, 5, 1),
24168        ],
24169        None,
24170        &mut cx,
24171    );
24172}
24173
24174#[gpui::test]
24175async fn test_indent_guide_tab(cx: &mut TestAppContext) {
24176    let (buffer_id, mut cx) = setup_indent_guides_editor(
24177        &"
24178        fn main() {
24179            let a = 1;
24180                let b = 2;
24181            let c = 3;
24182        }"
24183        .unindent(),
24184        cx,
24185    )
24186    .await;
24187
24188    assert_indent_guides(
24189        0..5,
24190        vec![
24191            indent_guide(buffer_id, 1, 3, 0),
24192            indent_guide(buffer_id, 2, 2, 1),
24193        ],
24194        None,
24195        &mut cx,
24196    );
24197}
24198
24199#[gpui::test]
24200async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
24201    let (buffer_id, mut cx) = setup_indent_guides_editor(
24202        &"
24203        fn main() {
24204            let a = 1;
24205
24206            let c = 3;
24207        }"
24208        .unindent(),
24209        cx,
24210    )
24211    .await;
24212
24213    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
24214}
24215
24216#[gpui::test]
24217async fn test_indent_guide_complex(cx: &mut TestAppContext) {
24218    let (buffer_id, mut cx) = setup_indent_guides_editor(
24219        &"
24220        fn main() {
24221            let a = 1;
24222
24223            let c = 3;
24224
24225            if a == 3 {
24226                let b = 2;
24227            } else {
24228                let c = 3;
24229            }
24230        }"
24231        .unindent(),
24232        cx,
24233    )
24234    .await;
24235
24236    assert_indent_guides(
24237        0..11,
24238        vec![
24239            indent_guide(buffer_id, 1, 9, 0),
24240            indent_guide(buffer_id, 6, 6, 1),
24241            indent_guide(buffer_id, 8, 8, 1),
24242        ],
24243        None,
24244        &mut cx,
24245    );
24246}
24247
24248#[gpui::test]
24249async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
24250    let (buffer_id, mut cx) = setup_indent_guides_editor(
24251        &"
24252        fn main() {
24253            let a = 1;
24254
24255            let c = 3;
24256
24257            if a == 3 {
24258                let b = 2;
24259            } else {
24260                let c = 3;
24261            }
24262        }"
24263        .unindent(),
24264        cx,
24265    )
24266    .await;
24267
24268    assert_indent_guides(
24269        1..11,
24270        vec![
24271            indent_guide(buffer_id, 1, 9, 0),
24272            indent_guide(buffer_id, 6, 6, 1),
24273            indent_guide(buffer_id, 8, 8, 1),
24274        ],
24275        None,
24276        &mut cx,
24277    );
24278}
24279
24280#[gpui::test]
24281async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
24282    let (buffer_id, mut cx) = setup_indent_guides_editor(
24283        &"
24284        fn main() {
24285            let a = 1;
24286
24287            let c = 3;
24288
24289            if a == 3 {
24290                let b = 2;
24291            } else {
24292                let c = 3;
24293            }
24294        }"
24295        .unindent(),
24296        cx,
24297    )
24298    .await;
24299
24300    assert_indent_guides(
24301        1..10,
24302        vec![
24303            indent_guide(buffer_id, 1, 9, 0),
24304            indent_guide(buffer_id, 6, 6, 1),
24305            indent_guide(buffer_id, 8, 8, 1),
24306        ],
24307        None,
24308        &mut cx,
24309    );
24310}
24311
24312#[gpui::test]
24313async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
24314    let (buffer_id, mut cx) = setup_indent_guides_editor(
24315        &"
24316        fn main() {
24317            if a {
24318                b(
24319                    c,
24320                    d,
24321                )
24322            } else {
24323                e(
24324                    f
24325                )
24326            }
24327        }"
24328        .unindent(),
24329        cx,
24330    )
24331    .await;
24332
24333    assert_indent_guides(
24334        0..11,
24335        vec![
24336            indent_guide(buffer_id, 1, 10, 0),
24337            indent_guide(buffer_id, 2, 5, 1),
24338            indent_guide(buffer_id, 7, 9, 1),
24339            indent_guide(buffer_id, 3, 4, 2),
24340            indent_guide(buffer_id, 8, 8, 2),
24341        ],
24342        None,
24343        &mut cx,
24344    );
24345
24346    cx.update_editor(|editor, window, cx| {
24347        editor.fold_at(MultiBufferRow(2), window, cx);
24348        assert_eq!(
24349            editor.display_text(cx),
24350            "
24351            fn main() {
24352                if a {
24353                    b(⋯)
24354                } else {
24355                    e(
24356                        f
24357                    )
24358                }
24359            }"
24360            .unindent()
24361        );
24362    });
24363
24364    assert_indent_guides(
24365        0..11,
24366        vec![
24367            indent_guide(buffer_id, 1, 10, 0),
24368            indent_guide(buffer_id, 2, 5, 1),
24369            indent_guide(buffer_id, 7, 9, 1),
24370            indent_guide(buffer_id, 8, 8, 2),
24371        ],
24372        None,
24373        &mut cx,
24374    );
24375}
24376
24377#[gpui::test]
24378async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
24379    let (buffer_id, mut cx) = setup_indent_guides_editor(
24380        &"
24381        block1
24382            block2
24383                block3
24384                    block4
24385            block2
24386        block1
24387        block1"
24388            .unindent(),
24389        cx,
24390    )
24391    .await;
24392
24393    assert_indent_guides(
24394        1..10,
24395        vec![
24396            indent_guide(buffer_id, 1, 4, 0),
24397            indent_guide(buffer_id, 2, 3, 1),
24398            indent_guide(buffer_id, 3, 3, 2),
24399        ],
24400        None,
24401        &mut cx,
24402    );
24403}
24404
24405#[gpui::test]
24406async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
24407    let (buffer_id, mut cx) = setup_indent_guides_editor(
24408        &"
24409        block1
24410            block2
24411                block3
24412
24413        block1
24414        block1"
24415            .unindent(),
24416        cx,
24417    )
24418    .await;
24419
24420    assert_indent_guides(
24421        0..6,
24422        vec![
24423            indent_guide(buffer_id, 1, 2, 0),
24424            indent_guide(buffer_id, 2, 2, 1),
24425        ],
24426        None,
24427        &mut cx,
24428    );
24429}
24430
24431#[gpui::test]
24432async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
24433    let (buffer_id, mut cx) = setup_indent_guides_editor(
24434        &"
24435        function component() {
24436        \treturn (
24437        \t\t\t
24438        \t\t<div>
24439        \t\t\t<abc></abc>
24440        \t\t</div>
24441        \t)
24442        }"
24443        .unindent(),
24444        cx,
24445    )
24446    .await;
24447
24448    assert_indent_guides(
24449        0..8,
24450        vec![
24451            indent_guide(buffer_id, 1, 6, 0),
24452            indent_guide(buffer_id, 2, 5, 1),
24453            indent_guide(buffer_id, 4, 4, 2),
24454        ],
24455        None,
24456        &mut cx,
24457    );
24458}
24459
24460#[gpui::test]
24461async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
24462    let (buffer_id, mut cx) = setup_indent_guides_editor(
24463        &"
24464        function component() {
24465        \treturn (
24466        \t
24467        \t\t<div>
24468        \t\t\t<abc></abc>
24469        \t\t</div>
24470        \t)
24471        }"
24472        .unindent(),
24473        cx,
24474    )
24475    .await;
24476
24477    assert_indent_guides(
24478        0..8,
24479        vec![
24480            indent_guide(buffer_id, 1, 6, 0),
24481            indent_guide(buffer_id, 2, 5, 1),
24482            indent_guide(buffer_id, 4, 4, 2),
24483        ],
24484        None,
24485        &mut cx,
24486    );
24487}
24488
24489#[gpui::test]
24490async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
24491    let (buffer_id, mut cx) = setup_indent_guides_editor(
24492        &"
24493        block1
24494
24495
24496
24497            block2
24498        "
24499        .unindent(),
24500        cx,
24501    )
24502    .await;
24503
24504    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
24505}
24506
24507#[gpui::test]
24508async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
24509    let (buffer_id, mut cx) = setup_indent_guides_editor(
24510        &"
24511        def a:
24512        \tb = 3
24513        \tif True:
24514        \t\tc = 4
24515        \t\td = 5
24516        \tprint(b)
24517        "
24518        .unindent(),
24519        cx,
24520    )
24521    .await;
24522
24523    assert_indent_guides(
24524        0..6,
24525        vec![
24526            indent_guide(buffer_id, 1, 5, 0),
24527            indent_guide(buffer_id, 3, 4, 1),
24528        ],
24529        None,
24530        &mut cx,
24531    );
24532}
24533
24534#[gpui::test]
24535async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
24536    let (buffer_id, mut cx) = setup_indent_guides_editor(
24537        &"
24538    fn main() {
24539        let a = 1;
24540    }"
24541        .unindent(),
24542        cx,
24543    )
24544    .await;
24545
24546    cx.update_editor(|editor, window, cx| {
24547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24548            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
24549        });
24550    });
24551
24552    assert_indent_guides(
24553        0..3,
24554        vec![indent_guide(buffer_id, 1, 1, 0)],
24555        Some(vec![0]),
24556        &mut cx,
24557    );
24558}
24559
24560#[gpui::test]
24561async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
24562    let (buffer_id, mut cx) = setup_indent_guides_editor(
24563        &"
24564    fn main() {
24565        if 1 == 2 {
24566            let a = 1;
24567        }
24568    }"
24569        .unindent(),
24570        cx,
24571    )
24572    .await;
24573
24574    cx.update_editor(|editor, window, cx| {
24575        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24576            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
24577        });
24578    });
24579    cx.run_until_parked();
24580
24581    assert_indent_guides(
24582        0..4,
24583        vec![
24584            indent_guide(buffer_id, 1, 3, 0),
24585            indent_guide(buffer_id, 2, 2, 1),
24586        ],
24587        Some(vec![1]),
24588        &mut cx,
24589    );
24590
24591    cx.update_editor(|editor, window, cx| {
24592        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24593            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
24594        });
24595    });
24596    cx.run_until_parked();
24597
24598    assert_indent_guides(
24599        0..4,
24600        vec![
24601            indent_guide(buffer_id, 1, 3, 0),
24602            indent_guide(buffer_id, 2, 2, 1),
24603        ],
24604        Some(vec![1]),
24605        &mut cx,
24606    );
24607
24608    cx.update_editor(|editor, window, cx| {
24609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24610            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
24611        });
24612    });
24613    cx.run_until_parked();
24614
24615    assert_indent_guides(
24616        0..4,
24617        vec![
24618            indent_guide(buffer_id, 1, 3, 0),
24619            indent_guide(buffer_id, 2, 2, 1),
24620        ],
24621        Some(vec![0]),
24622        &mut cx,
24623    );
24624}
24625
24626#[gpui::test]
24627async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
24628    let (buffer_id, mut cx) = setup_indent_guides_editor(
24629        &"
24630    fn main() {
24631        let a = 1;
24632
24633        let b = 2;
24634    }"
24635        .unindent(),
24636        cx,
24637    )
24638    .await;
24639
24640    cx.update_editor(|editor, window, cx| {
24641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24642            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
24643        });
24644    });
24645
24646    assert_indent_guides(
24647        0..5,
24648        vec![indent_guide(buffer_id, 1, 3, 0)],
24649        Some(vec![0]),
24650        &mut cx,
24651    );
24652}
24653
24654#[gpui::test]
24655async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
24656    let (buffer_id, mut cx) = setup_indent_guides_editor(
24657        &"
24658    def m:
24659        a = 1
24660        pass"
24661            .unindent(),
24662        cx,
24663    )
24664    .await;
24665
24666    cx.update_editor(|editor, window, cx| {
24667        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24668            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
24669        });
24670    });
24671
24672    assert_indent_guides(
24673        0..3,
24674        vec![indent_guide(buffer_id, 1, 2, 0)],
24675        Some(vec![0]),
24676        &mut cx,
24677    );
24678}
24679
24680#[gpui::test]
24681async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
24682    init_test(cx, |_| {});
24683    let mut cx = EditorTestContext::new(cx).await;
24684    let text = indoc! {
24685        "
24686        impl A {
24687            fn b() {
24688                0;
24689                3;
24690                5;
24691                6;
24692                7;
24693            }
24694        }
24695        "
24696    };
24697    let base_text = indoc! {
24698        "
24699        impl A {
24700            fn b() {
24701                0;
24702                1;
24703                2;
24704                3;
24705                4;
24706            }
24707            fn c() {
24708                5;
24709                6;
24710                7;
24711            }
24712        }
24713        "
24714    };
24715
24716    cx.update_editor(|editor, window, cx| {
24717        editor.set_text(text, window, cx);
24718
24719        editor.buffer().update(cx, |multibuffer, cx| {
24720            let buffer = multibuffer.as_singleton().unwrap();
24721            let diff = cx.new(|cx| {
24722                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
24723            });
24724
24725            multibuffer.set_all_diff_hunks_expanded(cx);
24726            multibuffer.add_diff(diff, cx);
24727
24728            buffer.read(cx).remote_id()
24729        })
24730    });
24731    cx.run_until_parked();
24732
24733    cx.assert_state_with_diff(
24734        indoc! { "
24735          impl A {
24736              fn b() {
24737                  0;
24738        -         1;
24739        -         2;
24740                  3;
24741        -         4;
24742        -     }
24743        -     fn c() {
24744                  5;
24745                  6;
24746                  7;
24747              }
24748          }
24749          ˇ"
24750        }
24751        .to_string(),
24752    );
24753
24754    let mut actual_guides = cx.update_editor(|editor, window, cx| {
24755        editor
24756            .snapshot(window, cx)
24757            .buffer_snapshot()
24758            .indent_guides_in_range(Anchor::Min..Anchor::Max, false, cx)
24759            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
24760            .collect::<Vec<_>>()
24761    });
24762    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
24763    assert_eq!(
24764        actual_guides,
24765        vec![
24766            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
24767            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
24768            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
24769        ]
24770    );
24771}
24772
24773#[gpui::test]
24774async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
24775    init_test(cx, |_| {});
24776    let mut cx = EditorTestContext::new(cx).await;
24777
24778    let diff_base = r#"
24779        a
24780        b
24781        c
24782        "#
24783    .unindent();
24784
24785    cx.set_state(
24786        &r#"
24787        ˇA
24788        b
24789        C
24790        "#
24791        .unindent(),
24792    );
24793    cx.set_head_text(&diff_base);
24794    cx.update_editor(|editor, window, cx| {
24795        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24796    });
24797    executor.run_until_parked();
24798
24799    let both_hunks_expanded = r#"
24800        - a
24801        + ˇA
24802          b
24803        - c
24804        + C
24805        "#
24806    .unindent();
24807
24808    cx.assert_state_with_diff(both_hunks_expanded.clone());
24809
24810    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24811        let snapshot = editor.snapshot(window, cx);
24812        let hunks = editor
24813            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24814            .collect::<Vec<_>>();
24815        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
24816        hunks
24817            .into_iter()
24818            .map(|hunk| {
24819                multibuffer_snapshot
24820                    .anchor_in_excerpt(hunk.buffer_range.start)
24821                    .unwrap()
24822                    ..multibuffer_snapshot
24823                        .anchor_in_excerpt(hunk.buffer_range.end)
24824                        .unwrap()
24825            })
24826            .collect::<Vec<_>>()
24827    });
24828    assert_eq!(hunk_ranges.len(), 2);
24829
24830    cx.update_editor(|editor, _, cx| {
24831        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24832    });
24833    executor.run_until_parked();
24834
24835    let second_hunk_expanded = r#"
24836          ˇA
24837          b
24838        - c
24839        + C
24840        "#
24841    .unindent();
24842
24843    cx.assert_state_with_diff(second_hunk_expanded);
24844
24845    cx.update_editor(|editor, _, cx| {
24846        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24847    });
24848    executor.run_until_parked();
24849
24850    cx.assert_state_with_diff(both_hunks_expanded.clone());
24851
24852    cx.update_editor(|editor, _, cx| {
24853        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24854    });
24855    executor.run_until_parked();
24856
24857    let first_hunk_expanded = r#"
24858        - a
24859        + ˇA
24860          b
24861          C
24862        "#
24863    .unindent();
24864
24865    cx.assert_state_with_diff(first_hunk_expanded);
24866
24867    cx.update_editor(|editor, _, cx| {
24868        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24869    });
24870    executor.run_until_parked();
24871
24872    cx.assert_state_with_diff(both_hunks_expanded);
24873
24874    cx.set_state(
24875        &r#"
24876        ˇA
24877        b
24878        "#
24879        .unindent(),
24880    );
24881    cx.run_until_parked();
24882
24883    // TODO this cursor position seems bad
24884    cx.assert_state_with_diff(
24885        r#"
24886        - ˇa
24887        + A
24888          b
24889        "#
24890        .unindent(),
24891    );
24892
24893    cx.update_editor(|editor, window, cx| {
24894        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24895    });
24896
24897    cx.assert_state_with_diff(
24898        r#"
24899            - ˇa
24900            + A
24901              b
24902            - c
24903            "#
24904        .unindent(),
24905    );
24906
24907    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24908        let snapshot = editor.snapshot(window, cx);
24909        let hunks = editor
24910            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24911            .collect::<Vec<_>>();
24912        let multibuffer_snapshot = snapshot.buffer_snapshot();
24913        hunks
24914            .into_iter()
24915            .map(|hunk| {
24916                multibuffer_snapshot
24917                    .anchor_in_excerpt(hunk.buffer_range.start)
24918                    .unwrap()
24919                    ..multibuffer_snapshot
24920                        .anchor_in_excerpt(hunk.buffer_range.end)
24921                        .unwrap()
24922            })
24923            .collect::<Vec<_>>()
24924    });
24925    assert_eq!(hunk_ranges.len(), 2);
24926
24927    cx.update_editor(|editor, _, cx| {
24928        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24929    });
24930    executor.run_until_parked();
24931
24932    cx.assert_state_with_diff(
24933        r#"
24934        - ˇa
24935        + A
24936          b
24937        "#
24938        .unindent(),
24939    );
24940}
24941
24942#[gpui::test]
24943async fn test_toggle_deletion_hunk_at_start_of_file(
24944    executor: BackgroundExecutor,
24945    cx: &mut TestAppContext,
24946) {
24947    init_test(cx, |_| {});
24948    let mut cx = EditorTestContext::new(cx).await;
24949
24950    let diff_base = r#"
24951        a
24952        b
24953        c
24954        "#
24955    .unindent();
24956
24957    cx.set_state(
24958        &r#"
24959        ˇb
24960        c
24961        "#
24962        .unindent(),
24963    );
24964    cx.set_head_text(&diff_base);
24965    cx.update_editor(|editor, window, cx| {
24966        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24967    });
24968    executor.run_until_parked();
24969
24970    let hunk_expanded = r#"
24971        - a
24972          ˇb
24973          c
24974        "#
24975    .unindent();
24976
24977    cx.assert_state_with_diff(hunk_expanded.clone());
24978
24979    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24980        let snapshot = editor.snapshot(window, cx);
24981        let hunks = editor
24982            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24983            .collect::<Vec<_>>();
24984        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
24985        hunks
24986            .into_iter()
24987            .map(|hunk| {
24988                multibuffer_snapshot
24989                    .anchor_in_excerpt(hunk.buffer_range.start)
24990                    .unwrap()
24991                    ..multibuffer_snapshot
24992                        .anchor_in_excerpt(hunk.buffer_range.end)
24993                        .unwrap()
24994            })
24995            .collect::<Vec<_>>()
24996    });
24997    assert_eq!(hunk_ranges.len(), 1);
24998
24999    cx.update_editor(|editor, _, cx| {
25000        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
25001    });
25002    executor.run_until_parked();
25003
25004    let hunk_collapsed = r#"
25005          ˇb
25006          c
25007        "#
25008    .unindent();
25009
25010    cx.assert_state_with_diff(hunk_collapsed);
25011
25012    cx.update_editor(|editor, _, cx| {
25013        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
25014    });
25015    executor.run_until_parked();
25016
25017    cx.assert_state_with_diff(hunk_expanded);
25018}
25019
25020#[gpui::test]
25021async fn test_select_smaller_syntax_node_after_diff_hunk_collapse(
25022    executor: BackgroundExecutor,
25023    cx: &mut TestAppContext,
25024) {
25025    init_test(cx, |_| {});
25026
25027    let mut cx = EditorTestContext::new(cx).await;
25028    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
25029
25030    cx.set_state(
25031        &r#"
25032        fn main() {
25033            let x = ˇ1;
25034        }
25035        "#
25036        .unindent(),
25037    );
25038
25039    let diff_base = r#"
25040        fn removed_one() {
25041            println!("this function was deleted");
25042        }
25043
25044        fn removed_two() {
25045            println!("this function was also deleted");
25046        }
25047
25048        fn main() {
25049            let x = 1;
25050        }
25051        "#
25052    .unindent();
25053    cx.set_head_text(&diff_base);
25054    executor.run_until_parked();
25055
25056    cx.update_editor(|editor, window, cx| {
25057        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
25058    });
25059    executor.run_until_parked();
25060
25061    cx.update_editor(|editor, window, cx| {
25062        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
25063    });
25064
25065    cx.update_editor(|editor, window, cx| {
25066        editor.collapse_all_diff_hunks(&CollapseAllDiffHunks, window, cx);
25067    });
25068    executor.run_until_parked();
25069
25070    cx.update_editor(|editor, window, cx| {
25071        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
25072    });
25073}
25074
25075#[gpui::test]
25076async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
25077    executor: BackgroundExecutor,
25078    cx: &mut TestAppContext,
25079) {
25080    init_test(cx, |_| {});
25081    let mut cx = EditorTestContext::new(cx).await;
25082
25083    cx.set_state("ˇnew\nsecond\nthird\n");
25084    cx.set_head_text("old\nsecond\nthird\n");
25085    cx.update_editor(|editor, window, cx| {
25086        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
25087    });
25088    executor.run_until_parked();
25089    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
25090
25091    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
25092    cx.update_editor(|editor, window, cx| {
25093        let snapshot = editor.snapshot(window, cx);
25094        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
25095        let hunks = editor
25096            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
25097            .collect::<Vec<_>>();
25098        assert_eq!(hunks.len(), 1);
25099        let hunk_range = multibuffer_snapshot
25100            .anchor_in_excerpt(hunks[0].buffer_range.start)
25101            .unwrap()
25102            ..multibuffer_snapshot
25103                .anchor_in_excerpt(hunks[0].buffer_range.end)
25104                .unwrap();
25105        editor.toggle_single_diff_hunk(hunk_range, cx)
25106    });
25107    executor.run_until_parked();
25108    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
25109
25110    // Keep the editor scrolled to the top so the full hunk remains visible.
25111    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
25112}
25113
25114#[gpui::test]
25115async fn test_display_diff_hunks(cx: &mut TestAppContext) {
25116    init_test(cx, |_| {});
25117
25118    let fs = FakeFs::new(cx.executor());
25119    fs.insert_tree(
25120        path!("/test"),
25121        json!({
25122            ".git": {},
25123            "file-1": "ONE\n",
25124            "file-2": "TWO\n",
25125            "file-3": "THREE\n",
25126        }),
25127    )
25128    .await;
25129
25130    fs.set_head_for_repo(
25131        path!("/test/.git").as_ref(),
25132        &[
25133            ("file-1", "one\n".into()),
25134            ("file-2", "two\n".into()),
25135            ("file-3", "three\n".into()),
25136        ],
25137        "deadbeef",
25138    );
25139
25140    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
25141    let mut buffers = vec![];
25142    for i in 1..=3 {
25143        let buffer = project
25144            .update(cx, |project, cx| {
25145                let path = format!(path!("/test/file-{}"), i);
25146                project.open_local_buffer(path, cx)
25147            })
25148            .await
25149            .unwrap();
25150        buffers.push(buffer);
25151    }
25152
25153    let multibuffer = cx.new(|cx| {
25154        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
25155        multibuffer.set_all_diff_hunks_expanded(cx);
25156        for buffer in &buffers {
25157            let snapshot = buffer.read(cx).snapshot();
25158            multibuffer.set_excerpts_for_path(
25159                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
25160                buffer.clone(),
25161                vec![Point::zero()..snapshot.max_point()],
25162                2,
25163                cx,
25164            );
25165        }
25166        multibuffer
25167    });
25168
25169    let editor = cx.add_window(|window, cx| {
25170        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
25171    });
25172    cx.run_until_parked();
25173
25174    let snapshot = editor
25175        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25176        .unwrap();
25177    let hunks = snapshot
25178        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
25179        .map(|hunk| match hunk {
25180            DisplayDiffHunk::Unfolded {
25181                display_row_range, ..
25182            } => display_row_range,
25183            DisplayDiffHunk::Folded { .. } => unreachable!(),
25184        })
25185        .collect::<Vec<_>>();
25186    assert_eq!(
25187        hunks,
25188        [
25189            DisplayRow(2)..DisplayRow(4),
25190            DisplayRow(7)..DisplayRow(9),
25191            DisplayRow(12)..DisplayRow(14),
25192        ]
25193    );
25194}
25195
25196#[gpui::test]
25197async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
25198    init_test(cx, |_| {});
25199
25200    let mut cx = EditorTestContext::new(cx).await;
25201    cx.set_head_text(indoc! { "
25202        one
25203        two
25204        three
25205        four
25206        five
25207        "
25208    });
25209    cx.set_index_text(indoc! { "
25210        one
25211        two
25212        three
25213        four
25214        five
25215        "
25216    });
25217    cx.set_state(indoc! {"
25218        one
25219        TWO
25220        ˇTHREE
25221        FOUR
25222        five
25223    "});
25224    cx.run_until_parked();
25225    cx.update_editor(|editor, window, cx| {
25226        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
25227    });
25228    cx.run_until_parked();
25229    cx.assert_index_text(Some(indoc! {"
25230        one
25231        TWO
25232        THREE
25233        FOUR
25234        five
25235    "}));
25236    cx.set_state(indoc! { "
25237        one
25238        TWO
25239        ˇTHREE-HUNDRED
25240        FOUR
25241        five
25242    "});
25243    cx.run_until_parked();
25244    cx.update_editor(|editor, window, cx| {
25245        let snapshot = editor.snapshot(window, cx);
25246        let hunks = editor
25247            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
25248            .collect::<Vec<_>>();
25249        assert_eq!(hunks.len(), 1);
25250        assert_eq!(
25251            hunks[0].status(),
25252            DiffHunkStatus {
25253                kind: DiffHunkStatusKind::Modified,
25254                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
25255            }
25256        );
25257
25258        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
25259    });
25260    cx.run_until_parked();
25261    cx.assert_index_text(Some(indoc! {"
25262        one
25263        TWO
25264        THREE-HUNDRED
25265        FOUR
25266        five
25267    "}));
25268}
25269
25270#[gpui::test]
25271fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
25272    init_test(cx, |_| {});
25273
25274    let editor = cx.add_window(|window, cx| {
25275        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
25276        build_editor(buffer, window, cx)
25277    });
25278
25279    let render_args = Arc::new(Mutex::new(None));
25280    let snapshot = editor
25281        .update(cx, |editor, window, cx| {
25282            let snapshot = editor.buffer().read(cx).snapshot(cx);
25283            let range =
25284                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
25285
25286            struct RenderArgs {
25287                row: MultiBufferRow,
25288                folded: bool,
25289                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
25290            }
25291
25292            let crease = Crease::inline(
25293                range,
25294                FoldPlaceholder::test(),
25295                {
25296                    let toggle_callback = render_args.clone();
25297                    move |row, folded, callback, _window, _cx| {
25298                        *toggle_callback.lock() = Some(RenderArgs {
25299                            row,
25300                            folded,
25301                            callback,
25302                        });
25303                        div()
25304                    }
25305                },
25306                |_row, _folded, _window, _cx| div(),
25307            );
25308
25309            editor.insert_creases(Some(crease), cx);
25310            let snapshot = editor.snapshot(window, cx);
25311            let _div =
25312                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
25313            snapshot
25314        })
25315        .unwrap();
25316
25317    let render_args = render_args.lock().take().unwrap();
25318    assert_eq!(render_args.row, MultiBufferRow(1));
25319    assert!(!render_args.folded);
25320    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
25321
25322    cx.update_window(*editor, |_, window, cx| {
25323        (render_args.callback)(true, window, cx)
25324    })
25325    .unwrap();
25326    let snapshot = editor
25327        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25328        .unwrap();
25329    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
25330
25331    cx.update_window(*editor, |_, window, cx| {
25332        (render_args.callback)(false, window, cx)
25333    })
25334    .unwrap();
25335    let snapshot = editor
25336        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25337        .unwrap();
25338    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
25339}
25340
25341#[gpui::test]
25342async fn test_input_text(cx: &mut TestAppContext) {
25343    init_test(cx, |_| {});
25344    let mut cx = EditorTestContext::new(cx).await;
25345
25346    cx.set_state(
25347        &r#"ˇone
25348        two
25349
25350        three
25351        fourˇ
25352        five
25353
25354        siˇx"#
25355            .unindent(),
25356    );
25357
25358    cx.dispatch_action(HandleInput(String::new()));
25359    cx.assert_editor_state(
25360        &r#"ˇone
25361        two
25362
25363        three
25364        fourˇ
25365        five
25366
25367        siˇx"#
25368            .unindent(),
25369    );
25370
25371    cx.dispatch_action(HandleInput("AAAA".to_string()));
25372    cx.assert_editor_state(
25373        &r#"AAAAˇone
25374        two
25375
25376        three
25377        fourAAAAˇ
25378        five
25379
25380        siAAAAˇx"#
25381            .unindent(),
25382    );
25383}
25384
25385#[gpui::test]
25386async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
25387    init_test(cx, |_| {});
25388
25389    let mut cx = EditorTestContext::new(cx).await;
25390    cx.set_state(
25391        r#"let foo = 1;
25392let foo = 2;
25393let foo = 3;
25394let fooˇ = 4;
25395let foo = 5;
25396let foo = 6;
25397let foo = 7;
25398let foo = 8;
25399let foo = 9;
25400let foo = 10;
25401let foo = 11;
25402let foo = 12;
25403let foo = 13;
25404let foo = 14;
25405let foo = 15;"#,
25406    );
25407
25408    cx.update_editor(|e, window, cx| {
25409        assert_eq!(
25410            e.next_scroll_position,
25411            NextScrollCursorCenterTopBottom::Center,
25412            "Default next scroll direction is center",
25413        );
25414
25415        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25416        assert_eq!(
25417            e.next_scroll_position,
25418            NextScrollCursorCenterTopBottom::Top,
25419            "After center, next scroll direction should be top",
25420        );
25421
25422        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25423        assert_eq!(
25424            e.next_scroll_position,
25425            NextScrollCursorCenterTopBottom::Bottom,
25426            "After top, next scroll direction should be bottom",
25427        );
25428
25429        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25430        assert_eq!(
25431            e.next_scroll_position,
25432            NextScrollCursorCenterTopBottom::Center,
25433            "After bottom, scrolling should start over",
25434        );
25435
25436        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25437        assert_eq!(
25438            e.next_scroll_position,
25439            NextScrollCursorCenterTopBottom::Top,
25440            "Scrolling continues if retriggered fast enough"
25441        );
25442    });
25443
25444    cx.executor()
25445        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
25446    cx.executor().run_until_parked();
25447    cx.update_editor(|e, _, _| {
25448        assert_eq!(
25449            e.next_scroll_position,
25450            NextScrollCursorCenterTopBottom::Center,
25451            "If scrolling is not triggered fast enough, it should reset"
25452        );
25453    });
25454}
25455
25456#[gpui::test]
25457async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
25458    init_test(cx, |_| {});
25459    let mut cx = EditorLspTestContext::new_rust(
25460        lsp::ServerCapabilities {
25461            definition_provider: Some(lsp::OneOf::Left(true)),
25462            references_provider: Some(lsp::OneOf::Left(true)),
25463            ..lsp::ServerCapabilities::default()
25464        },
25465        cx,
25466    )
25467    .await;
25468
25469    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
25470        let go_to_definition = cx
25471            .lsp
25472            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
25473                move |params, _| async move {
25474                    if empty_go_to_definition {
25475                        Ok(None)
25476                    } else {
25477                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
25478                            uri: params.text_document_position_params.text_document.uri,
25479                            range: lsp::Range::new(
25480                                lsp::Position::new(4, 3),
25481                                lsp::Position::new(4, 6),
25482                            ),
25483                        })))
25484                    }
25485                },
25486            );
25487        let references = cx
25488            .lsp
25489            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25490                Ok(Some(vec![lsp::Location {
25491                    uri: params.text_document_position.text_document.uri,
25492                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
25493                }]))
25494            });
25495        (go_to_definition, references)
25496    };
25497
25498    cx.set_state(
25499        &r#"fn one() {
25500            let mut a = ˇtwo();
25501        }
25502
25503        fn two() {}"#
25504            .unindent(),
25505    );
25506    set_up_lsp_handlers(false, &mut cx);
25507    let navigated = cx
25508        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25509        .await
25510        .expect("Failed to navigate to definition");
25511    assert_eq!(
25512        navigated,
25513        Navigated::Yes,
25514        "Should have navigated to definition from the GetDefinition response"
25515    );
25516    cx.assert_editor_state(
25517        &r#"fn one() {
25518            let mut a = two();
25519        }
25520
25521        fn «twoˇ»() {}"#
25522            .unindent(),
25523    );
25524
25525    let editors = cx.update_workspace(|workspace, _, cx| {
25526        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25527    });
25528    cx.update_editor(|_, _, test_editor_cx| {
25529        assert_eq!(
25530            editors.len(),
25531            1,
25532            "Initially, only one, test, editor should be open in the workspace"
25533        );
25534        assert_eq!(
25535            test_editor_cx.entity(),
25536            editors.last().expect("Asserted len is 1").clone()
25537        );
25538    });
25539
25540    set_up_lsp_handlers(true, &mut cx);
25541    let navigated = cx
25542        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25543        .await
25544        .expect("Failed to navigate to lookup references");
25545    assert_eq!(
25546        navigated,
25547        Navigated::Yes,
25548        "Should have navigated to references as a fallback after empty GoToDefinition response"
25549    );
25550    // We should not change the selections in the existing file,
25551    // if opening another milti buffer with the references
25552    cx.assert_editor_state(
25553        &r#"fn one() {
25554            let mut a = two();
25555        }
25556
25557        fn «twoˇ»() {}"#
25558            .unindent(),
25559    );
25560    let editors = cx.update_workspace(|workspace, _, cx| {
25561        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25562    });
25563    cx.update_editor(|_, _, test_editor_cx| {
25564        assert_eq!(
25565            editors.len(),
25566            2,
25567            "After falling back to references search, we open a new editor with the results"
25568        );
25569        let references_fallback_text = editors
25570            .into_iter()
25571            .find(|new_editor| *new_editor != test_editor_cx.entity())
25572            .expect("Should have one non-test editor now")
25573            .read(test_editor_cx)
25574            .text(test_editor_cx);
25575        assert_eq!(
25576            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
25577            "Should use the range from the references response and not the GoToDefinition one"
25578        );
25579    });
25580}
25581
25582#[gpui::test]
25583async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
25584    init_test(cx, |_| {});
25585    cx.update(|cx| {
25586        let mut editor_settings = EditorSettings::get_global(cx).clone();
25587        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
25588        EditorSettings::override_global(editor_settings, cx);
25589    });
25590    let mut cx = EditorLspTestContext::new_rust(
25591        lsp::ServerCapabilities {
25592            definition_provider: Some(lsp::OneOf::Left(true)),
25593            references_provider: Some(lsp::OneOf::Left(true)),
25594            ..lsp::ServerCapabilities::default()
25595        },
25596        cx,
25597    )
25598    .await;
25599    let original_state = r#"fn one() {
25600        let mut a = ˇtwo();
25601    }
25602
25603    fn two() {}"#
25604        .unindent();
25605    cx.set_state(&original_state);
25606
25607    let mut go_to_definition = cx
25608        .lsp
25609        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
25610            move |_, _| async move { Ok(None) },
25611        );
25612    let _references = cx
25613        .lsp
25614        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
25615            panic!("Should not call for references with no go to definition fallback")
25616        });
25617
25618    let navigated = cx
25619        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25620        .await
25621        .expect("Failed to navigate to lookup references");
25622    go_to_definition
25623        .next()
25624        .await
25625        .expect("Should have called the go_to_definition handler");
25626
25627    assert_eq!(
25628        navigated,
25629        Navigated::No,
25630        "Should have navigated to references as a fallback after empty GoToDefinition response"
25631    );
25632    cx.assert_editor_state(&original_state);
25633    let editors = cx.update_workspace(|workspace, _, cx| {
25634        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25635    });
25636    cx.update_editor(|_, _, _| {
25637        assert_eq!(
25638            editors.len(),
25639            1,
25640            "After unsuccessful fallback, no other editor should have been opened"
25641        );
25642    });
25643}
25644
25645#[gpui::test]
25646async fn test_goto_definition_close_ranges_open_singleton(cx: &mut TestAppContext) {
25647    init_test(cx, |_| {});
25648    let mut cx = EditorLspTestContext::new_rust(
25649        lsp::ServerCapabilities {
25650            definition_provider: Some(lsp::OneOf::Left(true)),
25651            ..lsp::ServerCapabilities::default()
25652        },
25653        cx,
25654    )
25655    .await;
25656
25657    // File content: 10 lines with functions defined on lines 3, 5, and 7 (0-indexed).
25658    // With the default excerpt_context_lines of 2, ranges that are within
25659    // 2 * 2 = 4 rows of each other should be grouped into one excerpt.
25660    cx.set_state(
25661        &r#"fn caller() {
25662            let _ = ˇtarget();
25663        }
25664        fn target_a() {}
25665
25666        fn target_b() {}
25667
25668        fn target_c() {}
25669        "#
25670        .unindent(),
25671    );
25672
25673    // Return two definitions that are close together (lines 3 and 5, gap of 2 rows)
25674    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25675        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25676            lsp::Location {
25677                uri: url.clone(),
25678                range: lsp::Range::new(lsp::Position::new(3, 3), lsp::Position::new(3, 11)),
25679            },
25680            lsp::Location {
25681                uri: url,
25682                range: lsp::Range::new(lsp::Position::new(5, 3), lsp::Position::new(5, 11)),
25683            },
25684        ])))
25685    });
25686
25687    let navigated = cx
25688        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25689        .await
25690        .expect("Failed to navigate to definitions");
25691    assert_eq!(navigated, Navigated::Yes);
25692
25693    let editors = cx.update_workspace(|workspace, _, cx| {
25694        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25695    });
25696    cx.update_editor(|_, _, _| {
25697        assert_eq!(
25698            editors.len(),
25699            1,
25700            "Close ranges should navigate in-place without opening a new editor"
25701        );
25702    });
25703
25704    // Both target ranges should be selected
25705    cx.assert_editor_state(
25706        &r#"fn caller() {
25707            let _ = target();
25708        }
25709        fn «target_aˇ»() {}
25710
25711        fn «target_bˇ»() {}
25712
25713        fn target_c() {}
25714        "#
25715        .unindent(),
25716    );
25717}
25718
25719#[gpui::test]
25720async fn test_goto_definition_far_ranges_open_multibuffer(cx: &mut TestAppContext) {
25721    init_test(cx, |_| {});
25722    let mut cx = EditorLspTestContext::new_rust(
25723        lsp::ServerCapabilities {
25724            definition_provider: Some(lsp::OneOf::Left(true)),
25725            ..lsp::ServerCapabilities::default()
25726        },
25727        cx,
25728    )
25729    .await;
25730
25731    // Create a file with definitions far apart (more than 2 * excerpt_context_lines rows).
25732    cx.set_state(
25733        &r#"fn caller() {
25734            let _ = ˇtarget();
25735        }
25736        fn target_a() {}
25737
25738
25739
25740
25741
25742
25743
25744
25745
25746
25747
25748
25749
25750
25751
25752        fn target_b() {}
25753        "#
25754        .unindent(),
25755    );
25756
25757    // Return two definitions that are far apart (lines 3 and 19, gap of 16 rows)
25758    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25759        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25760            lsp::Location {
25761                uri: url.clone(),
25762                range: lsp::Range::new(lsp::Position::new(3, 3), lsp::Position::new(3, 11)),
25763            },
25764            lsp::Location {
25765                uri: url,
25766                range: lsp::Range::new(lsp::Position::new(19, 3), lsp::Position::new(19, 11)),
25767            },
25768        ])))
25769    });
25770
25771    let navigated = cx
25772        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25773        .await
25774        .expect("Failed to navigate to definitions");
25775    assert_eq!(navigated, Navigated::Yes);
25776
25777    let editors = cx.update_workspace(|workspace, _, cx| {
25778        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25779    });
25780    cx.update_editor(|_, _, test_editor_cx| {
25781        assert_eq!(
25782            editors.len(),
25783            2,
25784            "Far apart ranges should open a new multibuffer editor"
25785        );
25786        let multibuffer_editor = editors
25787            .into_iter()
25788            .find(|editor| *editor != test_editor_cx.entity())
25789            .expect("Should have a multibuffer editor");
25790        let multibuffer_text = multibuffer_editor.read(test_editor_cx).text(test_editor_cx);
25791        assert!(
25792            multibuffer_text.contains("target_a"),
25793            "Multibuffer should contain the first definition"
25794        );
25795        assert!(
25796            multibuffer_text.contains("target_b"),
25797            "Multibuffer should contain the second definition"
25798        );
25799    });
25800}
25801
25802#[gpui::test]
25803async fn test_goto_definition_contained_ranges(cx: &mut TestAppContext) {
25804    init_test(cx, |_| {});
25805    let mut cx = EditorLspTestContext::new_rust(
25806        lsp::ServerCapabilities {
25807            definition_provider: Some(lsp::OneOf::Left(true)),
25808            ..lsp::ServerCapabilities::default()
25809        },
25810        cx,
25811    )
25812    .await;
25813
25814    // The LSP returns two single-line definitions on the same row where one
25815    // range contains the other. Both are on the same line so the
25816    // `fits_in_one_excerpt` check won't underflow, and the code reaches
25817    // `change_selections`.
25818    cx.set_state(
25819        &r#"fn caller() {
25820            let _ = ˇtarget();
25821        }
25822        fn target_outer() { fn target_inner() {} }
25823        "#
25824        .unindent(),
25825    );
25826
25827    // Return two definitions on the same line: an outer range covering the
25828    // whole line and an inner range for just the inner function name.
25829    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25830        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25831            // Inner range: just "target_inner" (cols 23..35)
25832            lsp::Location {
25833                uri: url.clone(),
25834                range: lsp::Range::new(lsp::Position::new(3, 23), lsp::Position::new(3, 35)),
25835            },
25836            // Outer range: the whole line (cols 0..48)
25837            lsp::Location {
25838                uri: url,
25839                range: lsp::Range::new(lsp::Position::new(3, 0), lsp::Position::new(3, 48)),
25840            },
25841        ])))
25842    });
25843
25844    let navigated = cx
25845        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25846        .await
25847        .expect("Failed to navigate to definitions");
25848    assert_eq!(navigated, Navigated::Yes);
25849}
25850
25851#[gpui::test]
25852async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
25853    init_test(cx, |_| {});
25854    let mut cx = EditorLspTestContext::new_rust(
25855        lsp::ServerCapabilities {
25856            references_provider: Some(lsp::OneOf::Left(true)),
25857            ..lsp::ServerCapabilities::default()
25858        },
25859        cx,
25860    )
25861    .await;
25862
25863    cx.set_state(
25864        &r#"
25865        fn one() {
25866            let mut a = two();
25867        }
25868
25869        fn ˇtwo() {}"#
25870            .unindent(),
25871    );
25872    cx.lsp
25873        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25874            Ok(Some(vec![
25875                lsp::Location {
25876                    uri: params.text_document_position.text_document.uri.clone(),
25877                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
25878                },
25879                lsp::Location {
25880                    uri: params.text_document_position.text_document.uri,
25881                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
25882                },
25883            ]))
25884        });
25885    let navigated = cx
25886        .update_editor(|editor, window, cx| {
25887            editor.find_all_references(&FindAllReferences::default(), window, cx)
25888        })
25889        .unwrap()
25890        .await
25891        .expect("Failed to navigate to references");
25892    assert_eq!(
25893        navigated,
25894        Navigated::Yes,
25895        "Should have navigated to references from the FindAllReferences response"
25896    );
25897    cx.assert_editor_state(
25898        &r#"fn one() {
25899            let mut a = two();
25900        }
25901
25902        fn ˇtwo() {}"#
25903            .unindent(),
25904    );
25905
25906    let editors = cx.update_workspace(|workspace, _, cx| {
25907        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25908    });
25909    cx.update_editor(|_, _, _| {
25910        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
25911    });
25912
25913    cx.set_state(
25914        &r#"fn one() {
25915            let mut a = ˇtwo();
25916        }
25917
25918        fn two() {}"#
25919            .unindent(),
25920    );
25921    let navigated = cx
25922        .update_editor(|editor, window, cx| {
25923            editor.find_all_references(&FindAllReferences::default(), window, cx)
25924        })
25925        .unwrap()
25926        .await
25927        .expect("Failed to navigate to references");
25928    assert_eq!(
25929        navigated,
25930        Navigated::Yes,
25931        "Should have navigated to references from the FindAllReferences response"
25932    );
25933    cx.assert_editor_state(
25934        &r#"fn one() {
25935            let mut a = ˇtwo();
25936        }
25937
25938        fn two() {}"#
25939            .unindent(),
25940    );
25941    let editors = cx.update_workspace(|workspace, _, cx| {
25942        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25943    });
25944    cx.update_editor(|_, _, _| {
25945        assert_eq!(
25946            editors.len(),
25947            2,
25948            "should have re-used the previous multibuffer"
25949        );
25950    });
25951
25952    cx.set_state(
25953        &r#"fn one() {
25954            let mut a = ˇtwo();
25955        }
25956        fn three() {}
25957        fn two() {}"#
25958            .unindent(),
25959    );
25960    cx.lsp
25961        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25962            Ok(Some(vec![
25963                lsp::Location {
25964                    uri: params.text_document_position.text_document.uri.clone(),
25965                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
25966                },
25967                lsp::Location {
25968                    uri: params.text_document_position.text_document.uri,
25969                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
25970                },
25971            ]))
25972        });
25973    let navigated = cx
25974        .update_editor(|editor, window, cx| {
25975            editor.find_all_references(&FindAllReferences::default(), window, cx)
25976        })
25977        .unwrap()
25978        .await
25979        .expect("Failed to navigate to references");
25980    assert_eq!(
25981        navigated,
25982        Navigated::Yes,
25983        "Should have navigated to references from the FindAllReferences response"
25984    );
25985    cx.assert_editor_state(
25986        &r#"fn one() {
25987                let mut a = ˇtwo();
25988            }
25989            fn three() {}
25990            fn two() {}"#
25991            .unindent(),
25992    );
25993    let editors = cx.update_workspace(|workspace, _, cx| {
25994        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25995    });
25996    cx.update_editor(|_, _, _| {
25997        assert_eq!(
25998            editors.len(),
25999            3,
26000            "should have used a new multibuffer as offsets changed"
26001        );
26002    });
26003}
26004#[gpui::test]
26005async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
26006    init_test(cx, |_| {});
26007
26008    let language = Arc::new(Language::new(
26009        LanguageConfig::default(),
26010        Some(tree_sitter_rust::LANGUAGE.into()),
26011    ));
26012
26013    let text = r#"
26014        #[cfg(test)]
26015        mod tests() {
26016            #[test]
26017            fn runnable_1() {
26018                let a = 1;
26019            }
26020
26021            #[test]
26022            fn runnable_2() {
26023                let a = 1;
26024                let b = 2;
26025            }
26026        }
26027    "#
26028    .unindent();
26029
26030    let fs = FakeFs::new(cx.executor());
26031    fs.insert_file("/file.rs", Default::default()).await;
26032
26033    let project = Project::test(fs, ["/a".as_ref()], cx).await;
26034    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26035    let cx = &mut VisualTestContext::from_window(*window, cx);
26036    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26037    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
26038
26039    let editor = cx.new_window_entity(|window, cx| {
26040        Editor::new(
26041            EditorMode::full(),
26042            multi_buffer,
26043            Some(project.clone()),
26044            window,
26045            cx,
26046        )
26047    });
26048
26049    editor.update_in(cx, |editor, window, cx| {
26050        let snapshot = editor.buffer().read(cx).snapshot(cx);
26051        editor.runnables.insert(
26052            buffer.read(cx).remote_id(),
26053            3,
26054            buffer.read(cx).version(),
26055            RunnableTasks {
26056                templates: Vec::new(),
26057                offset: snapshot.anchor_before(MultiBufferOffset(43)),
26058                column: 0,
26059                extra_variables: HashMap::default(),
26060                context_range: BufferOffset(43)..BufferOffset(85),
26061            },
26062        );
26063        editor.runnables.insert(
26064            buffer.read(cx).remote_id(),
26065            8,
26066            buffer.read(cx).version(),
26067            RunnableTasks {
26068                templates: Vec::new(),
26069                offset: snapshot.anchor_before(MultiBufferOffset(86)),
26070                column: 0,
26071                extra_variables: HashMap::default(),
26072                context_range: BufferOffset(86)..BufferOffset(191),
26073            },
26074        );
26075
26076        // Test finding task when cursor is inside function body
26077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26078            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
26079        });
26080        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
26081        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
26082
26083        // Test finding task when cursor is on function name
26084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26085            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
26086        });
26087        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
26088        assert_eq!(row, 8, "Should find task when cursor is on function name");
26089    });
26090}
26091
26092#[gpui::test]
26093async fn test_folding_buffers(cx: &mut TestAppContext) {
26094    init_test(cx, |_| {});
26095
26096    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
26097    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
26098    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
26099
26100    let fs = FakeFs::new(cx.executor());
26101    fs.insert_tree(
26102        path!("/a"),
26103        json!({
26104            "first.rs": sample_text_1,
26105            "second.rs": sample_text_2,
26106            "third.rs": sample_text_3,
26107        }),
26108    )
26109    .await;
26110    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26111    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26112    let cx = &mut VisualTestContext::from_window(*window, cx);
26113    let worktree = project.update(cx, |project, cx| {
26114        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26115        assert_eq!(worktrees.len(), 1);
26116        worktrees.pop().unwrap()
26117    });
26118    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26119
26120    let buffer_1 = project
26121        .update(cx, |project, cx| {
26122            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
26123        })
26124        .await
26125        .unwrap();
26126    let buffer_2 = project
26127        .update(cx, |project, cx| {
26128            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
26129        })
26130        .await
26131        .unwrap();
26132    let buffer_3 = project
26133        .update(cx, |project, cx| {
26134            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
26135        })
26136        .await
26137        .unwrap();
26138
26139    let multi_buffer = cx.new(|cx| {
26140        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26141        multi_buffer.set_excerpts_for_path(
26142            PathKey::sorted(0),
26143            buffer_1.clone(),
26144            [
26145                Point::new(0, 0)..Point::new(2, 0),
26146                Point::new(5, 0)..Point::new(6, 0),
26147                Point::new(9, 0)..Point::new(10, 4),
26148            ],
26149            0,
26150            cx,
26151        );
26152        multi_buffer.set_excerpts_for_path(
26153            PathKey::sorted(1),
26154            buffer_2.clone(),
26155            [
26156                Point::new(0, 0)..Point::new(2, 0),
26157                Point::new(5, 0)..Point::new(6, 0),
26158                Point::new(9, 0)..Point::new(10, 4),
26159            ],
26160            0,
26161            cx,
26162        );
26163        multi_buffer.set_excerpts_for_path(
26164            PathKey::sorted(2),
26165            buffer_3.clone(),
26166            [
26167                Point::new(0, 0)..Point::new(2, 0),
26168                Point::new(5, 0)..Point::new(6, 0),
26169                Point::new(9, 0)..Point::new(10, 4),
26170            ],
26171            0,
26172            cx,
26173        );
26174        multi_buffer
26175    });
26176    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26177        Editor::new(
26178            EditorMode::full(),
26179            multi_buffer.clone(),
26180            Some(project.clone()),
26181            window,
26182            cx,
26183        )
26184    });
26185
26186    assert_eq!(
26187        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26188        "\n\naaaa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26189    );
26190
26191    multi_buffer_editor.update(cx, |editor, cx| {
26192        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
26193    });
26194    assert_eq!(
26195        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26196        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26197        "After folding the first buffer, its text should not be displayed"
26198    );
26199
26200    multi_buffer_editor.update(cx, |editor, cx| {
26201        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
26202    });
26203    assert_eq!(
26204        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26205        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26206        "After folding the second buffer, its text should not be displayed"
26207    );
26208
26209    multi_buffer_editor.update(cx, |editor, cx| {
26210        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
26211    });
26212    assert_eq!(
26213        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26214        "\n\n\n\n\n",
26215        "After folding the third buffer, its text should not be displayed"
26216    );
26217
26218    // Emulate selection inside the fold logic, that should work
26219    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26220        editor
26221            .snapshot(window, cx)
26222            .next_line_boundary(Point::new(0, 4));
26223    });
26224
26225    multi_buffer_editor.update(cx, |editor, cx| {
26226        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
26227    });
26228    assert_eq!(
26229        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26230        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
26231        "After unfolding the second buffer, its text should be displayed"
26232    );
26233
26234    // Typing inside of buffer 1 causes that buffer to be unfolded.
26235    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26236        assert_eq!(
26237            multi_buffer
26238                .read(cx)
26239                .snapshot(cx)
26240                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
26241                .collect::<String>(),
26242            "bbbb"
26243        );
26244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26245            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
26246        });
26247        editor.handle_input("B", window, cx);
26248    });
26249
26250    assert_eq!(
26251        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26252        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
26253        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
26254    );
26255
26256    multi_buffer_editor.update(cx, |editor, cx| {
26257        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
26258    });
26259    assert_eq!(
26260        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26261        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26262        "After unfolding the all buffers, all original text should be displayed"
26263    );
26264}
26265
26266#[gpui::test]
26267async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext) {
26268    init_test(cx, |_| {});
26269
26270    let fs = FakeFs::new(cx.executor());
26271    fs.insert_tree(
26272        path!("/root"),
26273        json!({
26274            "file_a.txt": "File A\nFile A\nFile A",
26275            "file_b.txt": "File B\nFile B\nFile B",
26276        }),
26277    )
26278    .await;
26279
26280    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
26281    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26282    let cx = &mut VisualTestContext::from_window(*window, cx);
26283    let worktree = project.update(cx, |project, cx| {
26284        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26285        assert_eq!(worktrees.len(), 1);
26286        worktrees.pop().unwrap()
26287    });
26288    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26289
26290    let buffer_a = project
26291        .update(cx, |project, cx| {
26292            project.open_buffer((worktree_id, rel_path("file_a.txt")), cx)
26293        })
26294        .await
26295        .unwrap();
26296    let buffer_b = project
26297        .update(cx, |project, cx| {
26298            project.open_buffer((worktree_id, rel_path("file_b.txt")), cx)
26299        })
26300        .await
26301        .unwrap();
26302
26303    let multi_buffer = cx.new(|cx| {
26304        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26305        let range_a = Point::new(0, 0)..Point::new(2, 4);
26306        let range_b = Point::new(0, 0)..Point::new(2, 4);
26307
26308        multi_buffer.set_excerpts_for_path(PathKey::sorted(0), buffer_a.clone(), [range_a], 0, cx);
26309        multi_buffer.set_excerpts_for_path(PathKey::sorted(1), buffer_b.clone(), [range_b], 0, cx);
26310        multi_buffer
26311    });
26312
26313    let editor = cx.new_window_entity(|window, cx| {
26314        Editor::new(
26315            EditorMode::full(),
26316            multi_buffer.clone(),
26317            Some(project.clone()),
26318            window,
26319            cx,
26320        )
26321    });
26322
26323    editor.update(cx, |editor, cx| {
26324        editor.fold_buffer(buffer_a.read(cx).remote_id(), cx);
26325    });
26326    assert!(editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
26327
26328    // When the excerpts for `buffer_a` are removed, a
26329    // `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
26330    // picked up by the editor and update the display map accordingly.
26331    multi_buffer.update(cx, |multi_buffer, cx| {
26332        multi_buffer.remove_excerpts(PathKey::sorted(0), cx)
26333    });
26334    assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
26335}
26336
26337#[gpui::test]
26338async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
26339    init_test(cx, |_| {});
26340
26341    let sample_text_1 = "1111\n2222\n3333".to_string();
26342    let sample_text_2 = "4444\n5555\n6666".to_string();
26343    let sample_text_3 = "7777\n8888\n9999".to_string();
26344
26345    let fs = FakeFs::new(cx.executor());
26346    fs.insert_tree(
26347        path!("/a"),
26348        json!({
26349            "first.rs": sample_text_1,
26350            "second.rs": sample_text_2,
26351            "third.rs": sample_text_3,
26352        }),
26353    )
26354    .await;
26355    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26356    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26357    let cx = &mut VisualTestContext::from_window(*window, cx);
26358    let worktree = project.update(cx, |project, cx| {
26359        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26360        assert_eq!(worktrees.len(), 1);
26361        worktrees.pop().unwrap()
26362    });
26363    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26364
26365    let buffer_1 = project
26366        .update(cx, |project, cx| {
26367            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
26368        })
26369        .await
26370        .unwrap();
26371    let buffer_2 = project
26372        .update(cx, |project, cx| {
26373            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
26374        })
26375        .await
26376        .unwrap();
26377    let buffer_3 = project
26378        .update(cx, |project, cx| {
26379            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
26380        })
26381        .await
26382        .unwrap();
26383
26384    let multi_buffer = cx.new(|cx| {
26385        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26386        multi_buffer.set_excerpts_for_path(
26387            PathKey::sorted(0),
26388            buffer_1.clone(),
26389            [Point::new(0, 0)..Point::new(3, 0)],
26390            0,
26391            cx,
26392        );
26393        multi_buffer.set_excerpts_for_path(
26394            PathKey::sorted(1),
26395            buffer_2.clone(),
26396            [Point::new(0, 0)..Point::new(3, 0)],
26397            0,
26398            cx,
26399        );
26400        multi_buffer.set_excerpts_for_path(
26401            PathKey::sorted(2),
26402            buffer_3.clone(),
26403            [Point::new(0, 0)..Point::new(3, 0)],
26404            0,
26405            cx,
26406        );
26407        multi_buffer
26408    });
26409
26410    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26411        Editor::new(
26412            EditorMode::full(),
26413            multi_buffer,
26414            Some(project.clone()),
26415            window,
26416            cx,
26417        )
26418    });
26419
26420    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
26421    assert_eq!(
26422        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26423        full_text,
26424    );
26425
26426    multi_buffer_editor.update(cx, |editor, cx| {
26427        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
26428    });
26429    assert_eq!(
26430        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26431        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
26432        "After folding the first buffer, its text should not be displayed"
26433    );
26434
26435    multi_buffer_editor.update(cx, |editor, cx| {
26436        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
26437    });
26438
26439    assert_eq!(
26440        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26441        "\n\n\n\n\n\n7777\n8888\n9999",
26442        "After folding the second buffer, its text should not be displayed"
26443    );
26444
26445    multi_buffer_editor.update(cx, |editor, cx| {
26446        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
26447    });
26448    assert_eq!(
26449        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26450        "\n\n\n\n\n",
26451        "After folding the third buffer, its text should not be displayed"
26452    );
26453
26454    multi_buffer_editor.update(cx, |editor, cx| {
26455        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
26456    });
26457    assert_eq!(
26458        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26459        "\n\n\n\n4444\n5555\n6666\n\n",
26460        "After unfolding the second buffer, its text should be displayed"
26461    );
26462
26463    multi_buffer_editor.update(cx, |editor, cx| {
26464        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
26465    });
26466    assert_eq!(
26467        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26468        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
26469        "After unfolding the first buffer, its text should be displayed"
26470    );
26471
26472    multi_buffer_editor.update(cx, |editor, cx| {
26473        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
26474    });
26475    assert_eq!(
26476        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26477        full_text,
26478        "After unfolding all buffers, all original text should be displayed"
26479    );
26480}
26481
26482#[gpui::test]
26483async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
26484    init_test(cx, |_| {});
26485
26486    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
26487
26488    let fs = FakeFs::new(cx.executor());
26489    fs.insert_tree(
26490        path!("/a"),
26491        json!({
26492            "main.rs": sample_text,
26493        }),
26494    )
26495    .await;
26496    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26497    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26498    let cx = &mut VisualTestContext::from_window(*window, cx);
26499    let worktree = project.update(cx, |project, cx| {
26500        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26501        assert_eq!(worktrees.len(), 1);
26502        worktrees.pop().unwrap()
26503    });
26504    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26505
26506    let buffer_1 = project
26507        .update(cx, |project, cx| {
26508            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
26509        })
26510        .await
26511        .unwrap();
26512
26513    let multi_buffer = cx.new(|cx| {
26514        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26515        multi_buffer.set_excerpts_for_path(
26516            PathKey::sorted(0),
26517            buffer_1.clone(),
26518            [Point::new(0, 0)
26519                ..Point::new(
26520                    sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
26521                    0,
26522                )],
26523            0,
26524            cx,
26525        );
26526        multi_buffer
26527    });
26528    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26529        Editor::new(
26530            EditorMode::full(),
26531            multi_buffer,
26532            Some(project.clone()),
26533            window,
26534            cx,
26535        )
26536    });
26537
26538    let selection_range = Point::new(1, 0)..Point::new(2, 0);
26539    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26540        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26541        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
26542        editor.highlight_text(
26543            HighlightKey::Editor,
26544            vec![highlight_range.clone()],
26545            HighlightStyle::color(Hsla::green()),
26546            cx,
26547        );
26548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26549            s.select_ranges(Some(highlight_range))
26550        });
26551    });
26552
26553    let full_text = format!("\n\n{sample_text}");
26554    assert_eq!(
26555        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26556        full_text,
26557    );
26558}
26559
26560#[gpui::test]
26561async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
26562    init_test(cx, |_| {});
26563    cx.update(|cx| {
26564        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
26565            "keymaps/default-linux.json",
26566            cx,
26567        )
26568        .unwrap();
26569        cx.bind_keys(default_key_bindings);
26570    });
26571
26572    let (editor, cx) = cx.add_window_view(|window, cx| {
26573        let multi_buffer = MultiBuffer::build_multi(
26574            [
26575                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
26576                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
26577                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
26578                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
26579            ],
26580            cx,
26581        );
26582        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26583
26584        let buffer_ids = multi_buffer
26585            .read(cx)
26586            .snapshot(cx)
26587            .excerpts()
26588            .map(|excerpt| excerpt.context.start.buffer_id)
26589            .collect::<Vec<_>>();
26590        // fold all but the second buffer, so that we test navigating between two
26591        // adjacent folded buffers, as well as folded buffers at the start and
26592        // end the multibuffer
26593        editor.fold_buffer(buffer_ids[0], cx);
26594        editor.fold_buffer(buffer_ids[2], cx);
26595        editor.fold_buffer(buffer_ids[3], cx);
26596
26597        editor
26598    });
26599    cx.simulate_resize(size(px(1000.), px(1000.)));
26600
26601    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26602    cx.assert_excerpts_with_selections(indoc! {"
26603        [EXCERPT]
26604        ˇ[FOLDED]
26605        [EXCERPT]
26606        a1
26607        b1
26608        [EXCERPT]
26609        [FOLDED]
26610        [EXCERPT]
26611        [FOLDED]
26612        "
26613    });
26614    cx.simulate_keystroke("down");
26615    cx.assert_excerpts_with_selections(indoc! {"
26616        [EXCERPT]
26617        [FOLDED]
26618        [EXCERPT]
26619        ˇa1
26620        b1
26621        [EXCERPT]
26622        [FOLDED]
26623        [EXCERPT]
26624        [FOLDED]
26625        "
26626    });
26627    cx.simulate_keystroke("down");
26628    cx.assert_excerpts_with_selections(indoc! {"
26629        [EXCERPT]
26630        [FOLDED]
26631        [EXCERPT]
26632        a1
26633        ˇb1
26634        [EXCERPT]
26635        [FOLDED]
26636        [EXCERPT]
26637        [FOLDED]
26638        "
26639    });
26640    cx.simulate_keystroke("down");
26641    cx.assert_excerpts_with_selections(indoc! {"
26642        [EXCERPT]
26643        [FOLDED]
26644        [EXCERPT]
26645        a1
26646        b1
26647        ˇ[EXCERPT]
26648        [FOLDED]
26649        [EXCERPT]
26650        [FOLDED]
26651        "
26652    });
26653    cx.simulate_keystroke("down");
26654    cx.assert_excerpts_with_selections(indoc! {"
26655        [EXCERPT]
26656        [FOLDED]
26657        [EXCERPT]
26658        a1
26659        b1
26660        [EXCERPT]
26661        ˇ[FOLDED]
26662        [EXCERPT]
26663        [FOLDED]
26664        "
26665    });
26666    for _ in 0..5 {
26667        cx.simulate_keystroke("down");
26668        cx.assert_excerpts_with_selections(indoc! {"
26669            [EXCERPT]
26670            [FOLDED]
26671            [EXCERPT]
26672            a1
26673            b1
26674            [EXCERPT]
26675            [FOLDED]
26676            [EXCERPT]
26677            ˇ[FOLDED]
26678            "
26679        });
26680    }
26681
26682    cx.simulate_keystroke("up");
26683    cx.assert_excerpts_with_selections(indoc! {"
26684        [EXCERPT]
26685        [FOLDED]
26686        [EXCERPT]
26687        a1
26688        b1
26689        [EXCERPT]
26690        ˇ[FOLDED]
26691        [EXCERPT]
26692        [FOLDED]
26693        "
26694    });
26695    cx.simulate_keystroke("up");
26696    cx.assert_excerpts_with_selections(indoc! {"
26697        [EXCERPT]
26698        [FOLDED]
26699        [EXCERPT]
26700        a1
26701        b1
26702        ˇ[EXCERPT]
26703        [FOLDED]
26704        [EXCERPT]
26705        [FOLDED]
26706        "
26707    });
26708    cx.simulate_keystroke("up");
26709    cx.assert_excerpts_with_selections(indoc! {"
26710        [EXCERPT]
26711        [FOLDED]
26712        [EXCERPT]
26713        a1
26714        ˇb1
26715        [EXCERPT]
26716        [FOLDED]
26717        [EXCERPT]
26718        [FOLDED]
26719        "
26720    });
26721    cx.simulate_keystroke("up");
26722    cx.assert_excerpts_with_selections(indoc! {"
26723        [EXCERPT]
26724        [FOLDED]
26725        [EXCERPT]
26726        ˇa1
26727        b1
26728        [EXCERPT]
26729        [FOLDED]
26730        [EXCERPT]
26731        [FOLDED]
26732        "
26733    });
26734    for _ in 0..5 {
26735        cx.simulate_keystroke("up");
26736        cx.assert_excerpts_with_selections(indoc! {"
26737            [EXCERPT]
26738            ˇ[FOLDED]
26739            [EXCERPT]
26740            a1
26741            b1
26742            [EXCERPT]
26743            [FOLDED]
26744            [EXCERPT]
26745            [FOLDED]
26746            "
26747        });
26748    }
26749}
26750
26751#[gpui::test]
26752async fn test_edit_prediction_text(cx: &mut TestAppContext) {
26753    init_test(cx, |_| {});
26754
26755    // Simple insertion
26756    assert_highlighted_edits(
26757        "Hello, world!",
26758        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
26759        true,
26760        cx,
26761        &|highlighted_edits, cx| {
26762            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
26763            assert_eq!(highlighted_edits.highlights.len(), 1);
26764            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
26765            assert_eq!(
26766                highlighted_edits.highlights[0].1.background_color,
26767                Some(cx.theme().status().created_background)
26768            );
26769        },
26770    )
26771    .await;
26772
26773    // Replacement
26774    assert_highlighted_edits(
26775        "This is a test.",
26776        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
26777        false,
26778        cx,
26779        &|highlighted_edits, cx| {
26780            assert_eq!(highlighted_edits.text, "That is a test.");
26781            assert_eq!(highlighted_edits.highlights.len(), 1);
26782            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
26783            assert_eq!(
26784                highlighted_edits.highlights[0].1.background_color,
26785                Some(cx.theme().status().created_background)
26786            );
26787        },
26788    )
26789    .await;
26790
26791    // Multiple edits
26792    assert_highlighted_edits(
26793        "Hello, world!",
26794        vec![
26795            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
26796            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
26797        ],
26798        false,
26799        cx,
26800        &|highlighted_edits, cx| {
26801            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
26802            assert_eq!(highlighted_edits.highlights.len(), 2);
26803            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
26804            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
26805            assert_eq!(
26806                highlighted_edits.highlights[0].1.background_color,
26807                Some(cx.theme().status().created_background)
26808            );
26809            assert_eq!(
26810                highlighted_edits.highlights[1].1.background_color,
26811                Some(cx.theme().status().created_background)
26812            );
26813        },
26814    )
26815    .await;
26816
26817    // Multiple lines with edits
26818    assert_highlighted_edits(
26819        "First line\nSecond line\nThird line\nFourth line",
26820        vec![
26821            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
26822            (
26823                Point::new(2, 0)..Point::new(2, 10),
26824                "New third line".to_string(),
26825            ),
26826            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
26827        ],
26828        false,
26829        cx,
26830        &|highlighted_edits, cx| {
26831            assert_eq!(
26832                highlighted_edits.text,
26833                "Second modified\nNew third line\nFourth updated line"
26834            );
26835            assert_eq!(highlighted_edits.highlights.len(), 3);
26836            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
26837            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
26838            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
26839            for highlight in &highlighted_edits.highlights {
26840                assert_eq!(
26841                    highlight.1.background_color,
26842                    Some(cx.theme().status().created_background)
26843                );
26844            }
26845        },
26846    )
26847    .await;
26848}
26849
26850#[gpui::test]
26851async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
26852    init_test(cx, |_| {});
26853
26854    // Deletion
26855    assert_highlighted_edits(
26856        "Hello, world!",
26857        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
26858        true,
26859        cx,
26860        &|highlighted_edits, cx| {
26861            assert_eq!(highlighted_edits.text, "Hello, world!");
26862            assert_eq!(highlighted_edits.highlights.len(), 1);
26863            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
26864            assert_eq!(
26865                highlighted_edits.highlights[0].1.background_color,
26866                Some(cx.theme().status().deleted_background)
26867            );
26868        },
26869    )
26870    .await;
26871
26872    // Insertion
26873    assert_highlighted_edits(
26874        "Hello, world!",
26875        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
26876        true,
26877        cx,
26878        &|highlighted_edits, cx| {
26879            assert_eq!(highlighted_edits.highlights.len(), 1);
26880            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
26881            assert_eq!(
26882                highlighted_edits.highlights[0].1.background_color,
26883                Some(cx.theme().status().created_background)
26884            );
26885        },
26886    )
26887    .await;
26888}
26889
26890async fn assert_highlighted_edits(
26891    text: &str,
26892    edits: Vec<(Range<Point>, String)>,
26893    include_deletions: bool,
26894    cx: &mut TestAppContext,
26895    assertion_fn: &dyn Fn(HighlightedText, &App),
26896) {
26897    let window = cx.add_window(|window, cx| {
26898        let buffer = MultiBuffer::build_simple(text, cx);
26899        Editor::new(EditorMode::full(), buffer, None, window, cx)
26900    });
26901    let cx = &mut VisualTestContext::from_window(*window, cx);
26902
26903    let (buffer, snapshot) = window
26904        .update(cx, |editor, _window, cx| {
26905            (
26906                editor.buffer().clone(),
26907                editor.buffer().read(cx).snapshot(cx),
26908            )
26909        })
26910        .unwrap();
26911
26912    let edits = edits
26913        .into_iter()
26914        .map(|(range, edit)| {
26915            (
26916                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
26917                edit,
26918            )
26919        })
26920        .collect::<Vec<_>>();
26921
26922    let text_anchor_edits = edits
26923        .clone()
26924        .into_iter()
26925        .map(|(range, edit)| {
26926            (
26927                range.start.expect_text_anchor()..range.end.expect_text_anchor(),
26928                edit.into(),
26929            )
26930        })
26931        .collect::<Vec<_>>();
26932
26933    let edit_preview = window
26934        .update(cx, |_, _window, cx| {
26935            buffer
26936                .read(cx)
26937                .as_singleton()
26938                .unwrap()
26939                .read(cx)
26940                .preview_edits(text_anchor_edits.into(), cx)
26941        })
26942        .unwrap()
26943        .await;
26944
26945    cx.update(|_window, cx| {
26946        let highlighted_edits = edit_prediction_edit_text(
26947            snapshot.as_singleton().unwrap(),
26948            &edits,
26949            &edit_preview,
26950            include_deletions,
26951            &snapshot,
26952            cx,
26953        );
26954        assertion_fn(highlighted_edits, cx)
26955    });
26956}
26957
26958#[track_caller]
26959fn assert_breakpoint(
26960    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
26961    path: &Arc<Path>,
26962    expected: Vec<(u32, Breakpoint)>,
26963) {
26964    if expected.is_empty() {
26965        assert!(!breakpoints.contains_key(path), "{}", path.display());
26966    } else {
26967        let mut breakpoint = breakpoints
26968            .get(path)
26969            .unwrap()
26970            .iter()
26971            .map(|breakpoint| {
26972                (
26973                    breakpoint.row,
26974                    Breakpoint {
26975                        message: breakpoint.message.clone(),
26976                        state: breakpoint.state,
26977                        condition: breakpoint.condition.clone(),
26978                        hit_condition: breakpoint.hit_condition.clone(),
26979                    },
26980                )
26981            })
26982            .collect::<Vec<_>>();
26983
26984        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
26985
26986        assert_eq!(expected, breakpoint);
26987    }
26988}
26989
26990fn add_log_breakpoint_at_cursor(
26991    editor: &mut Editor,
26992    log_message: &str,
26993    window: &mut Window,
26994    cx: &mut Context<Editor>,
26995) {
26996    let (anchor, bp) = editor
26997        .breakpoints_at_cursors(window, cx)
26998        .first()
26999        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
27000        .unwrap_or_else(|| {
27001            let snapshot = editor.snapshot(window, cx);
27002            let cursor_position: Point =
27003                editor.selections.newest(&snapshot.display_snapshot).head();
27004
27005            let breakpoint_position = snapshot
27006                .buffer_snapshot()
27007                .anchor_before(Point::new(cursor_position.row, 0));
27008
27009            (breakpoint_position, Breakpoint::new_log(log_message))
27010        });
27011
27012    editor.edit_breakpoint_at_anchor(
27013        anchor,
27014        bp,
27015        BreakpointEditAction::EditLogMessage(log_message.into()),
27016        cx,
27017    );
27018}
27019
27020#[gpui::test]
27021async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
27022    init_test(cx, |_| {});
27023
27024    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27025    let fs = FakeFs::new(cx.executor());
27026    fs.insert_tree(
27027        path!("/a"),
27028        json!({
27029            "main.rs": sample_text,
27030        }),
27031    )
27032    .await;
27033    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27034    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27035    let cx = &mut VisualTestContext::from_window(*window, cx);
27036
27037    let fs = FakeFs::new(cx.executor());
27038    fs.insert_tree(
27039        path!("/a"),
27040        json!({
27041            "main.rs": sample_text,
27042        }),
27043    )
27044    .await;
27045    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27046    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27047    let workspace = window
27048        .read_with(cx, |mw, _| mw.workspace().clone())
27049        .unwrap();
27050    let cx = &mut VisualTestContext::from_window(*window, cx);
27051    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27052        workspace.project().update(cx, |project, cx| {
27053            project.worktrees(cx).next().unwrap().read(cx).id()
27054        })
27055    });
27056
27057    let buffer = project
27058        .update(cx, |project, cx| {
27059            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27060        })
27061        .await
27062        .unwrap();
27063
27064    let (editor, cx) = cx.add_window_view(|window, cx| {
27065        Editor::new(
27066            EditorMode::full(),
27067            MultiBuffer::build_from_buffer(buffer, cx),
27068            Some(project.clone()),
27069            window,
27070            cx,
27071        )
27072    });
27073
27074    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27075    let abs_path = project.read_with(cx, |project, cx| {
27076        project
27077            .absolute_path(&project_path, cx)
27078            .map(Arc::from)
27079            .unwrap()
27080    });
27081
27082    // assert we can add breakpoint on the first line
27083    editor.update_in(cx, |editor, window, cx| {
27084        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27085        editor.move_to_end(&MoveToEnd, window, cx);
27086        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27087    });
27088
27089    let breakpoints = editor.update(cx, |editor, cx| {
27090        editor
27091            .breakpoint_store()
27092            .as_ref()
27093            .unwrap()
27094            .read(cx)
27095            .all_source_breakpoints(cx)
27096    });
27097
27098    assert_eq!(1, breakpoints.len());
27099    assert_breakpoint(
27100        &breakpoints,
27101        &abs_path,
27102        vec![
27103            (0, Breakpoint::new_standard()),
27104            (3, Breakpoint::new_standard()),
27105        ],
27106    );
27107
27108    editor.update_in(cx, |editor, window, cx| {
27109        editor.move_to_beginning(&MoveToBeginning, window, cx);
27110        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27111    });
27112
27113    let breakpoints = editor.update(cx, |editor, cx| {
27114        editor
27115            .breakpoint_store()
27116            .as_ref()
27117            .unwrap()
27118            .read(cx)
27119            .all_source_breakpoints(cx)
27120    });
27121
27122    assert_eq!(1, breakpoints.len());
27123    assert_breakpoint(
27124        &breakpoints,
27125        &abs_path,
27126        vec![(3, Breakpoint::new_standard())],
27127    );
27128
27129    editor.update_in(cx, |editor, window, cx| {
27130        editor.move_to_end(&MoveToEnd, window, cx);
27131        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27132    });
27133
27134    let breakpoints = editor.update(cx, |editor, cx| {
27135        editor
27136            .breakpoint_store()
27137            .as_ref()
27138            .unwrap()
27139            .read(cx)
27140            .all_source_breakpoints(cx)
27141    });
27142
27143    assert_eq!(0, breakpoints.len());
27144    assert_breakpoint(&breakpoints, &abs_path, vec![]);
27145}
27146
27147#[gpui::test]
27148async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
27149    init_test(cx, |_| {});
27150
27151    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27152
27153    let fs = FakeFs::new(cx.executor());
27154    fs.insert_tree(
27155        path!("/a"),
27156        json!({
27157            "main.rs": sample_text,
27158        }),
27159    )
27160    .await;
27161    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27162    let (multi_workspace, cx) =
27163        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27164    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27165
27166    let worktree_id = workspace.update(cx, |workspace, cx| {
27167        workspace.project().update(cx, |project, cx| {
27168            project.worktrees(cx).next().unwrap().read(cx).id()
27169        })
27170    });
27171
27172    let buffer = project
27173        .update(cx, |project, cx| {
27174            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27175        })
27176        .await
27177        .unwrap();
27178
27179    let (editor, cx) = cx.add_window_view(|window, cx| {
27180        Editor::new(
27181            EditorMode::full(),
27182            MultiBuffer::build_from_buffer(buffer, cx),
27183            Some(project.clone()),
27184            window,
27185            cx,
27186        )
27187    });
27188
27189    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27190    let abs_path = project.read_with(cx, |project, cx| {
27191        project
27192            .absolute_path(&project_path, cx)
27193            .map(Arc::from)
27194            .unwrap()
27195    });
27196
27197    editor.update_in(cx, |editor, window, cx| {
27198        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
27199    });
27200
27201    let breakpoints = editor.update(cx, |editor, cx| {
27202        editor
27203            .breakpoint_store()
27204            .as_ref()
27205            .unwrap()
27206            .read(cx)
27207            .all_source_breakpoints(cx)
27208    });
27209
27210    assert_breakpoint(
27211        &breakpoints,
27212        &abs_path,
27213        vec![(0, Breakpoint::new_log("hello world"))],
27214    );
27215
27216    // Removing a log message from a log breakpoint should remove it
27217    editor.update_in(cx, |editor, window, cx| {
27218        add_log_breakpoint_at_cursor(editor, "", window, cx);
27219    });
27220
27221    let breakpoints = editor.update(cx, |editor, cx| {
27222        editor
27223            .breakpoint_store()
27224            .as_ref()
27225            .unwrap()
27226            .read(cx)
27227            .all_source_breakpoints(cx)
27228    });
27229
27230    assert_breakpoint(&breakpoints, &abs_path, vec![]);
27231
27232    editor.update_in(cx, |editor, window, cx| {
27233        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27234        editor.move_to_end(&MoveToEnd, window, cx);
27235        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27236        // Not adding a log message to a standard breakpoint shouldn't remove it
27237        add_log_breakpoint_at_cursor(editor, "", window, cx);
27238    });
27239
27240    let breakpoints = editor.update(cx, |editor, cx| {
27241        editor
27242            .breakpoint_store()
27243            .as_ref()
27244            .unwrap()
27245            .read(cx)
27246            .all_source_breakpoints(cx)
27247    });
27248
27249    assert_breakpoint(
27250        &breakpoints,
27251        &abs_path,
27252        vec![
27253            (0, Breakpoint::new_standard()),
27254            (3, Breakpoint::new_standard()),
27255        ],
27256    );
27257
27258    editor.update_in(cx, |editor, window, cx| {
27259        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
27260    });
27261
27262    let breakpoints = editor.update(cx, |editor, cx| {
27263        editor
27264            .breakpoint_store()
27265            .as_ref()
27266            .unwrap()
27267            .read(cx)
27268            .all_source_breakpoints(cx)
27269    });
27270
27271    assert_breakpoint(
27272        &breakpoints,
27273        &abs_path,
27274        vec![
27275            (0, Breakpoint::new_standard()),
27276            (3, Breakpoint::new_log("hello world")),
27277        ],
27278    );
27279
27280    editor.update_in(cx, |editor, window, cx| {
27281        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
27282    });
27283
27284    let breakpoints = editor.update(cx, |editor, cx| {
27285        editor
27286            .breakpoint_store()
27287            .as_ref()
27288            .unwrap()
27289            .read(cx)
27290            .all_source_breakpoints(cx)
27291    });
27292
27293    assert_breakpoint(
27294        &breakpoints,
27295        &abs_path,
27296        vec![
27297            (0, Breakpoint::new_standard()),
27298            (3, Breakpoint::new_log("hello Earth!!")),
27299        ],
27300    );
27301}
27302
27303/// This also tests that Editor::breakpoint_at_cursor_head is working properly
27304/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
27305/// or when breakpoints were placed out of order. This tests for a regression too
27306#[gpui::test]
27307async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
27308    init_test(cx, |_| {});
27309
27310    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27311    let fs = FakeFs::new(cx.executor());
27312    fs.insert_tree(
27313        path!("/a"),
27314        json!({
27315            "main.rs": sample_text,
27316        }),
27317    )
27318    .await;
27319    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27320    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27321    let cx = &mut VisualTestContext::from_window(*window, cx);
27322
27323    let fs = FakeFs::new(cx.executor());
27324    fs.insert_tree(
27325        path!("/a"),
27326        json!({
27327            "main.rs": sample_text,
27328        }),
27329    )
27330    .await;
27331    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27332    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27333    let workspace = window
27334        .read_with(cx, |mw, _| mw.workspace().clone())
27335        .unwrap();
27336    let cx = &mut VisualTestContext::from_window(*window, cx);
27337    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27338        workspace.project().update(cx, |project, cx| {
27339            project.worktrees(cx).next().unwrap().read(cx).id()
27340        })
27341    });
27342
27343    let buffer = project
27344        .update(cx, |project, cx| {
27345            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27346        })
27347        .await
27348        .unwrap();
27349
27350    let (editor, cx) = cx.add_window_view(|window, cx| {
27351        Editor::new(
27352            EditorMode::full(),
27353            MultiBuffer::build_from_buffer(buffer, cx),
27354            Some(project.clone()),
27355            window,
27356            cx,
27357        )
27358    });
27359
27360    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27361    let abs_path = project.read_with(cx, |project, cx| {
27362        project
27363            .absolute_path(&project_path, cx)
27364            .map(Arc::from)
27365            .unwrap()
27366    });
27367
27368    // assert we can add breakpoint on the first line
27369    editor.update_in(cx, |editor, window, cx| {
27370        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27371        editor.move_to_end(&MoveToEnd, window, cx);
27372        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27373        editor.move_up(&MoveUp, window, cx);
27374        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27375    });
27376
27377    let breakpoints = editor.update(cx, |editor, cx| {
27378        editor
27379            .breakpoint_store()
27380            .as_ref()
27381            .unwrap()
27382            .read(cx)
27383            .all_source_breakpoints(cx)
27384    });
27385
27386    assert_eq!(1, breakpoints.len());
27387    assert_breakpoint(
27388        &breakpoints,
27389        &abs_path,
27390        vec![
27391            (0, Breakpoint::new_standard()),
27392            (2, Breakpoint::new_standard()),
27393            (3, Breakpoint::new_standard()),
27394        ],
27395    );
27396
27397    editor.update_in(cx, |editor, window, cx| {
27398        editor.move_to_beginning(&MoveToBeginning, window, cx);
27399        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27400        editor.move_to_end(&MoveToEnd, window, cx);
27401        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27402        // Disabling a breakpoint that doesn't exist should do nothing
27403        editor.move_up(&MoveUp, window, cx);
27404        editor.move_up(&MoveUp, window, cx);
27405        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27406    });
27407
27408    let breakpoints = editor.update(cx, |editor, cx| {
27409        editor
27410            .breakpoint_store()
27411            .as_ref()
27412            .unwrap()
27413            .read(cx)
27414            .all_source_breakpoints(cx)
27415    });
27416
27417    let disable_breakpoint = {
27418        let mut bp = Breakpoint::new_standard();
27419        bp.state = BreakpointState::Disabled;
27420        bp
27421    };
27422
27423    assert_eq!(1, breakpoints.len());
27424    assert_breakpoint(
27425        &breakpoints,
27426        &abs_path,
27427        vec![
27428            (0, disable_breakpoint.clone()),
27429            (2, Breakpoint::new_standard()),
27430            (3, disable_breakpoint.clone()),
27431        ],
27432    );
27433
27434    editor.update_in(cx, |editor, window, cx| {
27435        editor.move_to_beginning(&MoveToBeginning, window, cx);
27436        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
27437        editor.move_to_end(&MoveToEnd, window, cx);
27438        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
27439        editor.move_up(&MoveUp, window, cx);
27440        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27441    });
27442
27443    let breakpoints = editor.update(cx, |editor, cx| {
27444        editor
27445            .breakpoint_store()
27446            .as_ref()
27447            .unwrap()
27448            .read(cx)
27449            .all_source_breakpoints(cx)
27450    });
27451
27452    assert_eq!(1, breakpoints.len());
27453    assert_breakpoint(
27454        &breakpoints,
27455        &abs_path,
27456        vec![
27457            (0, Breakpoint::new_standard()),
27458            (2, disable_breakpoint),
27459            (3, Breakpoint::new_standard()),
27460        ],
27461    );
27462}
27463
27464#[gpui::test]
27465async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
27466    init_test(cx, |_| {});
27467
27468    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27469    let fs = FakeFs::new(cx.executor());
27470    fs.insert_tree(
27471        path!("/a"),
27472        json!({
27473            "main.rs": sample_text,
27474        }),
27475    )
27476    .await;
27477    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27478    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27479    let workspace = window
27480        .read_with(cx, |mw, _| mw.workspace().clone())
27481        .unwrap();
27482    let cx = &mut VisualTestContext::from_window(*window, cx);
27483    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27484        workspace.project().update(cx, |project, cx| {
27485            project.worktrees(cx).next().unwrap().read(cx).id()
27486        })
27487    });
27488
27489    let buffer = project
27490        .update(cx, |project, cx| {
27491            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27492        })
27493        .await
27494        .unwrap();
27495
27496    let (editor, cx) = cx.add_window_view(|window, cx| {
27497        Editor::new(
27498            EditorMode::full(),
27499            MultiBuffer::build_from_buffer(buffer, cx),
27500            Some(project.clone()),
27501            window,
27502            cx,
27503        )
27504    });
27505
27506    // Simulate hovering over row 0 with no existing breakpoint.
27507    editor.update(cx, |editor, _cx| {
27508        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
27509            display_row: DisplayRow(0),
27510            is_active: true,
27511            collides_with_existing_breakpoint: false,
27512        });
27513    });
27514
27515    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
27516    editor.update_in(cx, |editor, window, cx| {
27517        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27518    });
27519    editor.update(cx, |editor, _cx| {
27520        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27521        assert!(
27522            indicator.collides_with_existing_breakpoint,
27523            "Adding a breakpoint on the hovered row should set collision to true"
27524        );
27525    });
27526
27527    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
27528    editor.update_in(cx, |editor, window, cx| {
27529        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27530    });
27531    editor.update(cx, |editor, _cx| {
27532        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27533        assert!(
27534            !indicator.collides_with_existing_breakpoint,
27535            "Removing a breakpoint on the hovered row should set collision to false"
27536        );
27537    });
27538
27539    // Now move cursor to row 2 while phantom indicator stays on row 0.
27540    editor.update_in(cx, |editor, window, cx| {
27541        editor.move_down(&MoveDown, window, cx);
27542        editor.move_down(&MoveDown, window, cx);
27543    });
27544
27545    // Ensure phantom indicator is still on row 0, not colliding.
27546    editor.update(cx, |editor, _cx| {
27547        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
27548            display_row: DisplayRow(0),
27549            is_active: true,
27550            collides_with_existing_breakpoint: false,
27551        });
27552    });
27553
27554    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
27555    editor.update_in(cx, |editor, window, cx| {
27556        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27557    });
27558    editor.update(cx, |editor, _cx| {
27559        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27560        assert!(
27561            !indicator.collides_with_existing_breakpoint,
27562            "Toggling a breakpoint on a different row should not affect the phantom indicator"
27563        );
27564    });
27565}
27566
27567#[gpui::test]
27568async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
27569    init_test(cx, |_| {});
27570    let capabilities = lsp::ServerCapabilities {
27571        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
27572            prepare_provider: Some(true),
27573            work_done_progress_options: Default::default(),
27574        })),
27575        ..Default::default()
27576    };
27577    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
27578
27579    cx.set_state(indoc! {"
27580        struct Fˇoo {}
27581    "});
27582
27583    cx.update_editor(|editor, _, cx| {
27584        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
27585        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
27586        editor.highlight_background(
27587            HighlightKey::DocumentHighlightRead,
27588            &[highlight_range],
27589            |_, theme| theme.colors().editor_document_highlight_read_background,
27590            cx,
27591        );
27592    });
27593
27594    let mut prepare_rename_handler = cx
27595        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
27596            move |_, _, _| async move {
27597                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
27598                    start: lsp::Position {
27599                        line: 0,
27600                        character: 7,
27601                    },
27602                    end: lsp::Position {
27603                        line: 0,
27604                        character: 10,
27605                    },
27606                })))
27607            },
27608        );
27609    let prepare_rename_task = cx
27610        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
27611        .expect("Prepare rename was not started");
27612    prepare_rename_handler.next().await.unwrap();
27613    prepare_rename_task.await.expect("Prepare rename failed");
27614
27615    let mut rename_handler =
27616        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
27617            let edit = lsp::TextEdit {
27618                range: lsp::Range {
27619                    start: lsp::Position {
27620                        line: 0,
27621                        character: 7,
27622                    },
27623                    end: lsp::Position {
27624                        line: 0,
27625                        character: 10,
27626                    },
27627                },
27628                new_text: "FooRenamed".to_string(),
27629            };
27630            Ok(Some(lsp::WorkspaceEdit::new(
27631                // Specify the same edit twice
27632                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
27633            )))
27634        });
27635    let rename_task = cx
27636        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
27637        .expect("Confirm rename was not started");
27638    rename_handler.next().await.unwrap();
27639    rename_task.await.expect("Confirm rename failed");
27640    cx.run_until_parked();
27641
27642    // Despite two edits, only one is actually applied as those are identical
27643    cx.assert_editor_state(indoc! {"
27644        struct FooRenamedˇ {}
27645    "});
27646}
27647
27648#[gpui::test]
27649async fn test_rename_without_prepare(cx: &mut TestAppContext) {
27650    init_test(cx, |_| {});
27651    // These capabilities indicate that the server does not support prepare rename.
27652    let capabilities = lsp::ServerCapabilities {
27653        rename_provider: Some(lsp::OneOf::Left(true)),
27654        ..Default::default()
27655    };
27656    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
27657
27658    cx.set_state(indoc! {"
27659        struct Fˇoo {}
27660    "});
27661
27662    cx.update_editor(|editor, _window, cx| {
27663        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
27664        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
27665        editor.highlight_background(
27666            HighlightKey::DocumentHighlightRead,
27667            &[highlight_range],
27668            |_, theme| theme.colors().editor_document_highlight_read_background,
27669            cx,
27670        );
27671    });
27672
27673    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
27674        .expect("Prepare rename was not started")
27675        .await
27676        .expect("Prepare rename failed");
27677
27678    let mut rename_handler =
27679        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
27680            let edit = lsp::TextEdit {
27681                range: lsp::Range {
27682                    start: lsp::Position {
27683                        line: 0,
27684                        character: 7,
27685                    },
27686                    end: lsp::Position {
27687                        line: 0,
27688                        character: 10,
27689                    },
27690                },
27691                new_text: "FooRenamed".to_string(),
27692            };
27693            Ok(Some(lsp::WorkspaceEdit::new(
27694                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
27695            )))
27696        });
27697    let rename_task = cx
27698        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
27699        .expect("Confirm rename was not started");
27700    rename_handler.next().await.unwrap();
27701    rename_task.await.expect("Confirm rename failed");
27702    cx.run_until_parked();
27703
27704    // Correct range is renamed, as `surrounding_word` is used to find it.
27705    cx.assert_editor_state(indoc! {"
27706        struct FooRenamedˇ {}
27707    "});
27708}
27709
27710#[gpui::test]
27711async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
27712    init_test(cx, |_| {});
27713    let mut cx = EditorTestContext::new(cx).await;
27714
27715    let language = Arc::new(
27716        Language::new(
27717            LanguageConfig::default(),
27718            Some(tree_sitter_html::LANGUAGE.into()),
27719        )
27720        .with_brackets_query(
27721            r#"
27722            ("<" @open "/>" @close)
27723            ("</" @open ">" @close)
27724            ("<" @open ">" @close)
27725            ("\"" @open "\"" @close)
27726            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
27727        "#,
27728        )
27729        .unwrap(),
27730    );
27731    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27732
27733    cx.set_state(indoc! {"
27734        <span>ˇ</span>
27735    "});
27736    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27737    cx.assert_editor_state(indoc! {"
27738        <span>
27739        ˇ
27740        </span>
27741    "});
27742
27743    cx.set_state(indoc! {"
27744        <span><span></span>ˇ</span>
27745    "});
27746    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27747    cx.assert_editor_state(indoc! {"
27748        <span><span></span>
27749        ˇ</span>
27750    "});
27751
27752    cx.set_state(indoc! {"
27753        <span>ˇ
27754        </span>
27755    "});
27756    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27757    cx.assert_editor_state(indoc! {"
27758        <span>
27759        ˇ
27760        </span>
27761    "});
27762}
27763
27764#[gpui::test(iterations = 10)]
27765async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
27766    init_test(cx, |_| {});
27767
27768    let fs = FakeFs::new(cx.executor());
27769    fs.insert_tree(
27770        path!("/dir"),
27771        json!({
27772            "a.ts": "a",
27773        }),
27774    )
27775    .await;
27776
27777    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
27778    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27779    let workspace = window
27780        .read_with(cx, |mw, _| mw.workspace().clone())
27781        .unwrap();
27782    let cx = &mut VisualTestContext::from_window(*window, cx);
27783
27784    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27785    language_registry.add(Arc::new(Language::new(
27786        LanguageConfig {
27787            name: "TypeScript".into(),
27788            matcher: LanguageMatcher {
27789                path_suffixes: vec!["ts".to_string()],
27790                ..Default::default()
27791            },
27792            ..Default::default()
27793        },
27794        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
27795    )));
27796    let mut fake_language_servers = language_registry.register_fake_lsp(
27797        "TypeScript",
27798        FakeLspAdapter {
27799            capabilities: lsp::ServerCapabilities {
27800                code_lens_provider: Some(lsp::CodeLensOptions {
27801                    resolve_provider: Some(true),
27802                }),
27803                execute_command_provider: Some(lsp::ExecuteCommandOptions {
27804                    commands: vec!["_the/command".to_string()],
27805                    ..lsp::ExecuteCommandOptions::default()
27806                }),
27807                ..lsp::ServerCapabilities::default()
27808            },
27809            ..FakeLspAdapter::default()
27810        },
27811    );
27812
27813    let editor = workspace
27814        .update_in(cx, |workspace, window, cx| {
27815            workspace.open_abs_path(
27816                PathBuf::from(path!("/dir/a.ts")),
27817                OpenOptions::default(),
27818                window,
27819                cx,
27820            )
27821        })
27822        .await
27823        .unwrap()
27824        .downcast::<Editor>()
27825        .unwrap();
27826    cx.executor().run_until_parked();
27827
27828    let fake_server = fake_language_servers.next().await.unwrap();
27829
27830    let buffer = editor.update(cx, |editor, cx| {
27831        editor
27832            .buffer()
27833            .read(cx)
27834            .as_singleton()
27835            .expect("have opened a single file by path")
27836    });
27837
27838    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
27839    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
27840    drop(buffer_snapshot);
27841    let actions = cx
27842        .update_window(*window, |_, window, cx| {
27843            project.code_actions(&buffer, anchor..anchor, window, cx)
27844        })
27845        .unwrap();
27846
27847    fake_server
27848        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
27849            Ok(Some(vec![
27850                lsp::CodeLens {
27851                    range: lsp::Range::default(),
27852                    command: Some(lsp::Command {
27853                        title: "Code lens command".to_owned(),
27854                        command: "_the/command".to_owned(),
27855                        arguments: None,
27856                    }),
27857                    data: None,
27858                },
27859                lsp::CodeLens {
27860                    range: lsp::Range::default(),
27861                    command: Some(lsp::Command {
27862                        title: "Command not in capabilities".to_owned(),
27863                        command: "not in capabilities".to_owned(),
27864                        arguments: None,
27865                    }),
27866                    data: None,
27867                },
27868                lsp::CodeLens {
27869                    range: lsp::Range {
27870                        start: lsp::Position {
27871                            line: 1,
27872                            character: 1,
27873                        },
27874                        end: lsp::Position {
27875                            line: 1,
27876                            character: 1,
27877                        },
27878                    },
27879                    command: Some(lsp::Command {
27880                        title: "Command not in range".to_owned(),
27881                        command: "_the/command".to_owned(),
27882                        arguments: None,
27883                    }),
27884                    data: None,
27885                },
27886            ]))
27887        })
27888        .next()
27889        .await;
27890
27891    let actions = actions.await.unwrap();
27892    assert_eq!(
27893        actions.len(),
27894        1,
27895        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
27896    );
27897    let action = actions[0].clone();
27898    let apply = project.update(cx, |project, cx| {
27899        project.apply_code_action(buffer.clone(), action, true, cx)
27900    });
27901
27902    // Resolving the code action does not populate its edits. In absence of
27903    // edits, we must execute the given command.
27904    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
27905        |mut lens, _| async move {
27906            let lens_command = lens.command.as_mut().expect("should have a command");
27907            assert_eq!(lens_command.title, "Code lens command");
27908            lens_command.arguments = Some(vec![json!("the-argument")]);
27909            Ok(lens)
27910        },
27911    );
27912
27913    // While executing the command, the language server sends the editor
27914    // a `workspaceEdit` request.
27915    fake_server
27916        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
27917            let fake = fake_server.clone();
27918            move |params, _| {
27919                assert_eq!(params.command, "_the/command");
27920                let fake = fake.clone();
27921                async move {
27922                    fake.server
27923                        .request::<lsp::request::ApplyWorkspaceEdit>(
27924                            lsp::ApplyWorkspaceEditParams {
27925                                label: None,
27926                                edit: lsp::WorkspaceEdit {
27927                                    changes: Some(
27928                                        [(
27929                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
27930                                            vec![lsp::TextEdit {
27931                                                range: lsp::Range::new(
27932                                                    lsp::Position::new(0, 0),
27933                                                    lsp::Position::new(0, 0),
27934                                                ),
27935                                                new_text: "X".into(),
27936                                            }],
27937                                        )]
27938                                        .into_iter()
27939                                        .collect(),
27940                                    ),
27941                                    ..lsp::WorkspaceEdit::default()
27942                                },
27943                            },
27944                            DEFAULT_LSP_REQUEST_TIMEOUT,
27945                        )
27946                        .await
27947                        .into_response()
27948                        .unwrap();
27949                    Ok(Some(json!(null)))
27950                }
27951            }
27952        })
27953        .next()
27954        .await;
27955
27956    // Applying the code lens command returns a project transaction containing the edits
27957    // sent by the language server in its `workspaceEdit` request.
27958    let transaction = apply.await.unwrap();
27959    assert!(transaction.0.contains_key(&buffer));
27960    buffer.update(cx, |buffer, cx| {
27961        assert_eq!(buffer.text(), "Xa");
27962        buffer.undo(cx);
27963        assert_eq!(buffer.text(), "a");
27964    });
27965
27966    let actions_after_edits = cx
27967        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
27968        .unwrap()
27969        .await;
27970    assert_eq!(
27971        actions, actions_after_edits,
27972        "For the same selection, same code lens actions should be returned"
27973    );
27974
27975    let _responses =
27976        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
27977            panic!("No more code lens requests are expected");
27978        });
27979    editor.update_in(cx, |editor, window, cx| {
27980        editor.select_all(&SelectAll, window, cx);
27981    });
27982    cx.executor().run_until_parked();
27983    let new_actions = cx
27984        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
27985        .unwrap()
27986        .await;
27987    assert_eq!(
27988        actions, new_actions,
27989        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
27990    );
27991}
27992
27993#[gpui::test]
27994async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
27995    init_test(cx, |_| {});
27996
27997    let fs = FakeFs::new(cx.executor());
27998    let main_text = r#"fn main() {
27999println!("1");
28000println!("2");
28001println!("3");
28002println!("4");
28003println!("5");
28004}"#;
28005    let lib_text = "mod foo {}";
28006    fs.insert_tree(
28007        path!("/a"),
28008        json!({
28009            "lib.rs": lib_text,
28010            "main.rs": main_text,
28011        }),
28012    )
28013    .await;
28014
28015    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28016    let (multi_workspace, cx) =
28017        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28018    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28019    let worktree_id = workspace.update(cx, |workspace, cx| {
28020        workspace.project().update(cx, |project, cx| {
28021            project.worktrees(cx).next().unwrap().read(cx).id()
28022        })
28023    });
28024
28025    let expected_ranges = vec![
28026        Point::new(0, 0)..Point::new(0, 0),
28027        Point::new(1, 0)..Point::new(1, 1),
28028        Point::new(2, 0)..Point::new(2, 2),
28029        Point::new(3, 0)..Point::new(3, 3),
28030    ];
28031
28032    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28033    let editor_1 = workspace
28034        .update_in(cx, |workspace, window, cx| {
28035            workspace.open_path(
28036                (worktree_id, rel_path("main.rs")),
28037                Some(pane_1.downgrade()),
28038                true,
28039                window,
28040                cx,
28041            )
28042        })
28043        .unwrap()
28044        .await
28045        .downcast::<Editor>()
28046        .unwrap();
28047    pane_1.update(cx, |pane, cx| {
28048        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28049        open_editor.update(cx, |editor, cx| {
28050            assert_eq!(
28051                editor.display_text(cx),
28052                main_text,
28053                "Original main.rs text on initial open",
28054            );
28055            assert_eq!(
28056                editor
28057                    .selections
28058                    .all::<Point>(&editor.display_snapshot(cx))
28059                    .into_iter()
28060                    .map(|s| s.range())
28061                    .collect::<Vec<_>>(),
28062                vec![Point::zero()..Point::zero()],
28063                "Default selections on initial open",
28064            );
28065        })
28066    });
28067    editor_1.update_in(cx, |editor, window, cx| {
28068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28069            s.select_ranges(expected_ranges.clone());
28070        });
28071    });
28072
28073    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
28074        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
28075    });
28076    let editor_2 = workspace
28077        .update_in(cx, |workspace, window, cx| {
28078            workspace.open_path(
28079                (worktree_id, rel_path("main.rs")),
28080                Some(pane_2.downgrade()),
28081                true,
28082                window,
28083                cx,
28084            )
28085        })
28086        .unwrap()
28087        .await
28088        .downcast::<Editor>()
28089        .unwrap();
28090    pane_2.update(cx, |pane, cx| {
28091        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28092        open_editor.update(cx, |editor, cx| {
28093            assert_eq!(
28094                editor.display_text(cx),
28095                main_text,
28096                "Original main.rs text on initial open in another panel",
28097            );
28098            assert_eq!(
28099                editor
28100                    .selections
28101                    .all::<Point>(&editor.display_snapshot(cx))
28102                    .into_iter()
28103                    .map(|s| s.range())
28104                    .collect::<Vec<_>>(),
28105                vec![Point::zero()..Point::zero()],
28106                "Default selections on initial open in another panel",
28107            );
28108        })
28109    });
28110
28111    editor_2.update_in(cx, |editor, window, cx| {
28112        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
28113    });
28114
28115    let _other_editor_1 = workspace
28116        .update_in(cx, |workspace, window, cx| {
28117            workspace.open_path(
28118                (worktree_id, rel_path("lib.rs")),
28119                Some(pane_1.downgrade()),
28120                true,
28121                window,
28122                cx,
28123            )
28124        })
28125        .unwrap()
28126        .await
28127        .downcast::<Editor>()
28128        .unwrap();
28129    pane_1
28130        .update_in(cx, |pane, window, cx| {
28131            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
28132        })
28133        .await
28134        .unwrap();
28135    drop(editor_1);
28136    pane_1.update(cx, |pane, cx| {
28137        pane.active_item()
28138            .unwrap()
28139            .downcast::<Editor>()
28140            .unwrap()
28141            .update(cx, |editor, cx| {
28142                assert_eq!(
28143                    editor.display_text(cx),
28144                    lib_text,
28145                    "Other file should be open and active",
28146                );
28147            });
28148        assert_eq!(pane.items().count(), 1, "No other editors should be open");
28149    });
28150
28151    let _other_editor_2 = workspace
28152        .update_in(cx, |workspace, window, cx| {
28153            workspace.open_path(
28154                (worktree_id, rel_path("lib.rs")),
28155                Some(pane_2.downgrade()),
28156                true,
28157                window,
28158                cx,
28159            )
28160        })
28161        .unwrap()
28162        .await
28163        .downcast::<Editor>()
28164        .unwrap();
28165    pane_2
28166        .update_in(cx, |pane, window, cx| {
28167            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
28168        })
28169        .await
28170        .unwrap();
28171    drop(editor_2);
28172    pane_2.update(cx, |pane, cx| {
28173        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28174        open_editor.update(cx, |editor, cx| {
28175            assert_eq!(
28176                editor.display_text(cx),
28177                lib_text,
28178                "Other file should be open and active in another panel too",
28179            );
28180        });
28181        assert_eq!(
28182            pane.items().count(),
28183            1,
28184            "No other editors should be open in another pane",
28185        );
28186    });
28187
28188    let _editor_1_reopened = workspace
28189        .update_in(cx, |workspace, window, cx| {
28190            workspace.open_path(
28191                (worktree_id, rel_path("main.rs")),
28192                Some(pane_1.downgrade()),
28193                true,
28194                window,
28195                cx,
28196            )
28197        })
28198        .unwrap()
28199        .await
28200        .downcast::<Editor>()
28201        .unwrap();
28202    let _editor_2_reopened = workspace
28203        .update_in(cx, |workspace, window, cx| {
28204            workspace.open_path(
28205                (worktree_id, rel_path("main.rs")),
28206                Some(pane_2.downgrade()),
28207                true,
28208                window,
28209                cx,
28210            )
28211        })
28212        .unwrap()
28213        .await
28214        .downcast::<Editor>()
28215        .unwrap();
28216    pane_1.update(cx, |pane, cx| {
28217        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28218        open_editor.update(cx, |editor, cx| {
28219            assert_eq!(
28220                editor.display_text(cx),
28221                main_text,
28222                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
28223            );
28224            assert_eq!(
28225                editor
28226                    .selections
28227                    .all::<Point>(&editor.display_snapshot(cx))
28228                    .into_iter()
28229                    .map(|s| s.range())
28230                    .collect::<Vec<_>>(),
28231                expected_ranges,
28232                "Previous editor in the 1st panel had selections and should get them restored on reopen",
28233            );
28234        })
28235    });
28236    pane_2.update(cx, |pane, cx| {
28237        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28238        open_editor.update(cx, |editor, cx| {
28239            assert_eq!(
28240                editor.display_text(cx),
28241                r#"fn main() {
28242⋯rintln!("1");
28243⋯intln!("2");
28244⋯ntln!("3");
28245println!("4");
28246println!("5");
28247}"#,
28248                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
28249            );
28250            assert_eq!(
28251                editor
28252                    .selections
28253                    .all::<Point>(&editor.display_snapshot(cx))
28254                    .into_iter()
28255                    .map(|s| s.range())
28256                    .collect::<Vec<_>>(),
28257                vec![Point::zero()..Point::zero()],
28258                "Previous editor in the 2nd pane had no selections changed hence should restore none",
28259            );
28260        })
28261    });
28262}
28263
28264#[gpui::test]
28265async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
28266    init_test(cx, |_| {});
28267
28268    let fs = FakeFs::new(cx.executor());
28269    let main_text = r#"fn main() {
28270println!("1");
28271println!("2");
28272println!("3");
28273println!("4");
28274println!("5");
28275}"#;
28276    let lib_text = "mod foo {}";
28277    fs.insert_tree(
28278        path!("/a"),
28279        json!({
28280            "lib.rs": lib_text,
28281            "main.rs": main_text,
28282        }),
28283    )
28284    .await;
28285
28286    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28287    let (multi_workspace, cx) =
28288        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28289    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28290    let worktree_id = workspace.update(cx, |workspace, cx| {
28291        workspace.project().update(cx, |project, cx| {
28292            project.worktrees(cx).next().unwrap().read(cx).id()
28293        })
28294    });
28295
28296    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28297    let editor = workspace
28298        .update_in(cx, |workspace, window, cx| {
28299            workspace.open_path(
28300                (worktree_id, rel_path("main.rs")),
28301                Some(pane.downgrade()),
28302                true,
28303                window,
28304                cx,
28305            )
28306        })
28307        .unwrap()
28308        .await
28309        .downcast::<Editor>()
28310        .unwrap();
28311    pane.update(cx, |pane, cx| {
28312        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28313        open_editor.update(cx, |editor, cx| {
28314            assert_eq!(
28315                editor.display_text(cx),
28316                main_text,
28317                "Original main.rs text on initial open",
28318            );
28319        })
28320    });
28321    editor.update_in(cx, |editor, window, cx| {
28322        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
28323    });
28324
28325    cx.update_global(|store: &mut SettingsStore, cx| {
28326        store.update_user_settings(cx, |s| {
28327            s.workspace.restore_on_file_reopen = Some(false);
28328        });
28329    });
28330    editor.update_in(cx, |editor, window, cx| {
28331        editor.fold_ranges(
28332            vec![
28333                Point::new(1, 0)..Point::new(1, 1),
28334                Point::new(2, 0)..Point::new(2, 2),
28335                Point::new(3, 0)..Point::new(3, 3),
28336            ],
28337            false,
28338            window,
28339            cx,
28340        );
28341    });
28342    pane.update_in(cx, |pane, window, cx| {
28343        pane.close_all_items(&CloseAllItems::default(), window, cx)
28344    })
28345    .await
28346    .unwrap();
28347    pane.update(cx, |pane, _| {
28348        assert!(pane.active_item().is_none());
28349    });
28350    cx.update_global(|store: &mut SettingsStore, cx| {
28351        store.update_user_settings(cx, |s| {
28352            s.workspace.restore_on_file_reopen = Some(true);
28353        });
28354    });
28355
28356    let _editor_reopened = workspace
28357        .update_in(cx, |workspace, window, cx| {
28358            workspace.open_path(
28359                (worktree_id, rel_path("main.rs")),
28360                Some(pane.downgrade()),
28361                true,
28362                window,
28363                cx,
28364            )
28365        })
28366        .unwrap()
28367        .await
28368        .downcast::<Editor>()
28369        .unwrap();
28370    pane.update(cx, |pane, cx| {
28371        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28372        open_editor.update(cx, |editor, cx| {
28373            assert_eq!(
28374                editor.display_text(cx),
28375                main_text,
28376                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
28377            );
28378        })
28379    });
28380}
28381
28382#[gpui::test]
28383async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
28384    struct EmptyModalView {
28385        focus_handle: gpui::FocusHandle,
28386    }
28387    impl EventEmitter<DismissEvent> for EmptyModalView {}
28388    impl Render for EmptyModalView {
28389        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
28390            div()
28391        }
28392    }
28393    impl Focusable for EmptyModalView {
28394        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
28395            self.focus_handle.clone()
28396        }
28397    }
28398    impl workspace::ModalView for EmptyModalView {}
28399    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
28400        EmptyModalView {
28401            focus_handle: cx.focus_handle(),
28402        }
28403    }
28404
28405    init_test(cx, |_| {});
28406
28407    let fs = FakeFs::new(cx.executor());
28408    let project = Project::test(fs, [], cx).await;
28409    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28410    let workspace = window
28411        .read_with(cx, |mw, _| mw.workspace().clone())
28412        .unwrap();
28413    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
28414    let cx = &mut VisualTestContext::from_window(*window, cx);
28415    let editor = cx.new_window_entity(|window, cx| {
28416        Editor::new(
28417            EditorMode::full(),
28418            buffer,
28419            Some(project.clone()),
28420            window,
28421            cx,
28422        )
28423    });
28424    workspace.update_in(cx, |workspace, window, cx| {
28425        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
28426    });
28427
28428    editor.update_in(cx, |editor, window, cx| {
28429        editor.open_context_menu(&OpenContextMenu, window, cx);
28430        assert!(editor.mouse_context_menu.is_some());
28431    });
28432    workspace.update_in(cx, |workspace, window, cx| {
28433        workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
28434    });
28435
28436    cx.read(|cx| {
28437        assert!(editor.read(cx).mouse_context_menu.is_none());
28438    });
28439}
28440
28441fn set_linked_edit_ranges(
28442    opening: (Point, Point),
28443    closing: (Point, Point),
28444    editor: &mut Editor,
28445    cx: &mut Context<Editor>,
28446) {
28447    let Some((buffer, _)) = editor
28448        .buffer
28449        .read(cx)
28450        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
28451    else {
28452        panic!("Failed to get buffer for selection position");
28453    };
28454    let buffer = buffer.read(cx);
28455    let buffer_id = buffer.remote_id();
28456    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
28457    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
28458    let mut linked_ranges = HashMap::default();
28459    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
28460    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
28461}
28462
28463#[gpui::test]
28464async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
28465    init_test(cx, |_| {});
28466
28467    let fs = FakeFs::new(cx.executor());
28468    fs.insert_file(path!("/file.html"), Default::default())
28469        .await;
28470
28471    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
28472
28473    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28474    let html_language = Arc::new(Language::new(
28475        LanguageConfig {
28476            name: "HTML".into(),
28477            matcher: LanguageMatcher {
28478                path_suffixes: vec!["html".to_string()],
28479                ..LanguageMatcher::default()
28480            },
28481            brackets: BracketPairConfig {
28482                pairs: vec![BracketPair {
28483                    start: "<".into(),
28484                    end: ">".into(),
28485                    close: true,
28486                    ..Default::default()
28487                }],
28488                ..Default::default()
28489            },
28490            ..Default::default()
28491        },
28492        Some(tree_sitter_html::LANGUAGE.into()),
28493    ));
28494    language_registry.add(html_language);
28495    let mut fake_servers = language_registry.register_fake_lsp(
28496        "HTML",
28497        FakeLspAdapter {
28498            capabilities: lsp::ServerCapabilities {
28499                completion_provider: Some(lsp::CompletionOptions {
28500                    resolve_provider: Some(true),
28501                    ..Default::default()
28502                }),
28503                ..Default::default()
28504            },
28505            ..Default::default()
28506        },
28507    );
28508
28509    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28510    let workspace = window
28511        .read_with(cx, |mw, _| mw.workspace().clone())
28512        .unwrap();
28513    let cx = &mut VisualTestContext::from_window(*window, cx);
28514
28515    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
28516        workspace.project().update(cx, |project, cx| {
28517            project.worktrees(cx).next().unwrap().read(cx).id()
28518        })
28519    });
28520
28521    project
28522        .update(cx, |project, cx| {
28523            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
28524        })
28525        .await
28526        .unwrap();
28527    let editor = workspace
28528        .update_in(cx, |workspace, window, cx| {
28529            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
28530        })
28531        .await
28532        .unwrap()
28533        .downcast::<Editor>()
28534        .unwrap();
28535
28536    let fake_server = fake_servers.next().await.unwrap();
28537    cx.run_until_parked();
28538    editor.update_in(cx, |editor, window, cx| {
28539        editor.set_text("<ad></ad>", window, cx);
28540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28541            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
28542        });
28543        set_linked_edit_ranges(
28544            (Point::new(0, 1), Point::new(0, 3)),
28545            (Point::new(0, 6), Point::new(0, 8)),
28546            editor,
28547            cx,
28548        );
28549    });
28550    let mut completion_handle =
28551        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
28552            Ok(Some(lsp::CompletionResponse::Array(vec![
28553                lsp::CompletionItem {
28554                    label: "head".to_string(),
28555                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
28556                        lsp::InsertReplaceEdit {
28557                            new_text: "head".to_string(),
28558                            insert: lsp::Range::new(
28559                                lsp::Position::new(0, 1),
28560                                lsp::Position::new(0, 3),
28561                            ),
28562                            replace: lsp::Range::new(
28563                                lsp::Position::new(0, 1),
28564                                lsp::Position::new(0, 3),
28565                            ),
28566                        },
28567                    )),
28568                    ..Default::default()
28569                },
28570            ])))
28571        });
28572    editor.update_in(cx, |editor, window, cx| {
28573        editor.show_completions(&ShowCompletions, window, cx);
28574    });
28575    cx.run_until_parked();
28576    completion_handle.next().await.unwrap();
28577    editor.update(cx, |editor, _| {
28578        assert!(
28579            editor.context_menu_visible(),
28580            "Completion menu should be visible"
28581        );
28582    });
28583    editor.update_in(cx, |editor, window, cx| {
28584        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
28585    });
28586    cx.executor().run_until_parked();
28587    editor.update(cx, |editor, cx| {
28588        assert_eq!(editor.text(cx), "<head></head>");
28589    });
28590}
28591
28592#[gpui::test]
28593async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
28594    init_test(cx, |_| {});
28595
28596    let mut cx = EditorTestContext::new(cx).await;
28597    let language = Arc::new(Language::new(
28598        LanguageConfig {
28599            name: "TSX".into(),
28600            matcher: LanguageMatcher {
28601                path_suffixes: vec!["tsx".to_string()],
28602                ..LanguageMatcher::default()
28603            },
28604            brackets: BracketPairConfig {
28605                pairs: vec![BracketPair {
28606                    start: "<".into(),
28607                    end: ">".into(),
28608                    close: true,
28609                    ..Default::default()
28610                }],
28611                ..Default::default()
28612            },
28613            linked_edit_characters: HashSet::from_iter(['.']),
28614            ..Default::default()
28615        },
28616        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
28617    ));
28618    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28619
28620    // Test typing > does not extend linked pair
28621    cx.set_state("<divˇ<div></div>");
28622    cx.update_editor(|editor, _, cx| {
28623        set_linked_edit_ranges(
28624            (Point::new(0, 1), Point::new(0, 4)),
28625            (Point::new(0, 11), Point::new(0, 14)),
28626            editor,
28627            cx,
28628        );
28629    });
28630    cx.update_editor(|editor, window, cx| {
28631        editor.handle_input(">", window, cx);
28632    });
28633    cx.assert_editor_state("<div>ˇ<div></div>");
28634
28635    // Test typing . do extend linked pair
28636    cx.set_state("<Animatedˇ></Animated>");
28637    cx.update_editor(|editor, _, cx| {
28638        set_linked_edit_ranges(
28639            (Point::new(0, 1), Point::new(0, 9)),
28640            (Point::new(0, 12), Point::new(0, 20)),
28641            editor,
28642            cx,
28643        );
28644    });
28645    cx.update_editor(|editor, window, cx| {
28646        editor.handle_input(".", window, cx);
28647    });
28648    cx.assert_editor_state("<Animated.ˇ></Animated.>");
28649    cx.update_editor(|editor, _, cx| {
28650        set_linked_edit_ranges(
28651            (Point::new(0, 1), Point::new(0, 10)),
28652            (Point::new(0, 13), Point::new(0, 21)),
28653            editor,
28654            cx,
28655        );
28656    });
28657    cx.update_editor(|editor, window, cx| {
28658        editor.handle_input("V", window, cx);
28659    });
28660    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
28661}
28662
28663#[gpui::test]
28664async fn test_linked_edits_on_typing_dot_without_language_override(cx: &mut TestAppContext) {
28665    init_test(cx, |_| {});
28666
28667    let mut cx = EditorTestContext::new(cx).await;
28668    let language = Arc::new(Language::new(
28669        LanguageConfig {
28670            name: "HTML".into(),
28671            matcher: LanguageMatcher {
28672                path_suffixes: vec!["html".to_string()],
28673                ..LanguageMatcher::default()
28674            },
28675            brackets: BracketPairConfig {
28676                pairs: vec![BracketPair {
28677                    start: "<".into(),
28678                    end: ">".into(),
28679                    close: true,
28680                    ..Default::default()
28681                }],
28682                ..Default::default()
28683            },
28684            ..Default::default()
28685        },
28686        Some(tree_sitter_html::LANGUAGE.into()),
28687    ));
28688    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28689
28690    cx.set_state("<Tableˇ></Table>");
28691    cx.update_editor(|editor, _, cx| {
28692        set_linked_edit_ranges(
28693            (Point::new(0, 1), Point::new(0, 6)),
28694            (Point::new(0, 9), Point::new(0, 14)),
28695            editor,
28696            cx,
28697        );
28698    });
28699    cx.update_editor(|editor, window, cx| {
28700        editor.handle_input(".", window, cx);
28701    });
28702    cx.assert_editor_state("<Table.ˇ></Table.>");
28703}
28704
28705#[gpui::test]
28706async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
28707    init_test(cx, |_| {});
28708
28709    let fs = FakeFs::new(cx.executor());
28710    fs.insert_tree(
28711        path!("/root"),
28712        json!({
28713            "a": {
28714                "main.rs": "fn main() {}",
28715            },
28716            "foo": {
28717                "bar": {
28718                    "external_file.rs": "pub mod external {}",
28719                }
28720            }
28721        }),
28722    )
28723    .await;
28724
28725    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
28726    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28727    language_registry.add(rust_lang());
28728    let _fake_servers = language_registry.register_fake_lsp(
28729        "Rust",
28730        FakeLspAdapter {
28731            ..FakeLspAdapter::default()
28732        },
28733    );
28734    let (multi_workspace, cx) =
28735        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28736    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28737    let worktree_id = workspace.update(cx, |workspace, cx| {
28738        workspace.project().update(cx, |project, cx| {
28739            project.worktrees(cx).next().unwrap().read(cx).id()
28740        })
28741    });
28742
28743    let assert_language_servers_count =
28744        |expected: usize, context: &str, cx: &mut VisualTestContext| {
28745            project.update(cx, |project, cx| {
28746                let current = project
28747                    .lsp_store()
28748                    .read(cx)
28749                    .as_local()
28750                    .unwrap()
28751                    .language_servers
28752                    .len();
28753                assert_eq!(expected, current, "{context}");
28754            });
28755        };
28756
28757    assert_language_servers_count(
28758        0,
28759        "No servers should be running before any file is open",
28760        cx,
28761    );
28762    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28763    let main_editor = workspace
28764        .update_in(cx, |workspace, window, cx| {
28765            workspace.open_path(
28766                (worktree_id, rel_path("main.rs")),
28767                Some(pane.downgrade()),
28768                true,
28769                window,
28770                cx,
28771            )
28772        })
28773        .unwrap()
28774        .await
28775        .downcast::<Editor>()
28776        .unwrap();
28777    pane.update(cx, |pane, cx| {
28778        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28779        open_editor.update(cx, |editor, cx| {
28780            assert_eq!(
28781                editor.display_text(cx),
28782                "fn main() {}",
28783                "Original main.rs text on initial open",
28784            );
28785        });
28786        assert_eq!(open_editor, main_editor);
28787    });
28788    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
28789
28790    let external_editor = workspace
28791        .update_in(cx, |workspace, window, cx| {
28792            workspace.open_abs_path(
28793                PathBuf::from("/root/foo/bar/external_file.rs"),
28794                OpenOptions::default(),
28795                window,
28796                cx,
28797            )
28798        })
28799        .await
28800        .expect("opening external file")
28801        .downcast::<Editor>()
28802        .expect("downcasted external file's open element to editor");
28803    pane.update(cx, |pane, cx| {
28804        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28805        open_editor.update(cx, |editor, cx| {
28806            assert_eq!(
28807                editor.display_text(cx),
28808                "pub mod external {}",
28809                "External file is open now",
28810            );
28811        });
28812        assert_eq!(open_editor, external_editor);
28813    });
28814    assert_language_servers_count(
28815        1,
28816        "Second, external, *.rs file should join the existing server",
28817        cx,
28818    );
28819
28820    pane.update_in(cx, |pane, window, cx| {
28821        pane.close_active_item(&CloseActiveItem::default(), window, cx)
28822    })
28823    .await
28824    .unwrap();
28825    pane.update_in(cx, |pane, window, cx| {
28826        pane.navigate_backward(&Default::default(), window, cx);
28827    });
28828    cx.run_until_parked();
28829    pane.update(cx, |pane, cx| {
28830        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28831        open_editor.update(cx, |editor, cx| {
28832            assert_eq!(
28833                editor.display_text(cx),
28834                "pub mod external {}",
28835                "External file is open now",
28836            );
28837        });
28838    });
28839    assert_language_servers_count(
28840        1,
28841        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
28842        cx,
28843    );
28844
28845    cx.update(|_, cx| {
28846        workspace::reload(cx);
28847    });
28848    assert_language_servers_count(
28849        1,
28850        "After reloading the worktree with local and external files opened, only one project should be started",
28851        cx,
28852    );
28853}
28854
28855#[gpui::test]
28856async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
28857    init_test(cx, |_| {});
28858
28859    let mut cx = EditorTestContext::new(cx).await;
28860    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
28861    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28862
28863    // test cursor move to start of each line on tab
28864    // for `if`, `elif`, `else`, `while`, `with` and `for`
28865    cx.set_state(indoc! {"
28866        def main():
28867        ˇ    for item in items:
28868        ˇ        while item.active:
28869        ˇ            if item.value > 10:
28870        ˇ                continue
28871        ˇ            elif item.value < 0:
28872        ˇ                break
28873        ˇ            else:
28874        ˇ                with item.context() as ctx:
28875        ˇ                    yield count
28876        ˇ        else:
28877        ˇ            log('while else')
28878        ˇ    else:
28879        ˇ        log('for else')
28880    "});
28881    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28882    cx.wait_for_autoindent_applied().await;
28883    cx.assert_editor_state(indoc! {"
28884        def main():
28885            ˇfor item in items:
28886                ˇwhile item.active:
28887                    ˇif item.value > 10:
28888                        ˇcontinue
28889                    ˇelif item.value < 0:
28890                        ˇbreak
28891                    ˇelse:
28892                        ˇwith item.context() as ctx:
28893                            ˇyield count
28894                ˇelse:
28895                    ˇlog('while else')
28896            ˇelse:
28897                ˇlog('for else')
28898    "});
28899    // test relative indent is preserved when tab
28900    // for `if`, `elif`, `else`, `while`, `with` and `for`
28901    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28902    cx.wait_for_autoindent_applied().await;
28903    cx.assert_editor_state(indoc! {"
28904        def main():
28905                ˇfor item in items:
28906                    ˇwhile item.active:
28907                        ˇif item.value > 10:
28908                            ˇcontinue
28909                        ˇelif item.value < 0:
28910                            ˇbreak
28911                        ˇelse:
28912                            ˇwith item.context() as ctx:
28913                                ˇyield count
28914                    ˇelse:
28915                        ˇlog('while else')
28916                ˇelse:
28917                    ˇlog('for else')
28918    "});
28919
28920    // test cursor move to start of each line on tab
28921    // for `try`, `except`, `else`, `finally`, `match` and `def`
28922    cx.set_state(indoc! {"
28923        def main():
28924        ˇ    try:
28925        ˇ        fetch()
28926        ˇ    except ValueError:
28927        ˇ        handle_error()
28928        ˇ    else:
28929        ˇ        match value:
28930        ˇ            case _:
28931        ˇ    finally:
28932        ˇ        def status():
28933        ˇ            return 0
28934    "});
28935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28936    cx.wait_for_autoindent_applied().await;
28937    cx.assert_editor_state(indoc! {"
28938        def main():
28939            ˇtry:
28940                ˇfetch()
28941            ˇexcept ValueError:
28942                ˇhandle_error()
28943            ˇelse:
28944                ˇmatch value:
28945                    ˇcase _:
28946            ˇfinally:
28947                ˇdef status():
28948                    ˇreturn 0
28949    "});
28950    // test relative indent is preserved when tab
28951    // for `try`, `except`, `else`, `finally`, `match` and `def`
28952    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28953    cx.wait_for_autoindent_applied().await;
28954    cx.assert_editor_state(indoc! {"
28955        def main():
28956                ˇtry:
28957                    ˇfetch()
28958                ˇexcept ValueError:
28959                    ˇhandle_error()
28960                ˇelse:
28961                    ˇmatch value:
28962                        ˇcase _:
28963                ˇfinally:
28964                    ˇdef status():
28965                        ˇreturn 0
28966    "});
28967}
28968
28969#[gpui::test]
28970async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
28971    init_test(cx, |_| {});
28972
28973    let mut cx = EditorTestContext::new(cx).await;
28974    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
28975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28976
28977    // test `else` auto outdents when typed inside `if` block
28978    cx.set_state(indoc! {"
28979        def main():
28980            if i == 2:
28981                return
28982                ˇ
28983    "});
28984    cx.update_editor(|editor, window, cx| {
28985        editor.handle_input("else:", window, cx);
28986    });
28987    cx.wait_for_autoindent_applied().await;
28988    cx.assert_editor_state(indoc! {"
28989        def main():
28990            if i == 2:
28991                return
28992            else:ˇ
28993    "});
28994
28995    // test `except` auto outdents when typed inside `try` block
28996    cx.set_state(indoc! {"
28997        def main():
28998            try:
28999                i = 2
29000                ˇ
29001    "});
29002    cx.update_editor(|editor, window, cx| {
29003        editor.handle_input("except:", window, cx);
29004    });
29005    cx.wait_for_autoindent_applied().await;
29006    cx.assert_editor_state(indoc! {"
29007        def main():
29008            try:
29009                i = 2
29010            except:ˇ
29011    "});
29012
29013    // test `else` auto outdents when typed inside `except` block
29014    cx.set_state(indoc! {"
29015        def main():
29016            try:
29017                i = 2
29018            except:
29019                j = 2
29020                ˇ
29021    "});
29022    cx.update_editor(|editor, window, cx| {
29023        editor.handle_input("else:", window, cx);
29024    });
29025    cx.wait_for_autoindent_applied().await;
29026    cx.assert_editor_state(indoc! {"
29027        def main():
29028            try:
29029                i = 2
29030            except:
29031                j = 2
29032            else:ˇ
29033    "});
29034
29035    // test `finally` auto outdents when typed inside `else` block
29036    cx.set_state(indoc! {"
29037        def main():
29038            try:
29039                i = 2
29040            except:
29041                j = 2
29042            else:
29043                k = 2
29044                ˇ
29045    "});
29046    cx.update_editor(|editor, window, cx| {
29047        editor.handle_input("finally:", window, cx);
29048    });
29049    cx.wait_for_autoindent_applied().await;
29050    cx.assert_editor_state(indoc! {"
29051        def main():
29052            try:
29053                i = 2
29054            except:
29055                j = 2
29056            else:
29057                k = 2
29058            finally:ˇ
29059    "});
29060
29061    // test `else` does not outdents when typed inside `except` block right after for block
29062    cx.set_state(indoc! {"
29063        def main():
29064            try:
29065                i = 2
29066            except:
29067                for i in range(n):
29068                    pass
29069                ˇ
29070    "});
29071    cx.update_editor(|editor, window, cx| {
29072        editor.handle_input("else:", window, cx);
29073    });
29074    cx.wait_for_autoindent_applied().await;
29075    cx.assert_editor_state(indoc! {"
29076        def main():
29077            try:
29078                i = 2
29079            except:
29080                for i in range(n):
29081                    pass
29082                else:ˇ
29083    "});
29084
29085    // test `finally` auto outdents when typed inside `else` block right after for block
29086    cx.set_state(indoc! {"
29087        def main():
29088            try:
29089                i = 2
29090            except:
29091                j = 2
29092            else:
29093                for i in range(n):
29094                    pass
29095                ˇ
29096    "});
29097    cx.update_editor(|editor, window, cx| {
29098        editor.handle_input("finally:", window, cx);
29099    });
29100    cx.wait_for_autoindent_applied().await;
29101    cx.assert_editor_state(indoc! {"
29102        def main():
29103            try:
29104                i = 2
29105            except:
29106                j = 2
29107            else:
29108                for i in range(n):
29109                    pass
29110            finally:ˇ
29111    "});
29112
29113    // test `except` outdents to inner "try" block
29114    cx.set_state(indoc! {"
29115        def main():
29116            try:
29117                i = 2
29118                if i == 2:
29119                    try:
29120                        i = 3
29121                        ˇ
29122    "});
29123    cx.update_editor(|editor, window, cx| {
29124        editor.handle_input("except:", window, cx);
29125    });
29126    cx.wait_for_autoindent_applied().await;
29127    cx.assert_editor_state(indoc! {"
29128        def main():
29129            try:
29130                i = 2
29131                if i == 2:
29132                    try:
29133                        i = 3
29134                    except:ˇ
29135    "});
29136
29137    // test `except` outdents to outer "try" block
29138    cx.set_state(indoc! {"
29139        def main():
29140            try:
29141                i = 2
29142                if i == 2:
29143                    try:
29144                        i = 3
29145                ˇ
29146    "});
29147    cx.update_editor(|editor, window, cx| {
29148        editor.handle_input("except:", window, cx);
29149    });
29150    cx.wait_for_autoindent_applied().await;
29151    cx.assert_editor_state(indoc! {"
29152        def main():
29153            try:
29154                i = 2
29155                if i == 2:
29156                    try:
29157                        i = 3
29158            except:ˇ
29159    "});
29160
29161    // test `else` stays at correct indent when typed after `for` block
29162    cx.set_state(indoc! {"
29163        def main():
29164            for i in range(10):
29165                if i == 3:
29166                    break
29167            ˇ
29168    "});
29169    cx.update_editor(|editor, window, cx| {
29170        editor.handle_input("else:", window, cx);
29171    });
29172    cx.wait_for_autoindent_applied().await;
29173    cx.assert_editor_state(indoc! {"
29174        def main():
29175            for i in range(10):
29176                if i == 3:
29177                    break
29178            else:ˇ
29179    "});
29180
29181    // test does not outdent on typing after line with square brackets
29182    cx.set_state(indoc! {"
29183        def f() -> list[str]:
29184            ˇ
29185    "});
29186    cx.update_editor(|editor, window, cx| {
29187        editor.handle_input("a", window, cx);
29188    });
29189    cx.wait_for_autoindent_applied().await;
29190    cx.assert_editor_state(indoc! {"
29191        def f() -> list[str]:
2919229193    "});
29194
29195    // test does not outdent on typing : after case keyword
29196    cx.set_state(indoc! {"
29197        match 1:
29198            caseˇ
29199    "});
29200    cx.update_editor(|editor, window, cx| {
29201        editor.handle_input(":", window, cx);
29202    });
29203    cx.wait_for_autoindent_applied().await;
29204    cx.assert_editor_state(indoc! {"
29205        match 1:
29206            case:ˇ
29207    "});
29208}
29209
29210#[gpui::test]
29211async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
29212    init_test(cx, |_| {});
29213    update_test_language_settings(cx, &|settings| {
29214        settings.defaults.extend_comment_on_newline = Some(false);
29215    });
29216    let mut cx = EditorTestContext::new(cx).await;
29217    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
29218    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29219
29220    // test correct indent after newline on comment
29221    cx.set_state(indoc! {"
29222        # COMMENT:ˇ
29223    "});
29224    cx.update_editor(|editor, window, cx| {
29225        editor.newline(&Newline, window, cx);
29226    });
29227    cx.wait_for_autoindent_applied().await;
29228    cx.assert_editor_state(indoc! {"
29229        # COMMENT:
29230        ˇ
29231    "});
29232
29233    // test correct indent after newline in brackets
29234    cx.set_state(indoc! {"
29235        {ˇ}
29236    "});
29237    cx.update_editor(|editor, window, cx| {
29238        editor.newline(&Newline, window, cx);
29239    });
29240    cx.wait_for_autoindent_applied().await;
29241    cx.assert_editor_state(indoc! {"
29242        {
29243            ˇ
29244        }
29245    "});
29246
29247    cx.set_state(indoc! {"
29248        (ˇ)
29249    "});
29250    cx.update_editor(|editor, window, cx| {
29251        editor.newline(&Newline, window, cx);
29252    });
29253    cx.run_until_parked();
29254    cx.assert_editor_state(indoc! {"
29255        (
29256            ˇ
29257        )
29258    "});
29259
29260    // do not indent after empty lists or dictionaries
29261    cx.set_state(indoc! {"
29262        a = []ˇ
29263    "});
29264    cx.update_editor(|editor, window, cx| {
29265        editor.newline(&Newline, window, cx);
29266    });
29267    cx.run_until_parked();
29268    cx.assert_editor_state(indoc! {"
29269        a = []
29270        ˇ
29271    "});
29272}
29273
29274#[gpui::test]
29275async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
29276    init_test(cx, |_| {});
29277
29278    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
29279    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
29280    language_registry.add(markdown_lang());
29281    language_registry.add(python_lang);
29282
29283    let mut cx = EditorTestContext::new(cx).await;
29284    cx.update_buffer(|buffer, cx| {
29285        buffer.set_language_registry(language_registry);
29286        buffer.set_language(Some(markdown_lang()), cx);
29287    });
29288
29289    // Test that `else:` correctly outdents to match `if:` inside the Python code block
29290    cx.set_state(indoc! {"
29291        # Heading
29292
29293        ```python
29294        def main():
29295            if condition:
29296                pass
29297                ˇ
29298        ```
29299    "});
29300    cx.update_editor(|editor, window, cx| {
29301        editor.handle_input("else:", window, cx);
29302    });
29303    cx.run_until_parked();
29304    cx.assert_editor_state(indoc! {"
29305        # Heading
29306
29307        ```python
29308        def main():
29309            if condition:
29310                pass
29311            else:ˇ
29312        ```
29313    "});
29314}
29315
29316#[gpui::test]
29317async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
29318    init_test(cx, |_| {});
29319
29320    let mut cx = EditorTestContext::new(cx).await;
29321    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29322    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29323
29324    // test cursor move to start of each line on tab
29325    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
29326    cx.set_state(indoc! {"
29327        function main() {
29328        ˇ    for item in $items; do
29329        ˇ        while [ -n \"$item\" ]; do
29330        ˇ            if [ \"$value\" -gt 10 ]; then
29331        ˇ                continue
29332        ˇ            elif [ \"$value\" -lt 0 ]; then
29333        ˇ                break
29334        ˇ            else
29335        ˇ                echo \"$item\"
29336        ˇ            fi
29337        ˇ        done
29338        ˇ    done
29339        ˇ}
29340    "});
29341    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29342    cx.wait_for_autoindent_applied().await;
29343    cx.assert_editor_state(indoc! {"
29344        function main() {
29345            ˇfor item in $items; do
29346                ˇwhile [ -n \"$item\" ]; do
29347                    ˇif [ \"$value\" -gt 10 ]; then
29348                        ˇcontinue
29349                    ˇelif [ \"$value\" -lt 0 ]; then
29350                        ˇbreak
29351                    ˇelse
29352                        ˇecho \"$item\"
29353                    ˇfi
29354                ˇdone
29355            ˇdone
29356        ˇ}
29357    "});
29358    // test relative indent is preserved when tab
29359    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29360    cx.wait_for_autoindent_applied().await;
29361    cx.assert_editor_state(indoc! {"
29362        function main() {
29363                ˇfor item in $items; do
29364                    ˇwhile [ -n \"$item\" ]; do
29365                        ˇif [ \"$value\" -gt 10 ]; then
29366                            ˇcontinue
29367                        ˇelif [ \"$value\" -lt 0 ]; then
29368                            ˇbreak
29369                        ˇelse
29370                            ˇecho \"$item\"
29371                        ˇfi
29372                    ˇdone
29373                ˇdone
29374            ˇ}
29375    "});
29376
29377    // test cursor move to start of each line on tab
29378    // for `case` statement with patterns
29379    cx.set_state(indoc! {"
29380        function handle() {
29381        ˇ    case \"$1\" in
29382        ˇ        start)
29383        ˇ            echo \"a\"
29384        ˇ            ;;
29385        ˇ        stop)
29386        ˇ            echo \"b\"
29387        ˇ            ;;
29388        ˇ        *)
29389        ˇ            echo \"c\"
29390        ˇ            ;;
29391        ˇ    esac
29392        ˇ}
29393    "});
29394    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29395    cx.wait_for_autoindent_applied().await;
29396    cx.assert_editor_state(indoc! {"
29397        function handle() {
29398            ˇcase \"$1\" in
29399                ˇstart)
29400                    ˇecho \"a\"
29401                    ˇ;;
29402                ˇstop)
29403                    ˇecho \"b\"
29404                    ˇ;;
29405                ˇ*)
29406                    ˇecho \"c\"
29407                    ˇ;;
29408            ˇesac
29409        ˇ}
29410    "});
29411}
29412
29413#[gpui::test]
29414async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
29415    init_test(cx, |_| {});
29416
29417    let mut cx = EditorTestContext::new(cx).await;
29418    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29419    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29420
29421    // test indents on comment insert
29422    cx.set_state(indoc! {"
29423        function main() {
29424        ˇ    for item in $items; do
29425        ˇ        while [ -n \"$item\" ]; do
29426        ˇ            if [ \"$value\" -gt 10 ]; then
29427        ˇ                continue
29428        ˇ            elif [ \"$value\" -lt 0 ]; then
29429        ˇ                break
29430        ˇ            else
29431        ˇ                echo \"$item\"
29432        ˇ            fi
29433        ˇ        done
29434        ˇ    done
29435        ˇ}
29436    "});
29437    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
29438    cx.wait_for_autoindent_applied().await;
29439    cx.assert_editor_state(indoc! {"
29440        function main() {
29441        #ˇ    for item in $items; do
29442        #ˇ        while [ -n \"$item\" ]; do
29443        #ˇ            if [ \"$value\" -gt 10 ]; then
29444        #ˇ                continue
29445        #ˇ            elif [ \"$value\" -lt 0 ]; then
29446        #ˇ                break
29447        #ˇ            else
29448        #ˇ                echo \"$item\"
29449        #ˇ            fi
29450        #ˇ        done
29451        #ˇ    done
29452        #ˇ}
29453    "});
29454}
29455
29456#[gpui::test]
29457async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
29458    init_test(cx, |_| {});
29459
29460    let mut cx = EditorTestContext::new(cx).await;
29461    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29462    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29463
29464    // test `else` auto outdents when typed inside `if` block
29465    cx.set_state(indoc! {"
29466        if [ \"$1\" = \"test\" ]; then
29467            echo \"foo bar\"
29468            ˇ
29469    "});
29470    cx.update_editor(|editor, window, cx| {
29471        editor.handle_input("else", window, cx);
29472    });
29473    cx.wait_for_autoindent_applied().await;
29474    cx.assert_editor_state(indoc! {"
29475        if [ \"$1\" = \"test\" ]; then
29476            echo \"foo bar\"
29477        elseˇ
29478    "});
29479
29480    // test `elif` auto outdents when typed inside `if` block
29481    cx.set_state(indoc! {"
29482        if [ \"$1\" = \"test\" ]; then
29483            echo \"foo bar\"
29484            ˇ
29485    "});
29486    cx.update_editor(|editor, window, cx| {
29487        editor.handle_input("elif", window, cx);
29488    });
29489    cx.wait_for_autoindent_applied().await;
29490    cx.assert_editor_state(indoc! {"
29491        if [ \"$1\" = \"test\" ]; then
29492            echo \"foo bar\"
29493        elifˇ
29494    "});
29495
29496    // test `fi` auto outdents when typed inside `else` block
29497    cx.set_state(indoc! {"
29498        if [ \"$1\" = \"test\" ]; then
29499            echo \"foo bar\"
29500        else
29501            echo \"bar baz\"
29502            ˇ
29503    "});
29504    cx.update_editor(|editor, window, cx| {
29505        editor.handle_input("fi", window, cx);
29506    });
29507    cx.wait_for_autoindent_applied().await;
29508    cx.assert_editor_state(indoc! {"
29509        if [ \"$1\" = \"test\" ]; then
29510            echo \"foo bar\"
29511        else
29512            echo \"bar baz\"
29513        fiˇ
29514    "});
29515
29516    // test `done` auto outdents when typed inside `while` block
29517    cx.set_state(indoc! {"
29518        while read line; do
29519            echo \"$line\"
29520            ˇ
29521    "});
29522    cx.update_editor(|editor, window, cx| {
29523        editor.handle_input("done", window, cx);
29524    });
29525    cx.wait_for_autoindent_applied().await;
29526    cx.assert_editor_state(indoc! {"
29527        while read line; do
29528            echo \"$line\"
29529        doneˇ
29530    "});
29531
29532    // test `done` auto outdents when typed inside `for` block
29533    cx.set_state(indoc! {"
29534        for file in *.txt; do
29535            cat \"$file\"
29536            ˇ
29537    "});
29538    cx.update_editor(|editor, window, cx| {
29539        editor.handle_input("done", window, cx);
29540    });
29541    cx.wait_for_autoindent_applied().await;
29542    cx.assert_editor_state(indoc! {"
29543        for file in *.txt; do
29544            cat \"$file\"
29545        doneˇ
29546    "});
29547
29548    // test `esac` auto outdents when typed inside `case` block
29549    cx.set_state(indoc! {"
29550        case \"$1\" in
29551            start)
29552                echo \"foo bar\"
29553                ;;
29554            stop)
29555                echo \"bar baz\"
29556                ;;
29557            ˇ
29558    "});
29559    cx.update_editor(|editor, window, cx| {
29560        editor.handle_input("esac", window, cx);
29561    });
29562    cx.wait_for_autoindent_applied().await;
29563    cx.assert_editor_state(indoc! {"
29564        case \"$1\" in
29565            start)
29566                echo \"foo bar\"
29567                ;;
29568            stop)
29569                echo \"bar baz\"
29570                ;;
29571        esacˇ
29572    "});
29573
29574    // test `*)` auto outdents when typed inside `case` block
29575    cx.set_state(indoc! {"
29576        case \"$1\" in
29577            start)
29578                echo \"foo bar\"
29579                ;;
29580                ˇ
29581    "});
29582    cx.update_editor(|editor, window, cx| {
29583        editor.handle_input("*)", window, cx);
29584    });
29585    cx.wait_for_autoindent_applied().await;
29586    cx.assert_editor_state(indoc! {"
29587        case \"$1\" in
29588            start)
29589                echo \"foo bar\"
29590                ;;
29591            *)ˇ
29592    "});
29593
29594    // test `fi` outdents to correct level with nested if blocks
29595    cx.set_state(indoc! {"
29596        if [ \"$1\" = \"test\" ]; then
29597            echo \"outer if\"
29598            if [ \"$2\" = \"debug\" ]; then
29599                echo \"inner if\"
29600                ˇ
29601    "});
29602    cx.update_editor(|editor, window, cx| {
29603        editor.handle_input("fi", window, cx);
29604    });
29605    cx.wait_for_autoindent_applied().await;
29606    cx.assert_editor_state(indoc! {"
29607        if [ \"$1\" = \"test\" ]; then
29608            echo \"outer if\"
29609            if [ \"$2\" = \"debug\" ]; then
29610                echo \"inner if\"
29611            fiˇ
29612    "});
29613}
29614
29615#[gpui::test]
29616async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
29617    init_test(cx, |_| {});
29618    update_test_language_settings(cx, &|settings| {
29619        settings.defaults.extend_comment_on_newline = Some(false);
29620    });
29621    let mut cx = EditorTestContext::new(cx).await;
29622    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29623    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29624
29625    // test correct indent after newline on comment
29626    cx.set_state(indoc! {"
29627        # COMMENT:ˇ
29628    "});
29629    cx.update_editor(|editor, window, cx| {
29630        editor.newline(&Newline, window, cx);
29631    });
29632    cx.wait_for_autoindent_applied().await;
29633    cx.assert_editor_state(indoc! {"
29634        # COMMENT:
29635        ˇ
29636    "});
29637
29638    // test correct indent after newline after `then`
29639    cx.set_state(indoc! {"
29640
29641        if [ \"$1\" = \"test\" ]; thenˇ
29642    "});
29643    cx.update_editor(|editor, window, cx| {
29644        editor.newline(&Newline, window, cx);
29645    });
29646    cx.wait_for_autoindent_applied().await;
29647    cx.assert_editor_state(indoc! {"
29648
29649        if [ \"$1\" = \"test\" ]; then
29650            ˇ
29651    "});
29652
29653    // test correct indent after newline after `else`
29654    cx.set_state(indoc! {"
29655        if [ \"$1\" = \"test\" ]; then
29656        elseˇ
29657    "});
29658    cx.update_editor(|editor, window, cx| {
29659        editor.newline(&Newline, window, cx);
29660    });
29661    cx.wait_for_autoindent_applied().await;
29662    cx.assert_editor_state(indoc! {"
29663        if [ \"$1\" = \"test\" ]; then
29664        else
29665            ˇ
29666    "});
29667
29668    // test correct indent after newline after `elif`
29669    cx.set_state(indoc! {"
29670        if [ \"$1\" = \"test\" ]; then
29671        elifˇ
29672    "});
29673    cx.update_editor(|editor, window, cx| {
29674        editor.newline(&Newline, window, cx);
29675    });
29676    cx.wait_for_autoindent_applied().await;
29677    cx.assert_editor_state(indoc! {"
29678        if [ \"$1\" = \"test\" ]; then
29679        elif
29680            ˇ
29681    "});
29682
29683    // test correct indent after newline after `do`
29684    cx.set_state(indoc! {"
29685        for file in *.txt; doˇ
29686    "});
29687    cx.update_editor(|editor, window, cx| {
29688        editor.newline(&Newline, window, cx);
29689    });
29690    cx.wait_for_autoindent_applied().await;
29691    cx.assert_editor_state(indoc! {"
29692        for file in *.txt; do
29693            ˇ
29694    "});
29695
29696    // test correct indent after newline after case pattern
29697    cx.set_state(indoc! {"
29698        case \"$1\" in
29699            start)ˇ
29700    "});
29701    cx.update_editor(|editor, window, cx| {
29702        editor.newline(&Newline, window, cx);
29703    });
29704    cx.wait_for_autoindent_applied().await;
29705    cx.assert_editor_state(indoc! {"
29706        case \"$1\" in
29707            start)
29708                ˇ
29709    "});
29710
29711    // test correct indent after newline after case pattern
29712    cx.set_state(indoc! {"
29713        case \"$1\" in
29714            start)
29715                ;;
29716            *)ˇ
29717    "});
29718    cx.update_editor(|editor, window, cx| {
29719        editor.newline(&Newline, window, cx);
29720    });
29721    cx.wait_for_autoindent_applied().await;
29722    cx.assert_editor_state(indoc! {"
29723        case \"$1\" in
29724            start)
29725                ;;
29726            *)
29727                ˇ
29728    "});
29729
29730    // test correct indent after newline after function opening brace
29731    cx.set_state(indoc! {"
29732        function test() {ˇ}
29733    "});
29734    cx.update_editor(|editor, window, cx| {
29735        editor.newline(&Newline, window, cx);
29736    });
29737    cx.wait_for_autoindent_applied().await;
29738    cx.assert_editor_state(indoc! {"
29739        function test() {
29740            ˇ
29741        }
29742    "});
29743
29744    // test no extra indent after semicolon on same line
29745    cx.set_state(indoc! {"
29746        echo \"test\"29747    "});
29748    cx.update_editor(|editor, window, cx| {
29749        editor.newline(&Newline, window, cx);
29750    });
29751    cx.wait_for_autoindent_applied().await;
29752    cx.assert_editor_state(indoc! {"
29753        echo \"test\";
29754        ˇ
29755    "});
29756}
29757
29758fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
29759    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
29760    point..point
29761}
29762
29763#[track_caller]
29764fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
29765    let (text, ranges) = marked_text_ranges(marked_text, true);
29766    assert_eq!(editor.text(cx), text);
29767    assert_eq!(
29768        editor.selections.ranges(&editor.display_snapshot(cx)),
29769        ranges
29770            .iter()
29771            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
29772            .collect::<Vec<_>>(),
29773        "Assert selections are {}",
29774        marked_text
29775    );
29776}
29777
29778pub fn handle_signature_help_request(
29779    cx: &mut EditorLspTestContext,
29780    mocked_response: lsp::SignatureHelp,
29781) -> impl Future<Output = ()> + use<> {
29782    let mut request =
29783        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
29784            let mocked_response = mocked_response.clone();
29785            async move { Ok(Some(mocked_response)) }
29786        });
29787
29788    async move {
29789        request.next().await;
29790    }
29791}
29792
29793#[track_caller]
29794pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
29795    cx.update_editor(|editor, _, _| {
29796        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
29797            let entries = menu.entries.borrow();
29798            let entries = entries
29799                .iter()
29800                .map(|entry| entry.string.as_str())
29801                .collect::<Vec<_>>();
29802            assert_eq!(entries, expected);
29803        } else {
29804            panic!("Expected completions menu");
29805        }
29806    });
29807}
29808
29809#[gpui::test]
29810async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
29811    init_test(cx, |_| {});
29812    let mut cx = EditorLspTestContext::new_rust(
29813        lsp::ServerCapabilities {
29814            completion_provider: Some(lsp::CompletionOptions {
29815                ..Default::default()
29816            }),
29817            ..Default::default()
29818        },
29819        cx,
29820    )
29821    .await;
29822    cx.lsp
29823        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
29824            Ok(Some(lsp::CompletionResponse::Array(vec![
29825                lsp::CompletionItem {
29826                    label: "unsafe".into(),
29827                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
29828                        range: lsp::Range {
29829                            start: lsp::Position {
29830                                line: 0,
29831                                character: 9,
29832                            },
29833                            end: lsp::Position {
29834                                line: 0,
29835                                character: 11,
29836                            },
29837                        },
29838                        new_text: "unsafe".to_string(),
29839                    })),
29840                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
29841                    ..Default::default()
29842                },
29843            ])))
29844        });
29845
29846    cx.update_editor(|editor, _, cx| {
29847        editor.project().unwrap().update(cx, |project, cx| {
29848            project.snippets().update(cx, |snippets, _cx| {
29849                snippets.add_snippet_for_test(
29850                    None,
29851                    PathBuf::from("test_snippets.json"),
29852                    vec![
29853                        Arc::new(project::snippet_provider::Snippet {
29854                            prefix: vec![
29855                                "unlimited word count".to_string(),
29856                                "unlimit word count".to_string(),
29857                                "unlimited unknown".to_string(),
29858                            ],
29859                            body: "this is many words".to_string(),
29860                            description: Some("description".to_string()),
29861                            name: "multi-word snippet test".to_string(),
29862                        }),
29863                        Arc::new(project::snippet_provider::Snippet {
29864                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
29865                            body: "fewer words".to_string(),
29866                            description: Some("alt description".to_string()),
29867                            name: "other name".to_string(),
29868                        }),
29869                        Arc::new(project::snippet_provider::Snippet {
29870                            prefix: vec!["ab aa".to_string()],
29871                            body: "abcd".to_string(),
29872                            description: None,
29873                            name: "alphabet".to_string(),
29874                        }),
29875                    ],
29876                );
29877            });
29878        })
29879    });
29880
29881    let get_completions = |cx: &mut EditorLspTestContext| {
29882        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
29883            Some(CodeContextMenu::Completions(context_menu)) => {
29884                let entries = context_menu.entries.borrow();
29885                entries
29886                    .iter()
29887                    .map(|entry| entry.string.clone())
29888                    .collect_vec()
29889            }
29890            _ => vec![],
29891        })
29892    };
29893
29894    // snippets:
29895    //  @foo
29896    //  foo bar
29897    //
29898    // when typing:
29899    //
29900    // when typing:
29901    //  - if I type a symbol "open the completions with snippets only"
29902    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
29903    //
29904    // stuff we need:
29905    //  - filtering logic change?
29906    //  - remember how far back the completion started.
29907
29908    let test_cases: &[(&str, &[&str])] = &[
29909        (
29910            "un",
29911            &[
29912                "unsafe",
29913                "unlimit word count",
29914                "unlimited unknown",
29915                "unlimited word count",
29916                "unsnip",
29917            ],
29918        ),
29919        (
29920            "u ",
29921            &[
29922                "unlimit word count",
29923                "unlimited unknown",
29924                "unlimited word count",
29925            ],
29926        ),
29927        ("u a", &["ab aa", "unsafe"]), // unsAfe
29928        (
29929            "u u",
29930            &[
29931                "unsafe",
29932                "unlimit word count",
29933                "unlimited unknown", // ranked highest among snippets
29934                "unlimited word count",
29935                "unsnip",
29936            ],
29937        ),
29938        ("uw c", &["unlimit word count", "unlimited word count"]),
29939        (
29940            "u w",
29941            &[
29942                "unlimit word count",
29943                "unlimited word count",
29944                "unlimited unknown",
29945            ],
29946        ),
29947        ("u w ", &["unlimit word count", "unlimited word count"]),
29948        (
29949            "u ",
29950            &[
29951                "unlimit word count",
29952                "unlimited unknown",
29953                "unlimited word count",
29954            ],
29955        ),
29956        ("wor", &[]),
29957        ("uf", &["unsafe"]),
29958        ("af", &["unsafe"]),
29959        ("afu", &[]),
29960        (
29961            "ue",
29962            &["unsafe", "unlimited unknown", "unlimited word count"],
29963        ),
29964        ("@", &["@few"]),
29965        ("@few", &["@few"]),
29966        ("@ ", &[]),
29967        ("a@", &["@few"]),
29968        ("a@f", &["@few", "unsafe"]),
29969        ("a@fw", &["@few"]),
29970        ("a", &["ab aa", "unsafe"]),
29971        ("aa", &["ab aa"]),
29972        ("aaa", &["ab aa"]),
29973        ("ab", &["ab aa"]),
29974        ("ab ", &["ab aa"]),
29975        ("ab a", &["ab aa", "unsafe"]),
29976        ("ab ab", &["ab aa"]),
29977        ("ab ab aa", &["ab aa"]),
29978    ];
29979
29980    for &(input_to_simulate, expected_completions) in test_cases {
29981        cx.set_state("fn a() { ˇ }\n");
29982        for c in input_to_simulate.split("") {
29983            cx.simulate_input(c);
29984            cx.run_until_parked();
29985        }
29986        let expected_completions = expected_completions
29987            .iter()
29988            .map(|s| s.to_string())
29989            .collect_vec();
29990        assert_eq!(
29991            get_completions(&mut cx),
29992            expected_completions,
29993            "< actual / expected >, input = {input_to_simulate:?}",
29994        );
29995    }
29996}
29997
29998/// Handle completion request passing a marked string specifying where the completion
29999/// should be triggered from using '|' character, what range should be replaced, and what completions
30000/// should be returned using '<' and '>' to delimit the range.
30001///
30002/// Also see `handle_completion_request_with_insert_and_replace`.
30003#[track_caller]
30004pub fn handle_completion_request(
30005    marked_string: &str,
30006    completions: Vec<&'static str>,
30007    is_incomplete: bool,
30008    counter: Arc<AtomicUsize>,
30009    cx: &mut EditorLspTestContext,
30010) -> impl Future<Output = ()> {
30011    let complete_from_marker: TextRangeMarker = '|'.into();
30012    let replace_range_marker: TextRangeMarker = ('<', '>').into();
30013    let (_, mut marked_ranges) = marked_text_ranges_by(
30014        marked_string,
30015        vec![complete_from_marker.clone(), replace_range_marker.clone()],
30016    );
30017
30018    let complete_from_position = cx.to_lsp(MultiBufferOffset(
30019        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
30020    ));
30021    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
30022    let replace_range =
30023        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
30024
30025    let mut request =
30026        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
30027            let completions = completions.clone();
30028            counter.fetch_add(1, atomic::Ordering::Release);
30029            async move {
30030                assert_eq!(params.text_document_position.text_document.uri, url.clone());
30031                assert_eq!(
30032                    params.text_document_position.position,
30033                    complete_from_position
30034                );
30035                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
30036                    is_incomplete,
30037                    item_defaults: None,
30038                    items: completions
30039                        .iter()
30040                        .map(|completion_text| lsp::CompletionItem {
30041                            label: completion_text.to_string(),
30042                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
30043                                range: replace_range,
30044                                new_text: completion_text.to_string(),
30045                            })),
30046                            ..Default::default()
30047                        })
30048                        .collect(),
30049                })))
30050            }
30051        });
30052
30053    async move {
30054        request.next().await;
30055    }
30056}
30057
30058/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
30059/// given instead, which also contains an `insert` range.
30060///
30061/// This function uses markers to define ranges:
30062/// - `|` marks the cursor position
30063/// - `<>` marks the replace range
30064/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
30065pub fn handle_completion_request_with_insert_and_replace(
30066    cx: &mut EditorLspTestContext,
30067    marked_string: &str,
30068    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
30069    counter: Arc<AtomicUsize>,
30070) -> impl Future<Output = ()> {
30071    let complete_from_marker: TextRangeMarker = '|'.into();
30072    let replace_range_marker: TextRangeMarker = ('<', '>').into();
30073    let insert_range_marker: TextRangeMarker = ('{', '}').into();
30074
30075    let (_, mut marked_ranges) = marked_text_ranges_by(
30076        marked_string,
30077        vec![
30078            complete_from_marker.clone(),
30079            replace_range_marker.clone(),
30080            insert_range_marker.clone(),
30081        ],
30082    );
30083
30084    let complete_from_position = cx.to_lsp(MultiBufferOffset(
30085        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
30086    ));
30087    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
30088    let replace_range =
30089        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
30090
30091    let insert_range = match marked_ranges.remove(&insert_range_marker) {
30092        Some(ranges) if !ranges.is_empty() => {
30093            let range1 = ranges[0].clone();
30094            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
30095        }
30096        _ => lsp::Range {
30097            start: replace_range.start,
30098            end: complete_from_position,
30099        },
30100    };
30101
30102    let mut request =
30103        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
30104            let completions = completions.clone();
30105            counter.fetch_add(1, atomic::Ordering::Release);
30106            async move {
30107                assert_eq!(params.text_document_position.text_document.uri, url.clone());
30108                assert_eq!(
30109                    params.text_document_position.position, complete_from_position,
30110                    "marker `|` position doesn't match",
30111                );
30112                Ok(Some(lsp::CompletionResponse::Array(
30113                    completions
30114                        .iter()
30115                        .map(|(label, new_text)| lsp::CompletionItem {
30116                            label: label.to_string(),
30117                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
30118                                lsp::InsertReplaceEdit {
30119                                    insert: insert_range,
30120                                    replace: replace_range,
30121                                    new_text: new_text.to_string(),
30122                                },
30123                            )),
30124                            ..Default::default()
30125                        })
30126                        .collect(),
30127                )))
30128            }
30129        });
30130
30131    async move {
30132        request.next().await;
30133    }
30134}
30135
30136fn handle_resolve_completion_request(
30137    cx: &mut EditorLspTestContext,
30138    edits: Option<Vec<(&'static str, &'static str)>>,
30139) -> impl Future<Output = ()> {
30140    let edits = edits.map(|edits| {
30141        edits
30142            .iter()
30143            .map(|(marked_string, new_text)| {
30144                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
30145                let replace_range = cx.to_lsp_range(
30146                    MultiBufferOffset(marked_ranges[0].start)
30147                        ..MultiBufferOffset(marked_ranges[0].end),
30148                );
30149                lsp::TextEdit::new(replace_range, new_text.to_string())
30150            })
30151            .collect::<Vec<_>>()
30152    });
30153
30154    let mut request =
30155        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
30156            let edits = edits.clone();
30157            async move {
30158                Ok(lsp::CompletionItem {
30159                    additional_text_edits: edits,
30160                    ..Default::default()
30161                })
30162            }
30163        });
30164
30165    async move {
30166        request.next().await;
30167    }
30168}
30169
30170pub(crate) fn update_test_language_settings(
30171    cx: &mut TestAppContext,
30172    f: &dyn Fn(&mut AllLanguageSettingsContent),
30173) {
30174    cx.update(|cx| {
30175        SettingsStore::update_global(cx, |store, cx| {
30176            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
30177                f(&mut settings.project.all_languages)
30178            });
30179        });
30180    });
30181}
30182
30183pub(crate) fn update_test_project_settings(
30184    cx: &mut TestAppContext,
30185    f: &dyn Fn(&mut ProjectSettingsContent),
30186) {
30187    cx.update(|cx| {
30188        SettingsStore::update_global(cx, |store, cx| {
30189            store.update_user_settings(cx, |settings| f(&mut settings.project));
30190        });
30191    });
30192}
30193
30194pub(crate) fn update_test_editor_settings(
30195    cx: &mut TestAppContext,
30196    f: &dyn Fn(&mut EditorSettingsContent),
30197) {
30198    cx.update(|cx| {
30199        SettingsStore::update_global(cx, |store, cx| {
30200            store.update_user_settings(cx, |settings| f(&mut settings.editor));
30201        })
30202    })
30203}
30204
30205pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
30206    cx.update(|cx| {
30207        assets::Assets.load_test_fonts(cx);
30208        let store = SettingsStore::test(cx);
30209        cx.set_global(store);
30210        theme_settings::init(theme::LoadThemes::JustBase, cx);
30211        release_channel::init(semver::Version::new(0, 0, 0), cx);
30212        crate::init(cx);
30213    });
30214    zlog::init_test();
30215    update_test_language_settings(cx, &f);
30216}
30217
30218#[track_caller]
30219fn assert_hunk_revert(
30220    not_reverted_text_with_selections: &str,
30221    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
30222    expected_reverted_text_with_selections: &str,
30223    base_text: &str,
30224    cx: &mut EditorLspTestContext,
30225) {
30226    cx.set_state(not_reverted_text_with_selections);
30227    cx.set_head_text(base_text);
30228    cx.executor().run_until_parked();
30229
30230    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
30231        let snapshot = editor.snapshot(window, cx);
30232        let reverted_hunk_statuses = snapshot
30233            .buffer_snapshot()
30234            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
30235            .map(|hunk| hunk.status().kind)
30236            .collect::<Vec<_>>();
30237
30238        editor.git_restore(&Default::default(), window, cx);
30239        reverted_hunk_statuses
30240    });
30241    cx.executor().run_until_parked();
30242    cx.assert_editor_state(expected_reverted_text_with_selections);
30243    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
30244}
30245
30246#[gpui::test(iterations = 10)]
30247async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
30248    init_test(cx, |_| {});
30249
30250    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
30251    let counter = diagnostic_requests.clone();
30252
30253    let fs = FakeFs::new(cx.executor());
30254    fs.insert_tree(
30255        path!("/a"),
30256        json!({
30257            "first.rs": "fn main() { let a = 5; }",
30258            "second.rs": "// Test file",
30259        }),
30260    )
30261    .await;
30262
30263    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
30264    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30265    let workspace = window
30266        .read_with(cx, |mw, _| mw.workspace().clone())
30267        .unwrap();
30268    let cx = &mut VisualTestContext::from_window(*window, cx);
30269
30270    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30271    language_registry.add(rust_lang());
30272    let mut fake_servers = language_registry.register_fake_lsp(
30273        "Rust",
30274        FakeLspAdapter {
30275            capabilities: lsp::ServerCapabilities {
30276                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
30277                    lsp::DiagnosticOptions {
30278                        identifier: None,
30279                        inter_file_dependencies: true,
30280                        workspace_diagnostics: true,
30281                        work_done_progress_options: Default::default(),
30282                    },
30283                )),
30284                ..Default::default()
30285            },
30286            ..Default::default()
30287        },
30288    );
30289
30290    let editor = workspace
30291        .update_in(cx, |workspace, window, cx| {
30292            workspace.open_abs_path(
30293                PathBuf::from(path!("/a/first.rs")),
30294                OpenOptions::default(),
30295                window,
30296                cx,
30297            )
30298        })
30299        .await
30300        .unwrap()
30301        .downcast::<Editor>()
30302        .unwrap();
30303    let fake_server = fake_servers.next().await.unwrap();
30304    let server_id = fake_server.server.server_id();
30305    let mut first_request = fake_server
30306        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
30307            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
30308            let result_id = Some(new_result_id.to_string());
30309            assert_eq!(
30310                params.text_document.uri,
30311                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
30312            );
30313            async move {
30314                Ok(lsp::DocumentDiagnosticReportResult::Report(
30315                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
30316                        related_documents: None,
30317                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
30318                            items: Vec::new(),
30319                            result_id,
30320                        },
30321                    }),
30322                ))
30323            }
30324        });
30325
30326    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
30327        project.update(cx, |project, cx| {
30328            let buffer_id = editor
30329                .read(cx)
30330                .buffer()
30331                .read(cx)
30332                .as_singleton()
30333                .expect("created a singleton buffer")
30334                .read(cx)
30335                .remote_id();
30336            let buffer_result_id = project
30337                .lsp_store()
30338                .read(cx)
30339                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
30340            assert_eq!(expected_result_id, buffer_result_id);
30341        });
30342    };
30343
30344    ensure_result_id(None, cx);
30345    cx.executor().advance_clock(Duration::from_millis(60));
30346    cx.executor().run_until_parked();
30347    assert_eq!(
30348        diagnostic_requests.load(atomic::Ordering::Acquire),
30349        1,
30350        "Opening file should trigger diagnostic request"
30351    );
30352    first_request
30353        .next()
30354        .await
30355        .expect("should have sent the first diagnostics pull request");
30356    ensure_result_id(Some(SharedString::new_static("1")), cx);
30357
30358    // Editing should trigger diagnostics
30359    editor.update_in(cx, |editor, window, cx| {
30360        editor.handle_input("2", window, cx)
30361    });
30362    cx.executor().advance_clock(Duration::from_millis(60));
30363    cx.executor().run_until_parked();
30364    assert_eq!(
30365        diagnostic_requests.load(atomic::Ordering::Acquire),
30366        2,
30367        "Editing should trigger diagnostic request"
30368    );
30369    ensure_result_id(Some(SharedString::new_static("2")), cx);
30370
30371    // Moving cursor should not trigger diagnostic request
30372    editor.update_in(cx, |editor, window, cx| {
30373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30374            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
30375        });
30376    });
30377    cx.executor().advance_clock(Duration::from_millis(60));
30378    cx.executor().run_until_parked();
30379    assert_eq!(
30380        diagnostic_requests.load(atomic::Ordering::Acquire),
30381        2,
30382        "Cursor movement should not trigger diagnostic request"
30383    );
30384    ensure_result_id(Some(SharedString::new_static("2")), cx);
30385    // Multiple rapid edits should be debounced
30386    for _ in 0..5 {
30387        editor.update_in(cx, |editor, window, cx| {
30388            editor.handle_input("x", window, cx)
30389        });
30390    }
30391    cx.executor().advance_clock(Duration::from_millis(60));
30392    cx.executor().run_until_parked();
30393
30394    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
30395    assert!(
30396        final_requests <= 4,
30397        "Multiple rapid edits should be debounced (got {final_requests} requests)",
30398    );
30399    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
30400}
30401
30402#[gpui::test]
30403async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
30404    // Regression test for issue #11671
30405    // Previously, adding a cursor after moving multiple cursors would reset
30406    // the cursor count instead of adding to the existing cursors.
30407    init_test(cx, |_| {});
30408    let mut cx = EditorTestContext::new(cx).await;
30409
30410    // Create a simple buffer with cursor at start
30411    cx.set_state(indoc! {"
30412        ˇaaaa
30413        bbbb
30414        cccc
30415        dddd
30416        eeee
30417        ffff
30418        gggg
30419        hhhh"});
30420
30421    // Add 2 cursors below (so we have 3 total)
30422    cx.update_editor(|editor, window, cx| {
30423        editor.add_selection_below(&Default::default(), window, cx);
30424        editor.add_selection_below(&Default::default(), window, cx);
30425    });
30426
30427    // Verify we have 3 cursors
30428    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
30429    assert_eq!(
30430        initial_count, 3,
30431        "Should have 3 cursors after adding 2 below"
30432    );
30433
30434    // Move down one line
30435    cx.update_editor(|editor, window, cx| {
30436        editor.move_down(&MoveDown, window, cx);
30437    });
30438
30439    // Add another cursor below
30440    cx.update_editor(|editor, window, cx| {
30441        editor.add_selection_below(&Default::default(), window, cx);
30442    });
30443
30444    // Should now have 4 cursors (3 original + 1 new)
30445    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
30446    assert_eq!(
30447        final_count, 4,
30448        "Should have 4 cursors after moving and adding another"
30449    );
30450}
30451
30452#[gpui::test]
30453async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
30454    init_test(cx, |_| {});
30455
30456    let mut cx = EditorTestContext::new(cx).await;
30457
30458    cx.set_state(indoc!(
30459        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
30460           Second line here"#
30461    ));
30462
30463    cx.update_editor(|editor, window, cx| {
30464        // Enable soft wrapping with a narrow width to force soft wrapping and
30465        // confirm that more than 2 rows are being displayed.
30466        editor.set_wrap_width(Some(100.0.into()), cx);
30467        assert!(editor.display_text(cx).lines().count() > 2);
30468
30469        editor.add_selection_below(
30470            &AddSelectionBelow {
30471                skip_soft_wrap: true,
30472            },
30473            window,
30474            cx,
30475        );
30476
30477        assert_eq!(
30478            display_ranges(editor, cx),
30479            &[
30480                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
30481                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
30482            ]
30483        );
30484
30485        editor.add_selection_above(
30486            &AddSelectionAbove {
30487                skip_soft_wrap: true,
30488            },
30489            window,
30490            cx,
30491        );
30492
30493        assert_eq!(
30494            display_ranges(editor, cx),
30495            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
30496        );
30497
30498        editor.add_selection_below(
30499            &AddSelectionBelow {
30500                skip_soft_wrap: false,
30501            },
30502            window,
30503            cx,
30504        );
30505
30506        assert_eq!(
30507            display_ranges(editor, cx),
30508            &[
30509                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
30510                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
30511            ]
30512        );
30513
30514        editor.add_selection_above(
30515            &AddSelectionAbove {
30516                skip_soft_wrap: false,
30517            },
30518            window,
30519            cx,
30520        );
30521
30522        assert_eq!(
30523            display_ranges(editor, cx),
30524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
30525        );
30526    });
30527
30528    // Set up text where selections are in the middle of a soft-wrapped line.
30529    // When adding selection below with `skip_soft_wrap` set to `true`, the new
30530    // selection should be at the same buffer column, not the same pixel
30531    // position.
30532    cx.set_state(indoc!(
30533        r#"1. Very long line to show «howˇ» a wrapped line would look
30534           2. Very long line to show how a wrapped line would look"#
30535    ));
30536
30537    cx.update_editor(|editor, window, cx| {
30538        // Enable soft wrapping with a narrow width to force soft wrapping and
30539        // confirm that more than 2 rows are being displayed.
30540        editor.set_wrap_width(Some(100.0.into()), cx);
30541        assert!(editor.display_text(cx).lines().count() > 2);
30542
30543        editor.add_selection_below(
30544            &AddSelectionBelow {
30545                skip_soft_wrap: true,
30546            },
30547            window,
30548            cx,
30549        );
30550
30551        // Assert that there's now 2 selections, both selecting the same column
30552        // range in the buffer row.
30553        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
30554        let selections = editor.selections.all::<Point>(&display_map);
30555        assert_eq!(selections.len(), 2);
30556        assert_eq!(selections[0].start.column, selections[1].start.column);
30557        assert_eq!(selections[0].end.column, selections[1].end.column);
30558    });
30559}
30560
30561#[gpui::test]
30562async fn test_insert_snippet(cx: &mut TestAppContext) {
30563    init_test(cx, |_| {});
30564    let mut cx = EditorTestContext::new(cx).await;
30565
30566    cx.update_editor(|editor, _, cx| {
30567        editor.project().unwrap().update(cx, |project, cx| {
30568            project.snippets().update(cx, |snippets, _cx| {
30569                let snippet = project::snippet_provider::Snippet {
30570                    prefix: vec![], // no prefix needed!
30571                    body: "an Unspecified".to_string(),
30572                    description: Some("shhhh it's a secret".to_string()),
30573                    name: "super secret snippet".to_string(),
30574                };
30575                snippets.add_snippet_for_test(
30576                    None,
30577                    PathBuf::from("test_snippets.json"),
30578                    vec![Arc::new(snippet)],
30579                );
30580
30581                let snippet = project::snippet_provider::Snippet {
30582                    prefix: vec![], // no prefix needed!
30583                    body: " Location".to_string(),
30584                    description: Some("the word 'location'".to_string()),
30585                    name: "location word".to_string(),
30586                };
30587                snippets.add_snippet_for_test(
30588                    Some("Markdown".to_string()),
30589                    PathBuf::from("test_snippets.json"),
30590                    vec![Arc::new(snippet)],
30591                );
30592            });
30593        })
30594    });
30595
30596    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
30597
30598    cx.update_editor(|editor, window, cx| {
30599        editor.insert_snippet_at_selections(
30600            &InsertSnippet {
30601                language: None,
30602                name: Some("super secret snippet".to_string()),
30603                snippet: None,
30604            },
30605            window,
30606            cx,
30607        );
30608
30609        // Language is specified in the action,
30610        // so the buffer language does not need to match
30611        editor.insert_snippet_at_selections(
30612            &InsertSnippet {
30613                language: Some("Markdown".to_string()),
30614                name: Some("location word".to_string()),
30615                snippet: None,
30616            },
30617            window,
30618            cx,
30619        );
30620
30621        editor.insert_snippet_at_selections(
30622            &InsertSnippet {
30623                language: None,
30624                name: None,
30625                snippet: Some("$0 after".to_string()),
30626            },
30627            window,
30628            cx,
30629        );
30630    });
30631
30632    cx.assert_editor_state(
30633        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
30634    );
30635}
30636
30637#[gpui::test]
30638async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
30639    use crate::inlays::inlay_hints::InlayHintRefreshReason;
30640    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
30641    use settings::InlayHintSettingsContent;
30642    use std::sync::atomic::AtomicU32;
30643    use std::time::Duration;
30644
30645    const BASE_TIMEOUT_SECS: u64 = 1;
30646
30647    let request_count = Arc::new(AtomicU32::new(0));
30648    let closure_request_count = request_count.clone();
30649
30650    init_test(cx, &|settings| {
30651        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
30652            enabled: Some(true),
30653            ..InlayHintSettingsContent::default()
30654        })
30655    });
30656    cx.update(|cx| {
30657        SettingsStore::update_global(cx, |store, cx| {
30658            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
30659                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
30660                    request_timeout: Some(BASE_TIMEOUT_SECS),
30661                    button: Some(true),
30662                    notifications: None,
30663                    semantic_token_rules: None,
30664                });
30665            });
30666        });
30667    });
30668
30669    let fs = FakeFs::new(cx.executor());
30670    fs.insert_tree(
30671        path!("/a"),
30672        json!({
30673            "main.rs": "fn main() { let a = 5; }",
30674        }),
30675    )
30676    .await;
30677
30678    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
30679    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30680    language_registry.add(rust_lang());
30681    let mut fake_servers = language_registry.register_fake_lsp(
30682        "Rust",
30683        FakeLspAdapter {
30684            capabilities: lsp::ServerCapabilities {
30685                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30686                ..lsp::ServerCapabilities::default()
30687            },
30688            initializer: Some(Box::new(move |fake_server| {
30689                let request_count = closure_request_count.clone();
30690                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30691                    move |params, cx| {
30692                        let request_count = request_count.clone();
30693                        async move {
30694                            cx.background_executor()
30695                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
30696                                .await;
30697                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
30698                            assert_eq!(
30699                                params.text_document.uri,
30700                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
30701                            );
30702                            Ok(Some(vec![lsp::InlayHint {
30703                                position: lsp::Position::new(0, 1),
30704                                label: lsp::InlayHintLabel::String(count.to_string()),
30705                                kind: None,
30706                                text_edits: None,
30707                                tooltip: None,
30708                                padding_left: None,
30709                                padding_right: None,
30710                                data: None,
30711                            }]))
30712                        }
30713                    },
30714                );
30715            })),
30716            ..FakeLspAdapter::default()
30717        },
30718    );
30719
30720    let buffer = project
30721        .update(cx, |project, cx| {
30722            project.open_local_buffer(path!("/a/main.rs"), cx)
30723        })
30724        .await
30725        .unwrap();
30726    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
30727
30728    cx.executor().run_until_parked();
30729    let fake_server = fake_servers.next().await.unwrap();
30730
30731    cx.executor()
30732        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
30733    cx.executor().run_until_parked();
30734    editor
30735        .update(cx, |editor, _window, cx| {
30736            assert!(
30737                cached_hint_labels(editor, cx).is_empty(),
30738                "First request should time out, no hints cached"
30739            );
30740        })
30741        .unwrap();
30742
30743    editor
30744        .update(cx, |editor, _window, cx| {
30745            editor.refresh_inlay_hints(
30746                InlayHintRefreshReason::RefreshRequested {
30747                    server_id: fake_server.server.server_id(),
30748                    request_id: Some(1),
30749                },
30750                cx,
30751            );
30752        })
30753        .unwrap();
30754    cx.executor()
30755        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
30756    cx.executor().run_until_parked();
30757    editor
30758        .update(cx, |editor, _window, cx| {
30759            assert!(
30760                cached_hint_labels(editor, cx).is_empty(),
30761                "Second request should also time out with BASE_TIMEOUT, no hints cached"
30762            );
30763        })
30764        .unwrap();
30765
30766    cx.update(|cx| {
30767        SettingsStore::update_global(cx, |store, cx| {
30768            store.update_user_settings(cx, |settings| {
30769                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
30770                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
30771                    button: Some(true),
30772                    notifications: None,
30773                    semantic_token_rules: None,
30774                });
30775            });
30776        });
30777    });
30778    editor
30779        .update(cx, |editor, _window, cx| {
30780            editor.refresh_inlay_hints(
30781                InlayHintRefreshReason::RefreshRequested {
30782                    server_id: fake_server.server.server_id(),
30783                    request_id: Some(2),
30784                },
30785                cx,
30786            );
30787        })
30788        .unwrap();
30789    cx.executor()
30790        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
30791    cx.executor().run_until_parked();
30792    editor
30793        .update(cx, |editor, _window, cx| {
30794            assert_eq!(
30795                vec!["1".to_string()],
30796                cached_hint_labels(editor, cx),
30797                "With extended timeout (BASE * 4), hints should arrive successfully"
30798            );
30799            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
30800        })
30801        .unwrap();
30802}
30803
30804#[gpui::test]
30805async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
30806    init_test(cx, |_| {});
30807    let (editor, cx) = cx.add_window_view(Editor::single_line);
30808    editor.update_in(cx, |editor, window, cx| {
30809        editor.set_text("oops\n\nwow\n", window, cx)
30810    });
30811    cx.run_until_parked();
30812    editor.update(cx, |editor, cx| {
30813        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
30814    });
30815    editor.update(cx, |editor, cx| {
30816        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
30817    });
30818    cx.run_until_parked();
30819    editor.update(cx, |editor, cx| {
30820        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
30821    });
30822}
30823
30824#[gpui::test]
30825async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
30826    init_test(cx, |_| {});
30827
30828    cx.update(|cx| {
30829        register_project_item::<Editor>(cx);
30830    });
30831
30832    let fs = FakeFs::new(cx.executor());
30833    fs.insert_tree("/root1", json!({})).await;
30834    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
30835        .await;
30836
30837    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
30838    let (multi_workspace, cx) =
30839        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30840    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
30841
30842    let worktree_id = project.update(cx, |project, cx| {
30843        project.worktrees(cx).next().unwrap().read(cx).id()
30844    });
30845
30846    let handle = workspace
30847        .update_in(cx, |workspace, window, cx| {
30848            let project_path = (worktree_id, rel_path("one.pdf"));
30849            workspace.open_path(project_path, None, true, window, cx)
30850        })
30851        .await
30852        .unwrap();
30853    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
30854    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
30855    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
30856    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
30857}
30858
30859#[gpui::test]
30860async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
30861    init_test(cx, |_| {});
30862
30863    let language = Arc::new(Language::new(
30864        LanguageConfig::default(),
30865        Some(tree_sitter_rust::LANGUAGE.into()),
30866    ));
30867
30868    // Test hierarchical sibling navigation
30869    let text = r#"
30870        fn outer() {
30871            if condition {
30872                let a = 1;
30873            }
30874            let b = 2;
30875        }
30876
30877        fn another() {
30878            let c = 3;
30879        }
30880    "#;
30881
30882    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
30883    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30884    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
30885
30886    // Wait for parsing to complete
30887    editor
30888        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
30889        .await;
30890
30891    editor.update_in(cx, |editor, window, cx| {
30892        // Start by selecting "let a = 1;" inside the if block
30893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30894            s.select_display_ranges([
30895                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
30896            ]);
30897        });
30898
30899        let initial_selection = editor
30900            .selections
30901            .display_ranges(&editor.display_snapshot(cx));
30902        assert_eq!(initial_selection.len(), 1, "Should have one selection");
30903
30904        // Test select next sibling - should move up levels to find the next sibling
30905        // Since "let a = 1;" has no siblings in the if block, it should move up
30906        // to find "let b = 2;" which is a sibling of the if block
30907        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
30908        let next_selection = editor
30909            .selections
30910            .display_ranges(&editor.display_snapshot(cx));
30911
30912        // Should have a selection and it should be different from the initial
30913        assert_eq!(
30914            next_selection.len(),
30915            1,
30916            "Should have one selection after next"
30917        );
30918        assert_ne!(
30919            next_selection[0], initial_selection[0],
30920            "Next sibling selection should be different"
30921        );
30922
30923        // Test hierarchical navigation by going to the end of the current function
30924        // and trying to navigate to the next function
30925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30926            s.select_display_ranges([
30927                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
30928            ]);
30929        });
30930
30931        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
30932        let function_next_selection = editor
30933            .selections
30934            .display_ranges(&editor.display_snapshot(cx));
30935
30936        // Should move to the next function
30937        assert_eq!(
30938            function_next_selection.len(),
30939            1,
30940            "Should have one selection after function next"
30941        );
30942
30943        // Test select previous sibling navigation
30944        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
30945        let prev_selection = editor
30946            .selections
30947            .display_ranges(&editor.display_snapshot(cx));
30948
30949        // Should have a selection and it should be different
30950        assert_eq!(
30951            prev_selection.len(),
30952            1,
30953            "Should have one selection after prev"
30954        );
30955        assert_ne!(
30956            prev_selection[0], function_next_selection[0],
30957            "Previous sibling selection should be different from next"
30958        );
30959    });
30960}
30961
30962#[gpui::test]
30963async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
30964    init_test(cx, |_| {});
30965
30966    let mut cx = EditorTestContext::new(cx).await;
30967    cx.set_state(
30968        "let ˇvariable = 42;
30969let another = variable + 1;
30970let result = variable * 2;",
30971    );
30972
30973    // Set up document highlights manually (simulating LSP response)
30974    cx.update_editor(|editor, _window, cx| {
30975        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
30976
30977        // Create highlights for "variable" occurrences
30978        let highlight_ranges = [
30979            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
30980            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
30981            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
30982        ];
30983
30984        let anchor_ranges: Vec<_> = highlight_ranges
30985            .iter()
30986            .map(|range| range.clone().to_anchors(&buffer_snapshot))
30987            .collect();
30988
30989        editor.highlight_background(
30990            HighlightKey::DocumentHighlightRead,
30991            &anchor_ranges,
30992            |_, theme| theme.colors().editor_document_highlight_read_background,
30993            cx,
30994        );
30995    });
30996
30997    // Go to next highlight - should move to second "variable"
30998    cx.update_editor(|editor, window, cx| {
30999        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
31000    });
31001    cx.assert_editor_state(
31002        "let variable = 42;
31003let another = ˇvariable + 1;
31004let result = variable * 2;",
31005    );
31006
31007    // Go to next highlight - should move to third "variable"
31008    cx.update_editor(|editor, window, cx| {
31009        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
31010    });
31011    cx.assert_editor_state(
31012        "let variable = 42;
31013let another = variable + 1;
31014let result = ˇvariable * 2;",
31015    );
31016
31017    // Go to next highlight - should stay at third "variable" (no wrap-around)
31018    cx.update_editor(|editor, window, cx| {
31019        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
31020    });
31021    cx.assert_editor_state(
31022        "let variable = 42;
31023let another = variable + 1;
31024let result = ˇvariable * 2;",
31025    );
31026
31027    // Now test going backwards from third position
31028    cx.update_editor(|editor, window, cx| {
31029        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31030    });
31031    cx.assert_editor_state(
31032        "let variable = 42;
31033let another = ˇvariable + 1;
31034let result = variable * 2;",
31035    );
31036
31037    // Go to previous highlight - should move to first "variable"
31038    cx.update_editor(|editor, window, cx| {
31039        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31040    });
31041    cx.assert_editor_state(
31042        "let ˇvariable = 42;
31043let another = variable + 1;
31044let result = variable * 2;",
31045    );
31046
31047    // Go to previous highlight - should stay on first "variable"
31048    cx.update_editor(|editor, window, cx| {
31049        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31050    });
31051    cx.assert_editor_state(
31052        "let ˇvariable = 42;
31053let another = variable + 1;
31054let result = variable * 2;",
31055    );
31056}
31057
31058#[gpui::test]
31059async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
31060    cx: &mut gpui::TestAppContext,
31061) {
31062    init_test(cx, |_| {});
31063
31064    let url = "https://zed.dev";
31065
31066    let markdown_language = Arc::new(Language::new(
31067        LanguageConfig {
31068            name: "Markdown".into(),
31069            ..LanguageConfig::default()
31070        },
31071        None,
31072    ));
31073
31074    let mut cx = EditorTestContext::new(cx).await;
31075    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31076    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
31077
31078    cx.update_editor(|editor, window, cx| {
31079        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31080        editor.paste(&Paste, window, cx);
31081    });
31082
31083    cx.assert_editor_state(&format!(
31084        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
31085    ));
31086}
31087
31088#[gpui::test]
31089async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
31090    init_test(cx, |_| {});
31091
31092    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31093    let mut cx = EditorTestContext::new(cx).await;
31094
31095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31096
31097    // Case 1: Test if adding a character with multi cursors preserves nested list indents
31098    cx.set_state(&indoc! {"
31099        - [ ] Item 1
31100            - [ ] Item 1.a
31101        - [ˇ] Item 2
31102            - [ˇ] Item 2.a
31103            - [ˇ] Item 2.b
31104        "
31105    });
31106    cx.update_editor(|editor, window, cx| {
31107        editor.handle_input("x", window, cx);
31108    });
31109    cx.run_until_parked();
31110    cx.assert_editor_state(indoc! {"
31111        - [ ] Item 1
31112            - [ ] Item 1.a
31113        - [xˇ] Item 2
31114            - [xˇ] Item 2.a
31115            - [xˇ] Item 2.b
31116        "
31117    });
31118
31119    // Case 2: Test adding new line after nested list continues the list with unchecked task
31120    cx.set_state(&indoc! {"
31121        - [ ] Item 1
31122            - [ ] Item 1.a
31123        - [x] Item 2
31124            - [x] Item 2.a
31125            - [x] Item 2.bˇ"
31126    });
31127    cx.update_editor(|editor, window, cx| {
31128        editor.newline(&Newline, window, cx);
31129    });
31130    cx.assert_editor_state(indoc! {"
31131        - [ ] Item 1
31132            - [ ] Item 1.a
31133        - [x] Item 2
31134            - [x] Item 2.a
31135            - [x] Item 2.b
31136            - [ ] ˇ"
31137    });
31138
31139    // Case 3: Test adding content to continued list item
31140    cx.update_editor(|editor, window, cx| {
31141        editor.handle_input("Item 2.c", window, cx);
31142    });
31143    cx.run_until_parked();
31144    cx.assert_editor_state(indoc! {"
31145        - [ ] Item 1
31146            - [ ] Item 1.a
31147        - [x] Item 2
31148            - [x] Item 2.a
31149            - [x] Item 2.b
31150            - [ ] Item 2.cˇ"
31151    });
31152
31153    // Case 4: Test adding new line after nested ordered list continues with next number
31154    cx.set_state(indoc! {"
31155        1. Item 1
31156            1. Item 1.a
31157        2. Item 2
31158            1. Item 2.a
31159            2. Item 2.bˇ"
31160    });
31161    cx.update_editor(|editor, window, cx| {
31162        editor.newline(&Newline, window, cx);
31163    });
31164    cx.assert_editor_state(indoc! {"
31165        1. Item 1
31166            1. Item 1.a
31167        2. Item 2
31168            1. Item 2.a
31169            2. Item 2.b
31170            3. ˇ"
31171    });
31172
31173    // Case 5: Adding content to continued ordered list item
31174    cx.update_editor(|editor, window, cx| {
31175        editor.handle_input("Item 2.c", window, cx);
31176    });
31177    cx.run_until_parked();
31178    cx.assert_editor_state(indoc! {"
31179        1. Item 1
31180            1. Item 1.a
31181        2. Item 2
31182            1. Item 2.a
31183            2. Item 2.b
31184            3. Item 2.cˇ"
31185    });
31186
31187    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
31188    cx.set_state(indoc! {"
31189        - Item 1
31190            - Item 1.a
31191            - Item 1.a
31192        ˇ"});
31193    cx.update_editor(|editor, window, cx| {
31194        editor.handle_input("-", window, cx);
31195    });
31196    cx.run_until_parked();
31197    cx.assert_editor_state(indoc! {"
31198        - Item 1
31199            - Item 1.a
31200            - Item 1.a
31201"});
31202
31203    // Case 7: Test blockquote newline preserves something
31204    cx.set_state(indoc! {"
31205        > Item 1ˇ"
31206    });
31207    cx.update_editor(|editor, window, cx| {
31208        editor.newline(&Newline, window, cx);
31209    });
31210    cx.assert_editor_state(indoc! {"
31211        > Item 1
31212        ˇ"
31213    });
31214}
31215
31216#[gpui::test]
31217async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
31218    cx: &mut gpui::TestAppContext,
31219) {
31220    init_test(cx, |_| {});
31221
31222    let url = "https://zed.dev";
31223
31224    let markdown_language = Arc::new(Language::new(
31225        LanguageConfig {
31226            name: "Markdown".into(),
31227            ..LanguageConfig::default()
31228        },
31229        None,
31230    ));
31231
31232    let mut cx = EditorTestContext::new(cx).await;
31233    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31234    cx.set_state(&format!(
31235        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
31236    ));
31237
31238    cx.update_editor(|editor, window, cx| {
31239        editor.copy(&Copy, window, cx);
31240    });
31241
31242    cx.set_state(&format!(
31243        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
31244    ));
31245
31246    cx.update_editor(|editor, window, cx| {
31247        editor.paste(&Paste, window, cx);
31248    });
31249
31250    cx.assert_editor_state(&format!(
31251        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
31252    ));
31253}
31254
31255#[gpui::test]
31256async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
31257    cx: &mut gpui::TestAppContext,
31258) {
31259    init_test(cx, |_| {});
31260
31261    let url = "https://zed.dev";
31262
31263    let markdown_language = Arc::new(Language::new(
31264        LanguageConfig {
31265            name: "Markdown".into(),
31266            ..LanguageConfig::default()
31267        },
31268        None,
31269    ));
31270
31271    let mut cx = EditorTestContext::new(cx).await;
31272    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31273    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
31274
31275    cx.update_editor(|editor, window, cx| {
31276        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31277        editor.paste(&Paste, window, cx);
31278    });
31279
31280    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
31281}
31282
31283#[gpui::test]
31284async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
31285    cx: &mut gpui::TestAppContext,
31286) {
31287    init_test(cx, |_| {});
31288
31289    let text = "Awesome";
31290
31291    let markdown_language = Arc::new(Language::new(
31292        LanguageConfig {
31293            name: "Markdown".into(),
31294            ..LanguageConfig::default()
31295        },
31296        None,
31297    ));
31298
31299    let mut cx = EditorTestContext::new(cx).await;
31300    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31301    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
31302
31303    cx.update_editor(|editor, window, cx| {
31304        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
31305        editor.paste(&Paste, window, cx);
31306    });
31307
31308    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
31309}
31310
31311#[gpui::test]
31312async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
31313    cx: &mut gpui::TestAppContext,
31314) {
31315    init_test(cx, |_| {});
31316
31317    let url = "https://zed.dev";
31318
31319    let markdown_language = Arc::new(Language::new(
31320        LanguageConfig {
31321            name: "Rust".into(),
31322            ..LanguageConfig::default()
31323        },
31324        None,
31325    ));
31326
31327    let mut cx = EditorTestContext::new(cx).await;
31328    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31329    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
31330
31331    cx.update_editor(|editor, window, cx| {
31332        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31333        editor.paste(&Paste, window, cx);
31334    });
31335
31336    cx.assert_editor_state(&format!(
31337        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
31338    ));
31339}
31340
31341#[gpui::test]
31342async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
31343    cx: &mut TestAppContext,
31344) {
31345    init_test(cx, |_| {});
31346
31347    let url = "https://zed.dev";
31348
31349    let markdown_language = Arc::new(Language::new(
31350        LanguageConfig {
31351            name: "Markdown".into(),
31352            ..LanguageConfig::default()
31353        },
31354        None,
31355    ));
31356
31357    let (editor, cx) = cx.add_window_view(|window, cx| {
31358        let multi_buffer = MultiBuffer::build_multi(
31359            [
31360                ("this will embed -> link", vec![Point::row_range(0..1)]),
31361                ("this will replace -> link", vec![Point::row_range(0..1)]),
31362            ],
31363            cx,
31364        );
31365        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
31366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31367            s.select_ranges(vec![
31368                Point::new(0, 19)..Point::new(0, 23),
31369                Point::new(1, 21)..Point::new(1, 25),
31370            ])
31371        });
31372        let snapshot = multi_buffer.read(cx).snapshot(cx);
31373        let first_buffer_id = snapshot.all_buffer_ids().next().unwrap();
31374        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
31375        first_buffer.update(cx, |buffer, cx| {
31376            buffer.set_language(Some(markdown_language.clone()), cx);
31377        });
31378
31379        editor
31380    });
31381    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
31382
31383    cx.update_editor(|editor, window, cx| {
31384        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31385        editor.paste(&Paste, window, cx);
31386    });
31387
31388    cx.assert_editor_state(&format!(
31389        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
31390    ));
31391}
31392
31393#[gpui::test]
31394async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
31395    init_test(cx, |_| {});
31396
31397    let fs = FakeFs::new(cx.executor());
31398    fs.insert_tree(
31399        path!("/project"),
31400        json!({
31401            "first.rs": "# First Document\nSome content here.",
31402            "second.rs": "Plain text content for second file.",
31403        }),
31404    )
31405    .await;
31406
31407    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
31408    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31409    let cx = &mut VisualTestContext::from_window(*window, cx);
31410
31411    let language = rust_lang();
31412    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
31413    language_registry.add(language.clone());
31414    let mut fake_servers = language_registry.register_fake_lsp(
31415        "Rust",
31416        FakeLspAdapter {
31417            ..FakeLspAdapter::default()
31418        },
31419    );
31420
31421    let buffer1 = project
31422        .update(cx, |project, cx| {
31423            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
31424        })
31425        .await
31426        .unwrap();
31427    let buffer2 = project
31428        .update(cx, |project, cx| {
31429            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
31430        })
31431        .await
31432        .unwrap();
31433
31434    let multi_buffer = cx.new(|cx| {
31435        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
31436        multi_buffer.set_excerpts_for_path(
31437            PathKey::for_buffer(&buffer1, cx),
31438            buffer1.clone(),
31439            [Point::zero()..buffer1.read(cx).max_point()],
31440            3,
31441            cx,
31442        );
31443        multi_buffer.set_excerpts_for_path(
31444            PathKey::for_buffer(&buffer2, cx),
31445            buffer2.clone(),
31446            [Point::zero()..buffer1.read(cx).max_point()],
31447            3,
31448            cx,
31449        );
31450        multi_buffer
31451    });
31452
31453    let (editor, cx) = cx.add_window_view(|window, cx| {
31454        Editor::new(
31455            EditorMode::full(),
31456            multi_buffer,
31457            Some(project.clone()),
31458            window,
31459            cx,
31460        )
31461    });
31462
31463    let fake_language_server = fake_servers.next().await.unwrap();
31464
31465    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
31466
31467    let save = editor.update_in(cx, |editor, window, cx| {
31468        assert!(editor.is_dirty(cx));
31469
31470        editor.save(
31471            SaveOptions {
31472                format: true,
31473                autosave: true,
31474            },
31475            project,
31476            window,
31477            cx,
31478        )
31479    });
31480    let (start_edit_tx, start_edit_rx) = oneshot::channel();
31481    let (done_edit_tx, done_edit_rx) = oneshot::channel();
31482    let mut done_edit_rx = Some(done_edit_rx);
31483    let mut start_edit_tx = Some(start_edit_tx);
31484
31485    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
31486        start_edit_tx.take().unwrap().send(()).unwrap();
31487        let done_edit_rx = done_edit_rx.take().unwrap();
31488        async move {
31489            done_edit_rx.await.unwrap();
31490            Ok(None)
31491        }
31492    });
31493
31494    start_edit_rx.await.unwrap();
31495    buffer2
31496        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
31497        .unwrap();
31498
31499    done_edit_tx.send(()).unwrap();
31500
31501    save.await.unwrap();
31502    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
31503}
31504
31505#[gpui::test]
31506fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
31507    init_test(cx, |_| {});
31508
31509    let editor = cx.add_window(|window, cx| {
31510        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
31511        build_editor(buffer, window, cx)
31512    });
31513
31514    editor
31515        .update(cx, |editor, window, cx| {
31516            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31517                s.select_display_ranges([
31518                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
31519                ])
31520            });
31521
31522            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
31523
31524            assert_eq!(
31525                editor.display_text(cx),
31526                "line1\nline2\nline2",
31527                "Duplicating last line upward should create duplicate above, not on same line"
31528            );
31529
31530            assert_eq!(
31531                editor
31532                    .selections
31533                    .display_ranges(&editor.display_snapshot(cx)),
31534                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
31535                "Selection should move to the duplicated line"
31536            );
31537        })
31538        .unwrap();
31539}
31540
31541#[gpui::test]
31542async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
31543    init_test(cx, |_| {});
31544
31545    let mut cx = EditorTestContext::new(cx).await;
31546
31547    cx.set_state("line1\nline2ˇ");
31548
31549    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
31550
31551    let clipboard_text = cx
31552        .read_from_clipboard()
31553        .and_then(|item| item.text().as_deref().map(str::to_string));
31554
31555    assert_eq!(
31556        clipboard_text,
31557        Some("line2\n".to_string()),
31558        "Copying a line without trailing newline should include a newline"
31559    );
31560
31561    cx.set_state("line1\nˇ");
31562
31563    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31564
31565    cx.assert_editor_state("line1\nline2\nˇ");
31566}
31567
31568#[gpui::test]
31569async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
31570    init_test(cx, |_| {});
31571
31572    let mut cx = EditorTestContext::new(cx).await;
31573
31574    cx.set_state("ˇline1\nˇline2\nˇline3\n");
31575
31576    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
31577
31578    let clipboard_text = cx
31579        .read_from_clipboard()
31580        .and_then(|item| item.text().as_deref().map(str::to_string));
31581
31582    assert_eq!(
31583        clipboard_text,
31584        Some("line1\nline2\nline3\n".to_string()),
31585        "Copying multiple lines should include a single newline between lines"
31586    );
31587
31588    cx.set_state("lineA\nˇ");
31589
31590    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31591
31592    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
31593}
31594
31595#[gpui::test]
31596async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
31597    init_test(cx, |_| {});
31598
31599    let mut cx = EditorTestContext::new(cx).await;
31600
31601    cx.set_state("ˇline1\nˇline2\nˇline3\n");
31602
31603    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
31604
31605    let clipboard_text = cx
31606        .read_from_clipboard()
31607        .and_then(|item| item.text().as_deref().map(str::to_string));
31608
31609    assert_eq!(
31610        clipboard_text,
31611        Some("line1\nline2\nline3\n".to_string()),
31612        "Copying multiple lines should include a single newline between lines"
31613    );
31614
31615    cx.set_state("lineA\nˇ");
31616
31617    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31618
31619    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
31620}
31621
31622#[gpui::test]
31623async fn test_end_of_editor_context(cx: &mut TestAppContext) {
31624    init_test(cx, |_| {});
31625
31626    let mut cx = EditorTestContext::new(cx).await;
31627
31628    cx.set_state("line1\nline2ˇ");
31629    cx.update_editor(|e, window, cx| {
31630        e.set_mode(EditorMode::SingleLine);
31631        assert!(!e.key_context(window, cx).contains("start_of_input"));
31632        assert!(e.key_context(window, cx).contains("end_of_input"));
31633    });
31634    cx.set_state("ˇline1\nline2");
31635    cx.update_editor(|e, window, cx| {
31636        e.set_mode(EditorMode::SingleLine);
31637        assert!(e.key_context(window, cx).contains("start_of_input"));
31638        assert!(!e.key_context(window, cx).contains("end_of_input"));
31639    });
31640    cx.set_state("line1ˇ\nline2");
31641    cx.update_editor(|e, window, cx| {
31642        e.set_mode(EditorMode::SingleLine);
31643        assert!(!e.key_context(window, cx).contains("start_of_input"));
31644        assert!(!e.key_context(window, cx).contains("end_of_input"));
31645    });
31646
31647    cx.set_state("line1\nline2ˇ");
31648    cx.update_editor(|e, window, cx| {
31649        e.set_mode(EditorMode::AutoHeight {
31650            min_lines: 1,
31651            max_lines: Some(4),
31652        });
31653        assert!(!e.key_context(window, cx).contains("start_of_input"));
31654        assert!(e.key_context(window, cx).contains("end_of_input"));
31655    });
31656    cx.set_state("ˇline1\nline2");
31657    cx.update_editor(|e, window, cx| {
31658        e.set_mode(EditorMode::AutoHeight {
31659            min_lines: 1,
31660            max_lines: Some(4),
31661        });
31662        assert!(e.key_context(window, cx).contains("start_of_input"));
31663        assert!(!e.key_context(window, cx).contains("end_of_input"));
31664    });
31665    cx.set_state("line1ˇ\nline2");
31666    cx.update_editor(|e, window, cx| {
31667        e.set_mode(EditorMode::AutoHeight {
31668            min_lines: 1,
31669            max_lines: Some(4),
31670        });
31671        assert!(!e.key_context(window, cx).contains("start_of_input"));
31672        assert!(!e.key_context(window, cx).contains("end_of_input"));
31673    });
31674}
31675
31676#[gpui::test]
31677async fn test_sticky_scroll(cx: &mut TestAppContext) {
31678    init_test(cx, |_| {});
31679    let mut cx = EditorTestContext::new(cx).await;
31680
31681    let buffer = indoc! {"
31682            ˇfn foo() {
31683                let abc = 123;
31684            }
31685            struct Bar;
31686            impl Bar {
31687                fn new() -> Self {
31688                    Self
31689                }
31690            }
31691            fn baz() {
31692            }
31693        "};
31694    cx.set_state(&buffer);
31695
31696    cx.update_editor(|e, _, cx| {
31697        e.buffer()
31698            .read(cx)
31699            .as_singleton()
31700            .unwrap()
31701            .update(cx, |buffer, cx| {
31702                buffer.set_language(Some(rust_lang()), cx);
31703            })
31704    });
31705
31706    let mut sticky_headers = |offset: ScrollOffset| {
31707        cx.update_editor(|e, window, cx| {
31708            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31709        });
31710        cx.run_until_parked();
31711        cx.update_editor(|e, window, cx| {
31712            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31713                .into_iter()
31714                .map(
31715                    |StickyHeader {
31716                         start_point,
31717                         offset,
31718                         ..
31719                     }| { (start_point, offset) },
31720                )
31721                .collect::<Vec<_>>()
31722        })
31723    };
31724
31725    let fn_foo = Point { row: 0, column: 0 };
31726    let impl_bar = Point { row: 4, column: 0 };
31727    let fn_new = Point { row: 5, column: 4 };
31728
31729    assert_eq!(sticky_headers(0.0), vec![]);
31730    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
31731    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
31732    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
31733    assert_eq!(sticky_headers(2.0), vec![]);
31734    assert_eq!(sticky_headers(2.5), vec![]);
31735    assert_eq!(sticky_headers(3.0), vec![]);
31736    assert_eq!(sticky_headers(3.5), vec![]);
31737    assert_eq!(sticky_headers(4.0), vec![]);
31738    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
31739    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
31740    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
31741    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
31742    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
31743    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
31744    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
31745    assert_eq!(sticky_headers(8.0), vec![]);
31746    assert_eq!(sticky_headers(8.5), vec![]);
31747    assert_eq!(sticky_headers(9.0), vec![]);
31748    assert_eq!(sticky_headers(9.5), vec![]);
31749    assert_eq!(sticky_headers(10.0), vec![]);
31750}
31751
31752#[gpui::test]
31753async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(
31754    executor: BackgroundExecutor,
31755    cx: &mut TestAppContext,
31756) {
31757    init_test(cx, |_| {});
31758    let mut cx = EditorTestContext::new(cx).await;
31759
31760    let diff_base = indoc! {"
31761        fn foo() {
31762            let a = 1;
31763            let b = 2;
31764            let c = 3;
31765            let d = 4;
31766            let e = 5;
31767        }
31768    "};
31769
31770    let buffer = indoc! {"
31771        ˇfn foo() {
31772        }
31773    "};
31774
31775    cx.set_state(&buffer);
31776
31777    cx.update_editor(|e, _, cx| {
31778        e.buffer()
31779            .read(cx)
31780            .as_singleton()
31781            .unwrap()
31782            .update(cx, |buffer, cx| {
31783                buffer.set_language(Some(rust_lang()), cx);
31784            })
31785    });
31786
31787    cx.set_head_text(diff_base);
31788    executor.run_until_parked();
31789
31790    cx.update_editor(|editor, window, cx| {
31791        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
31792    });
31793    executor.run_until_parked();
31794
31795    // After expanding, the display should look like:
31796    //   row 0: fn foo() {
31797    //   row 1: -    let a = 1;   (deleted)
31798    //   row 2: -    let b = 2;   (deleted)
31799    //   row 3: -    let c = 3;   (deleted)
31800    //   row 4: -    let d = 4;   (deleted)
31801    //   row 5: -    let e = 5;   (deleted)
31802    //   row 6: }
31803    //
31804    // fn foo() spans display rows 0-6. Scrolling into the deleted region
31805    // (rows 1-5) should still show fn foo() as a sticky header.
31806
31807    let fn_foo = Point { row: 0, column: 0 };
31808
31809    let mut sticky_headers = |offset: ScrollOffset| {
31810        cx.update_editor(|e, window, cx| {
31811            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31812        });
31813        cx.run_until_parked();
31814        cx.update_editor(|e, window, cx| {
31815            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31816                .into_iter()
31817                .map(
31818                    |StickyHeader {
31819                         start_point,
31820                         offset,
31821                         ..
31822                     }| { (start_point, offset) },
31823                )
31824                .collect::<Vec<_>>()
31825        })
31826    };
31827
31828    assert_eq!(sticky_headers(0.0), vec![]);
31829    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
31830    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
31831    // Scrolling into deleted lines: fn foo() should still be a sticky header.
31832    assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]);
31833    assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]);
31834    assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]);
31835    assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]);
31836    assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]);
31837    // Past the closing brace: no more sticky header.
31838    assert_eq!(sticky_headers(6.0), vec![]);
31839}
31840
31841#[gpui::test]
31842async fn test_no_duplicated_sticky_headers(cx: &mut TestAppContext) {
31843    init_test(cx, |_| {});
31844    let mut cx = EditorTestContext::new(cx).await;
31845
31846    cx.set_state(indoc! {"
31847        ˇimpl Foo { fn bar() {
31848            let x = 1;
31849            fn baz() {
31850                let y = 2;
31851            }
31852        } }
31853    "});
31854
31855    cx.update_editor(|e, _, cx| {
31856        e.buffer()
31857            .read(cx)
31858            .as_singleton()
31859            .unwrap()
31860            .update(cx, |buffer, cx| {
31861                buffer.set_language(Some(rust_lang()), cx);
31862            })
31863    });
31864
31865    let mut sticky_headers = |offset: ScrollOffset| {
31866        cx.update_editor(|e, window, cx| {
31867            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31868        });
31869        cx.run_until_parked();
31870        cx.update_editor(|e, window, cx| {
31871            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31872                .into_iter()
31873                .map(
31874                    |StickyHeader {
31875                         start_point,
31876                         offset,
31877                         ..
31878                     }| { (start_point, offset) },
31879                )
31880                .collect::<Vec<_>>()
31881        })
31882    };
31883
31884    let struct_foo = Point { row: 0, column: 0 };
31885    let fn_baz = Point { row: 2, column: 4 };
31886
31887    assert_eq!(sticky_headers(0.0), vec![]);
31888    assert_eq!(sticky_headers(0.5), vec![(struct_foo, 0.0)]);
31889    assert_eq!(sticky_headers(1.0), vec![(struct_foo, 0.0)]);
31890    assert_eq!(sticky_headers(1.5), vec![(struct_foo, 0.0), (fn_baz, 1.0)]);
31891    assert_eq!(sticky_headers(2.0), vec![(struct_foo, 0.0), (fn_baz, 1.0)]);
31892    assert_eq!(sticky_headers(2.5), vec![(struct_foo, 0.0), (fn_baz, 0.5)]);
31893    assert_eq!(sticky_headers(3.0), vec![(struct_foo, 0.0)]);
31894    assert_eq!(sticky_headers(3.5), vec![(struct_foo, 0.0)]);
31895    assert_eq!(sticky_headers(4.0), vec![(struct_foo, 0.0)]);
31896    assert_eq!(sticky_headers(4.5), vec![(struct_foo, -0.5)]);
31897    assert_eq!(sticky_headers(5.0), vec![]);
31898}
31899
31900#[gpui::test]
31901fn test_relative_line_numbers(cx: &mut TestAppContext) {
31902    init_test(cx, |_| {});
31903
31904    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
31905    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
31906    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
31907
31908    let multibuffer = cx.new(|cx| {
31909        let mut multibuffer = MultiBuffer::new(ReadWrite);
31910        multibuffer.set_excerpts_for_path(
31911            PathKey::sorted(0),
31912            buffer_1.clone(),
31913            [Point::new(0, 0)..Point::new(2, 0)],
31914            0,
31915            cx,
31916        );
31917        multibuffer.set_excerpts_for_path(
31918            PathKey::sorted(1),
31919            buffer_2.clone(),
31920            [Point::new(0, 0)..Point::new(2, 0)],
31921            0,
31922            cx,
31923        );
31924        multibuffer.set_excerpts_for_path(
31925            PathKey::sorted(2),
31926            buffer_3.clone(),
31927            [Point::new(0, 0)..Point::new(2, 0)],
31928            0,
31929            cx,
31930        );
31931        multibuffer
31932    });
31933
31934    // wrapped contents of multibuffer:
31935    //    aaa
31936    //    aaa
31937    //    aaa
31938    //    a
31939    //    bbb
31940    //
31941    //    ccc
31942    //    ccc
31943    //    ccc
31944    //    c
31945    //    ddd
31946    //
31947    //    eee
31948    //    fff
31949    //    fff
31950    //    fff
31951    //    f
31952
31953    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
31954    _ = editor.update(cx, |editor, window, cx| {
31955        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
31956
31957        // includes trailing newlines.
31958        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
31959        let expected_wrapped_line_numbers = [
31960            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
31961        ];
31962
31963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31964            s.select_ranges([
31965                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
31966            ]);
31967        });
31968
31969        let snapshot = editor.snapshot(window, cx);
31970
31971        // these are all 0-indexed
31972        let base_display_row = DisplayRow(11);
31973        let base_row = 3;
31974        let wrapped_base_row = 7;
31975
31976        // test not counting wrapped lines
31977        let expected_relative_numbers = expected_line_numbers
31978            .into_iter()
31979            .enumerate()
31980            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
31981            .filter(|(_, relative_line_number)| *relative_line_number != 0)
31982            .collect_vec();
31983        let actual_relative_numbers = snapshot
31984            .calculate_relative_line_numbers(
31985                &(DisplayRow(0)..DisplayRow(24)),
31986                base_display_row,
31987                false,
31988            )
31989            .into_iter()
31990            .sorted()
31991            .collect_vec();
31992        assert_eq!(expected_relative_numbers, actual_relative_numbers);
31993        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
31994        for (display_row, relative_number) in expected_relative_numbers {
31995            assert_eq!(
31996                relative_number,
31997                snapshot
31998                    .relative_line_delta(display_row, base_display_row, false)
31999                    .unsigned_abs() as u32,
32000            );
32001        }
32002
32003        // test counting wrapped lines
32004        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
32005            .into_iter()
32006            .enumerate()
32007            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
32008            .filter(|(row, _)| *row != base_display_row)
32009            .collect_vec();
32010        let actual_relative_numbers = snapshot
32011            .calculate_relative_line_numbers(
32012                &(DisplayRow(0)..DisplayRow(24)),
32013                base_display_row,
32014                true,
32015            )
32016            .into_iter()
32017            .sorted()
32018            .collect_vec();
32019        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
32020        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
32021        for (display_row, relative_number) in expected_wrapped_relative_numbers {
32022            assert_eq!(
32023                relative_number,
32024                snapshot
32025                    .relative_line_delta(display_row, base_display_row, true)
32026                    .unsigned_abs() as u32,
32027            );
32028        }
32029    });
32030}
32031
32032#[gpui::test]
32033async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
32034    init_test(cx, |_| {});
32035    cx.update(|cx| {
32036        SettingsStore::update_global(cx, |store, cx| {
32037            store.update_user_settings(cx, |settings| {
32038                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
32039                    enabled: Some(true),
32040                })
32041            });
32042        });
32043    });
32044    let mut cx = EditorTestContext::new(cx).await;
32045
32046    let line_height = cx.update_editor(|editor, window, cx| {
32047        editor
32048            .style(cx)
32049            .text
32050            .line_height_in_pixels(window.rem_size())
32051    });
32052
32053    let buffer = indoc! {"
32054            ˇfn foo() {
32055                let abc = 123;
32056            }
32057            struct Bar;
32058            impl Bar {
32059                fn new() -> Self {
32060                    Self
32061                }
32062            }
32063            fn baz() {
32064            }
32065        "};
32066    cx.set_state(&buffer);
32067
32068    cx.update_editor(|e, _, cx| {
32069        e.buffer()
32070            .read(cx)
32071            .as_singleton()
32072            .unwrap()
32073            .update(cx, |buffer, cx| {
32074                buffer.set_language(Some(rust_lang()), cx);
32075            })
32076    });
32077
32078    let fn_foo = || empty_range(0, 0);
32079    let impl_bar = || empty_range(4, 0);
32080    let fn_new = || empty_range(5, 0);
32081
32082    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
32083        cx.update_editor(|e, window, cx| {
32084            e.scroll(
32085                gpui::Point {
32086                    x: 0.,
32087                    y: scroll_offset,
32088                },
32089                None,
32090                window,
32091                cx,
32092            );
32093        });
32094        cx.run_until_parked();
32095        cx.simulate_click(
32096            gpui::Point {
32097                x: px(0.),
32098                y: click_offset as f32 * line_height,
32099            },
32100            Modifiers::none(),
32101        );
32102        cx.run_until_parked();
32103        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
32104    };
32105    assert_eq!(
32106        scroll_and_click(
32107            4.5, // impl Bar is halfway off the screen
32108            0.0  // click top of screen
32109        ),
32110        // scrolled to impl Bar
32111        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32112    );
32113
32114    assert_eq!(
32115        scroll_and_click(
32116            4.5,  // impl Bar is halfway off the screen
32117            0.25  // click middle of impl Bar
32118        ),
32119        // scrolled to impl Bar
32120        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32121    );
32122
32123    assert_eq!(
32124        scroll_and_click(
32125            4.5, // impl Bar is halfway off the screen
32126            1.5  // click below impl Bar (e.g. fn new())
32127        ),
32128        // scrolled to fn new() - this is below the impl Bar header which has persisted
32129        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
32130    );
32131
32132    assert_eq!(
32133        scroll_and_click(
32134            5.5,  // fn new is halfway underneath impl Bar
32135            0.75  // click on the overlap of impl Bar and fn new()
32136        ),
32137        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32138    );
32139
32140    assert_eq!(
32141        scroll_and_click(
32142            5.5,  // fn new is halfway underneath impl Bar
32143            1.25  // click on the visible part of fn new()
32144        ),
32145        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
32146    );
32147
32148    assert_eq!(
32149        scroll_and_click(
32150            1.5, // fn foo is halfway off the screen
32151            0.0  // click top of screen
32152        ),
32153        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
32154    );
32155
32156    assert_eq!(
32157        scroll_and_click(
32158            1.5,  // fn foo is halfway off the screen
32159            0.75  // click visible part of let abc...
32160        )
32161        .0,
32162        // no change in scroll
32163        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
32164        (gpui::Point { x: 0., y: 1.5 })
32165    );
32166
32167    // Verify clicking at a specific x position within a sticky header places
32168    // the cursor at the corresponding column.
32169    let (text_origin_x, em_width) = cx.update_editor(|editor, _, _| {
32170        let position_map = editor.last_position_map.as_ref().unwrap();
32171        (
32172            position_map.text_hitbox.bounds.origin.x,
32173            position_map.em_layout_width,
32174        )
32175    });
32176
32177    // Click on "impl Bar {" sticky header at column 5 (the 'B' in 'Bar').
32178    // The text "impl Bar {" starts at column 0, so column 5 = 'B'.
32179    let click_x = text_origin_x + em_width * 5.5;
32180    cx.update_editor(|e, window, cx| {
32181        e.scroll(gpui::Point { x: 0., y: 4.5 }, None, window, cx);
32182    });
32183    cx.run_until_parked();
32184    cx.simulate_click(
32185        gpui::Point {
32186            x: click_x,
32187            y: 0.25 * line_height,
32188        },
32189        Modifiers::none(),
32190    );
32191    cx.run_until_parked();
32192    let (scroll_pos, selections) =
32193        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)));
32194    assert_eq!(scroll_pos, gpui::Point { x: 0., y: 4. });
32195    assert_eq!(selections, vec![empty_range(4, 5)]);
32196}
32197
32198#[gpui::test]
32199async fn test_clicking_sticky_header_sets_character_select_mode(cx: &mut TestAppContext) {
32200    init_test(cx, |_| {});
32201    cx.update(|cx| {
32202        SettingsStore::update_global(cx, |store, cx| {
32203            store.update_user_settings(cx, |settings| {
32204                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
32205                    enabled: Some(true),
32206                })
32207            });
32208        });
32209    });
32210    let mut cx = EditorTestContext::new(cx).await;
32211
32212    let line_height = cx.update_editor(|editor, window, cx| {
32213        editor
32214            .style(cx)
32215            .text
32216            .line_height_in_pixels(window.rem_size())
32217    });
32218
32219    let buffer = indoc! {"
32220            fn foo() {
32221                let abc = 123;
32222            }
32223            ˇstruct Bar;
32224        "};
32225    cx.set_state(&buffer);
32226
32227    cx.update_editor(|editor, _, cx| {
32228        editor
32229            .buffer()
32230            .read(cx)
32231            .as_singleton()
32232            .unwrap()
32233            .update(cx, |buffer, cx| {
32234                buffer.set_language(Some(rust_lang()), cx);
32235            })
32236    });
32237
32238    let text_origin_x = cx.update_editor(|editor, _, _| {
32239        editor
32240            .last_position_map
32241            .as_ref()
32242            .unwrap()
32243            .text_hitbox
32244            .bounds
32245            .origin
32246            .x
32247    });
32248
32249    cx.update_editor(|editor, window, cx| {
32250        // Double click on `struct` to select it
32251        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 1), false, 2, window, cx);
32252        editor.end_selection(window, cx);
32253
32254        // Scroll down one row to make `fn foo() {` a sticky header
32255        editor.scroll(gpui::Point { x: 0., y: 1. }, None, window, cx);
32256    });
32257    cx.run_until_parked();
32258
32259    // Click at the start of the `fn foo() {` sticky header
32260    cx.simulate_click(
32261        gpui::Point {
32262            x: text_origin_x,
32263            y: 0.5 * line_height,
32264        },
32265        Modifiers::none(),
32266    );
32267    cx.run_until_parked();
32268
32269    // Shift-click at the end of `fn foo() {` to select the whole row
32270    cx.update_editor(|editor, window, cx| {
32271        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
32272        editor.end_selection(window, cx);
32273    });
32274    cx.run_until_parked();
32275
32276    let selections = cx.update_editor(|editor, _, cx| display_ranges(editor, cx));
32277    assert_eq!(
32278        selections,
32279        vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 10)]
32280    );
32281}
32282
32283#[gpui::test]
32284async fn test_next_prev_reference(cx: &mut TestAppContext) {
32285    const CYCLE_POSITIONS: &[&'static str] = &[
32286        indoc! {"
32287            fn foo() {
32288                let ˇabc = 123;
32289                let x = abc + 1;
32290                let y = abc + 2;
32291                let z = abc + 2;
32292            }
32293        "},
32294        indoc! {"
32295            fn foo() {
32296                let abc = 123;
32297                let x = ˇabc + 1;
32298                let y = abc + 2;
32299                let z = abc + 2;
32300            }
32301        "},
32302        indoc! {"
32303            fn foo() {
32304                let abc = 123;
32305                let x = abc + 1;
32306                let y = ˇabc + 2;
32307                let z = abc + 2;
32308            }
32309        "},
32310        indoc! {"
32311            fn foo() {
32312                let abc = 123;
32313                let x = abc + 1;
32314                let y = abc + 2;
32315                let z = ˇabc + 2;
32316            }
32317        "},
32318    ];
32319
32320    init_test(cx, |_| {});
32321
32322    let mut cx = EditorLspTestContext::new_rust(
32323        lsp::ServerCapabilities {
32324            references_provider: Some(lsp::OneOf::Left(true)),
32325            ..Default::default()
32326        },
32327        cx,
32328    )
32329    .await;
32330
32331    // importantly, the cursor is in the middle
32332    cx.set_state(indoc! {"
32333        fn foo() {
32334            let aˇbc = 123;
32335            let x = abc + 1;
32336            let y = abc + 2;
32337            let z = abc + 2;
32338        }
32339    "});
32340
32341    let reference_ranges = [
32342        lsp::Position::new(1, 8),
32343        lsp::Position::new(2, 12),
32344        lsp::Position::new(3, 12),
32345        lsp::Position::new(4, 12),
32346    ]
32347    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
32348
32349    cx.lsp
32350        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
32351            Ok(Some(
32352                reference_ranges
32353                    .map(|range| lsp::Location {
32354                        uri: params.text_document_position.text_document.uri.clone(),
32355                        range,
32356                    })
32357                    .to_vec(),
32358            ))
32359        });
32360
32361    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
32362        cx.update_editor(|editor, window, cx| {
32363            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
32364        })
32365        .unwrap()
32366        .await
32367        .unwrap()
32368    };
32369
32370    _move(Direction::Next, 1, &mut cx).await;
32371    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32372
32373    _move(Direction::Next, 1, &mut cx).await;
32374    cx.assert_editor_state(CYCLE_POSITIONS[2]);
32375
32376    _move(Direction::Next, 1, &mut cx).await;
32377    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32378
32379    // loops back to the start
32380    _move(Direction::Next, 1, &mut cx).await;
32381    cx.assert_editor_state(CYCLE_POSITIONS[0]);
32382
32383    // loops back to the end
32384    _move(Direction::Prev, 1, &mut cx).await;
32385    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32386
32387    _move(Direction::Prev, 1, &mut cx).await;
32388    cx.assert_editor_state(CYCLE_POSITIONS[2]);
32389
32390    _move(Direction::Prev, 1, &mut cx).await;
32391    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32392
32393    _move(Direction::Prev, 1, &mut cx).await;
32394    cx.assert_editor_state(CYCLE_POSITIONS[0]);
32395
32396    _move(Direction::Next, 3, &mut cx).await;
32397    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32398
32399    _move(Direction::Prev, 2, &mut cx).await;
32400    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32401}
32402
32403#[gpui::test]
32404async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
32405    init_test(cx, |_| {});
32406
32407    let (editor, cx) = cx.add_window_view(|window, cx| {
32408        let multi_buffer = MultiBuffer::build_multi(
32409            [
32410                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32411                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32412            ],
32413            cx,
32414        );
32415        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32416    });
32417
32418    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
32419    let buffer_ids = cx.multibuffer(|mb, cx| {
32420        mb.snapshot(cx)
32421            .excerpts()
32422            .map(|excerpt| excerpt.context.start.buffer_id)
32423            .collect::<Vec<_>>()
32424    });
32425
32426    cx.assert_excerpts_with_selections(indoc! {"
32427        [EXCERPT]
32428        ˇ1
32429        2
32430        3
32431        [EXCERPT]
32432        1
32433        2
32434        3
32435        "});
32436
32437    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
32438    cx.update_editor(|editor, window, cx| {
32439        editor.change_selections(None.into(), window, cx, |s| {
32440            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32441        });
32442    });
32443    cx.assert_excerpts_with_selections(indoc! {"
32444        [EXCERPT]
32445        1
3244632447        3
32448        [EXCERPT]
32449        1
32450        2
32451        3
32452        "});
32453
32454    cx.update_editor(|editor, window, cx| {
32455        editor
32456            .select_all_matches(&SelectAllMatches, window, cx)
32457            .unwrap();
32458    });
32459    cx.assert_excerpts_with_selections(indoc! {"
32460        [EXCERPT]
32461        1
3246232463        3
32464        [EXCERPT]
32465        1
3246632467        3
32468        "});
32469
32470    cx.update_editor(|editor, window, cx| {
32471        editor.handle_input("X", window, cx);
32472    });
32473    cx.assert_excerpts_with_selections(indoc! {"
32474        [EXCERPT]
32475        1
3247632477        3
32478        [EXCERPT]
32479        1
3248032481        3
32482        "});
32483
32484    // Scenario 2: Select "2", then fold second buffer before insertion
32485    cx.update_multibuffer(|mb, cx| {
32486        for buffer_id in buffer_ids.iter() {
32487            let buffer = mb.buffer(*buffer_id).unwrap();
32488            buffer.update(cx, |buffer, cx| {
32489                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
32490            });
32491        }
32492    });
32493
32494    // Select "2" and select all matches
32495    cx.update_editor(|editor, window, cx| {
32496        editor.change_selections(None.into(), window, cx, |s| {
32497            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32498        });
32499        editor
32500            .select_all_matches(&SelectAllMatches, window, cx)
32501            .unwrap();
32502    });
32503
32504    // Fold second buffer - should remove selections from folded buffer
32505    cx.update_editor(|editor, _, cx| {
32506        editor.fold_buffer(buffer_ids[1], cx);
32507    });
32508    cx.assert_excerpts_with_selections(indoc! {"
32509        [EXCERPT]
32510        1
3251132512        3
32513        [EXCERPT]
32514        [FOLDED]
32515        "});
32516
32517    // Insert text - should only affect first buffer
32518    cx.update_editor(|editor, window, cx| {
32519        editor.handle_input("Y", window, cx);
32520    });
32521    cx.update_editor(|editor, _, cx| {
32522        editor.unfold_buffer(buffer_ids[1], cx);
32523    });
32524    cx.assert_excerpts_with_selections(indoc! {"
32525        [EXCERPT]
32526        1
3252732528        3
32529        [EXCERPT]
32530        1
32531        2
32532        3
32533        "});
32534
32535    // Scenario 3: Select "2", then fold first buffer before insertion
32536    cx.update_multibuffer(|mb, cx| {
32537        for buffer_id in buffer_ids.iter() {
32538            let buffer = mb.buffer(*buffer_id).unwrap();
32539            buffer.update(cx, |buffer, cx| {
32540                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
32541            });
32542        }
32543    });
32544
32545    // Select "2" and select all matches
32546    cx.update_editor(|editor, window, cx| {
32547        editor.change_selections(None.into(), window, cx, |s| {
32548            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32549        });
32550        editor
32551            .select_all_matches(&SelectAllMatches, window, cx)
32552            .unwrap();
32553    });
32554
32555    // Fold first buffer - should remove selections from folded buffer
32556    cx.update_editor(|editor, _, cx| {
32557        editor.fold_buffer(buffer_ids[0], cx);
32558    });
32559    cx.assert_excerpts_with_selections(indoc! {"
32560        [EXCERPT]
32561        [FOLDED]
32562        [EXCERPT]
32563        1
3256432565        3
32566        "});
32567
32568    // Insert text - should only affect second buffer
32569    cx.update_editor(|editor, window, cx| {
32570        editor.handle_input("Z", window, cx);
32571    });
32572    cx.update_editor(|editor, _, cx| {
32573        editor.unfold_buffer(buffer_ids[0], cx);
32574    });
32575    cx.assert_excerpts_with_selections(indoc! {"
32576        [EXCERPT]
32577        1
32578        2
32579        3
32580        [EXCERPT]
32581        1
3258232583        3
32584        "});
32585
32586    // Test correct folded header is selected upon fold
32587    cx.update_editor(|editor, _, cx| {
32588        editor.fold_buffer(buffer_ids[0], cx);
32589        editor.fold_buffer(buffer_ids[1], cx);
32590    });
32591    cx.assert_excerpts_with_selections(indoc! {"
32592        [EXCERPT]
32593        [FOLDED]
32594        [EXCERPT]
32595        ˇ[FOLDED]
32596        "});
32597
32598    // Test selection inside folded buffer unfolds it on type
32599    cx.update_editor(|editor, window, cx| {
32600        editor.handle_input("W", window, cx);
32601    });
32602    cx.update_editor(|editor, _, cx| {
32603        editor.unfold_buffer(buffer_ids[0], cx);
32604    });
32605    cx.assert_excerpts_with_selections(indoc! {"
32606        [EXCERPT]
32607        1
32608        2
32609        3
32610        [EXCERPT]
32611        Wˇ1
32612        Z
32613        3
32614        "});
32615}
32616
32617#[gpui::test]
32618async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
32619    init_test(cx, |_| {});
32620
32621    let (editor, cx) = cx.add_window_view(|window, cx| {
32622        let multi_buffer = MultiBuffer::build_multi(
32623            [
32624                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32625                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
32626            ],
32627            cx,
32628        );
32629        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32630    });
32631
32632    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
32633
32634    cx.assert_excerpts_with_selections(indoc! {"
32635        [EXCERPT]
32636        ˇ1
32637        2
32638        3
32639        [EXCERPT]
32640        1
32641        2
32642        3
32643        4
32644        5
32645        6
32646        7
32647        8
32648        9
32649        "});
32650
32651    cx.update_editor(|editor, window, cx| {
32652        editor.change_selections(None.into(), window, cx, |s| {
32653            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
32654        });
32655    });
32656
32657    cx.assert_excerpts_with_selections(indoc! {"
32658        [EXCERPT]
32659        1
32660        2
32661        3
32662        [EXCERPT]
32663        1
32664        2
32665        3
32666        4
32667        5
32668        6
32669        ˇ7
32670        8
32671        9
32672        "});
32673
32674    cx.update_editor(|editor, _window, cx| {
32675        editor.set_vertical_scroll_margin(0, cx);
32676    });
32677
32678    cx.update_editor(|editor, window, cx| {
32679        assert_eq!(editor.vertical_scroll_margin(), 0);
32680        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
32681        assert_eq!(
32682            editor.snapshot(window, cx).scroll_position(),
32683            gpui::Point::new(0., 12.0)
32684        );
32685    });
32686
32687    cx.update_editor(|editor, _window, cx| {
32688        editor.set_vertical_scroll_margin(3, cx);
32689    });
32690
32691    cx.update_editor(|editor, window, cx| {
32692        assert_eq!(editor.vertical_scroll_margin(), 3);
32693        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
32694        assert_eq!(
32695            editor.snapshot(window, cx).scroll_position(),
32696            gpui::Point::new(0., 9.0)
32697        );
32698    });
32699}
32700
32701#[gpui::test]
32702async fn test_find_references_single_case(cx: &mut TestAppContext) {
32703    init_test(cx, |_| {});
32704    let mut cx = EditorLspTestContext::new_rust(
32705        lsp::ServerCapabilities {
32706            references_provider: Some(lsp::OneOf::Left(true)),
32707            ..lsp::ServerCapabilities::default()
32708        },
32709        cx,
32710    )
32711    .await;
32712
32713    let before = indoc!(
32714        r#"
32715        fn main() {
32716            let aˇbc = 123;
32717            let xyz = abc;
32718        }
32719        "#
32720    );
32721    let after = indoc!(
32722        r#"
32723        fn main() {
32724            let abc = 123;
32725            let xyz = ˇabc;
32726        }
32727        "#
32728    );
32729
32730    cx.lsp
32731        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
32732            Ok(Some(vec![
32733                lsp::Location {
32734                    uri: params.text_document_position.text_document.uri.clone(),
32735                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
32736                },
32737                lsp::Location {
32738                    uri: params.text_document_position.text_document.uri,
32739                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
32740                },
32741            ]))
32742        });
32743
32744    cx.set_state(before);
32745
32746    let action = FindAllReferences {
32747        always_open_multibuffer: false,
32748    };
32749
32750    let navigated = cx
32751        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
32752        .expect("should have spawned a task")
32753        .await
32754        .unwrap();
32755
32756    assert_eq!(navigated, Navigated::No);
32757
32758    cx.run_until_parked();
32759
32760    cx.assert_editor_state(after);
32761}
32762
32763#[gpui::test]
32764async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
32765    init_test(cx, |settings| {
32766        settings.defaults.tab_size = Some(2.try_into().unwrap());
32767    });
32768
32769    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
32770    let mut cx = EditorTestContext::new(cx).await;
32771    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
32772
32773    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
32774    cx.set_state(indoc! {"
32775        - [ ] taskˇ
32776    "});
32777    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32778    cx.wait_for_autoindent_applied().await;
32779    cx.assert_editor_state(indoc! {"
32780        - [ ] task
32781        - [ ] ˇ
32782    "});
32783
32784    // Case 2: Works with checked task items too
32785    cx.set_state(indoc! {"
32786        - [x] completed taskˇ
32787    "});
32788    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32789    cx.wait_for_autoindent_applied().await;
32790    cx.assert_editor_state(indoc! {"
32791        - [x] completed task
32792        - [ ] ˇ
32793    "});
32794
32795    // Case 2.1: Works with uppercase checked marker too
32796    cx.set_state(indoc! {"
32797        - [X] completed taskˇ
32798    "});
32799    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32800    cx.wait_for_autoindent_applied().await;
32801    cx.assert_editor_state(indoc! {"
32802        - [X] completed task
32803        - [ ] ˇ
32804    "});
32805
32806    // Case 3: Cursor position doesn't matter - content after marker is what counts
32807    cx.set_state(indoc! {"
32808        - [ ] taˇsk
32809    "});
32810    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32811    cx.wait_for_autoindent_applied().await;
32812    cx.assert_editor_state(indoc! {"
32813        - [ ] ta
32814        - [ ] ˇsk
32815    "});
32816
32817    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
32818    cx.set_state(indoc! {"
32819        - [ ]  ˇ
32820    "});
32821    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32822    cx.wait_for_autoindent_applied().await;
32823    cx.assert_editor_state(
32824        indoc! {"
32825        - [ ]$$
32826        ˇ
32827    "}
32828        .replace("$", " ")
32829        .as_str(),
32830    );
32831
32832    // Case 5: Adding newline with content adds marker preserving indentation
32833    cx.set_state(indoc! {"
32834        - [ ] task
32835          - [ ] indentedˇ
32836    "});
32837    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32838    cx.wait_for_autoindent_applied().await;
32839    cx.assert_editor_state(indoc! {"
32840        - [ ] task
32841          - [ ] indented
32842          - [ ] ˇ
32843    "});
32844
32845    // Case 6: Adding newline with cursor right after prefix, unindents
32846    cx.set_state(indoc! {"
32847        - [ ] task
32848          - [ ] sub task
32849            - [ ] ˇ
32850    "});
32851    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32852    cx.wait_for_autoindent_applied().await;
32853    cx.assert_editor_state(indoc! {"
32854        - [ ] task
32855          - [ ] sub task
32856          - [ ] ˇ
32857    "});
32858    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32859    cx.wait_for_autoindent_applied().await;
32860
32861    // Case 7: Adding newline with cursor right after prefix, removes marker
32862    cx.assert_editor_state(indoc! {"
32863        - [ ] task
32864          - [ ] sub task
32865        - [ ] ˇ
32866    "});
32867    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32868    cx.wait_for_autoindent_applied().await;
32869    cx.assert_editor_state(indoc! {"
32870        - [ ] task
32871          - [ ] sub task
32872        ˇ
32873    "});
32874
32875    // Case 8: Cursor before or inside prefix does not add marker
32876    cx.set_state(indoc! {"
32877        ˇ- [ ] task
32878    "});
32879    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32880    cx.wait_for_autoindent_applied().await;
32881    cx.assert_editor_state(indoc! {"
32882
32883        ˇ- [ ] task
32884    "});
32885
32886    cx.set_state(indoc! {"
32887        - [ˇ ] task
32888    "});
32889    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32890    cx.wait_for_autoindent_applied().await;
32891    cx.assert_editor_state(indoc! {"
32892        - [
32893        ˇ
32894        ] task
32895    "});
32896}
32897
32898#[gpui::test]
32899async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
32900    init_test(cx, |settings| {
32901        settings.defaults.tab_size = Some(2.try_into().unwrap());
32902    });
32903
32904    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
32905    let mut cx = EditorTestContext::new(cx).await;
32906    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
32907
32908    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
32909    cx.set_state(indoc! {"
32910        - itemˇ
32911    "});
32912    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32913    cx.wait_for_autoindent_applied().await;
32914    cx.assert_editor_state(indoc! {"
32915        - item
32916        - ˇ
32917    "});
32918
32919    // Case 2: Works with different markers
32920    cx.set_state(indoc! {"
32921        * starred itemˇ
32922    "});
32923    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32924    cx.wait_for_autoindent_applied().await;
32925    cx.assert_editor_state(indoc! {"
32926        * starred item
32927        * ˇ
32928    "});
32929
32930    cx.set_state(indoc! {"
32931        + plus itemˇ
32932    "});
32933    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32934    cx.wait_for_autoindent_applied().await;
32935    cx.assert_editor_state(indoc! {"
32936        + plus item
32937        + ˇ
32938    "});
32939
32940    // Case 3: Cursor position doesn't matter - content after marker is what counts
32941    cx.set_state(indoc! {"
32942        - itˇem
32943    "});
32944    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32945    cx.wait_for_autoindent_applied().await;
32946    cx.assert_editor_state(indoc! {"
32947        - it
32948        - ˇem
32949    "});
32950
32951    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
32952    cx.set_state(indoc! {"
32953        -  ˇ
32954    "});
32955    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32956    cx.wait_for_autoindent_applied().await;
32957    cx.assert_editor_state(
32958        indoc! {"
32959        - $
32960        ˇ
32961    "}
32962        .replace("$", " ")
32963        .as_str(),
32964    );
32965
32966    // Case 5: Adding newline with content adds marker preserving indentation
32967    cx.set_state(indoc! {"
32968        - item
32969          - indentedˇ
32970    "});
32971    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32972    cx.wait_for_autoindent_applied().await;
32973    cx.assert_editor_state(indoc! {"
32974        - item
32975          - indented
32976          - ˇ
32977    "});
32978
32979    // Case 6: Adding newline with cursor right after marker, unindents
32980    cx.set_state(indoc! {"
32981        - item
32982          - sub item
32983            - ˇ
32984    "});
32985    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32986    cx.wait_for_autoindent_applied().await;
32987    cx.assert_editor_state(indoc! {"
32988        - item
32989          - sub item
32990          - ˇ
32991    "});
32992    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32993    cx.wait_for_autoindent_applied().await;
32994
32995    // Case 7: Adding newline with cursor right after marker, removes marker
32996    cx.assert_editor_state(indoc! {"
32997        - item
32998          - sub item
32999        - ˇ
33000    "});
33001    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33002    cx.wait_for_autoindent_applied().await;
33003    cx.assert_editor_state(indoc! {"
33004        - item
33005          - sub item
33006        ˇ
33007    "});
33008
33009    // Case 8: Cursor before or inside prefix does not add marker
33010    cx.set_state(indoc! {"
33011        ˇ- item
33012    "});
33013    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33014    cx.wait_for_autoindent_applied().await;
33015    cx.assert_editor_state(indoc! {"
33016
33017        ˇ- item
33018    "});
33019
33020    cx.set_state(indoc! {"
33021        -ˇ item
33022    "});
33023    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33024    cx.wait_for_autoindent_applied().await;
33025    cx.assert_editor_state(indoc! {"
33026        -
33027        ˇitem
33028    "});
33029}
33030
33031#[gpui::test]
33032async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
33033    init_test(cx, |settings| {
33034        settings.defaults.tab_size = Some(2.try_into().unwrap());
33035    });
33036
33037    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33038    let mut cx = EditorTestContext::new(cx).await;
33039    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33040
33041    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
33042    cx.set_state(indoc! {"
33043        1. first itemˇ
33044    "});
33045    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33046    cx.wait_for_autoindent_applied().await;
33047    cx.assert_editor_state(indoc! {"
33048        1. first item
33049        2. ˇ
33050    "});
33051
33052    // Case 2: Works with larger numbers
33053    cx.set_state(indoc! {"
33054        10. tenth itemˇ
33055    "});
33056    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33057    cx.wait_for_autoindent_applied().await;
33058    cx.assert_editor_state(indoc! {"
33059        10. tenth item
33060        11. ˇ
33061    "});
33062
33063    // Case 3: Cursor position doesn't matter - content after marker is what counts
33064    cx.set_state(indoc! {"
33065        1. itˇem
33066    "});
33067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33068    cx.wait_for_autoindent_applied().await;
33069    cx.assert_editor_state(indoc! {"
33070        1. it
33071        2. ˇem
33072    "});
33073
33074    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
33075    cx.set_state(indoc! {"
33076        1.  ˇ
33077    "});
33078    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33079    cx.wait_for_autoindent_applied().await;
33080    cx.assert_editor_state(
33081        indoc! {"
33082        1. $
33083        ˇ
33084    "}
33085        .replace("$", " ")
33086        .as_str(),
33087    );
33088
33089    // Case 5: Adding newline with content adds marker preserving indentation
33090    cx.set_state(indoc! {"
33091        1. item
33092          2. indentedˇ
33093    "});
33094    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33095    cx.wait_for_autoindent_applied().await;
33096    cx.assert_editor_state(indoc! {"
33097        1. item
33098          2. indented
33099          3. ˇ
33100    "});
33101
33102    // Case 6: Adding newline with cursor right after marker, unindents
33103    cx.set_state(indoc! {"
33104        1. item
33105          2. sub item
33106            3. ˇ
33107    "});
33108    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33109    cx.wait_for_autoindent_applied().await;
33110    cx.assert_editor_state(indoc! {"
33111        1. item
33112          2. sub item
33113          1. ˇ
33114    "});
33115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33116    cx.wait_for_autoindent_applied().await;
33117
33118    // Case 7: Adding newline with cursor right after marker, removes marker
33119    cx.assert_editor_state(indoc! {"
33120        1. item
33121          2. sub item
33122        1. ˇ
33123    "});
33124    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33125    cx.wait_for_autoindent_applied().await;
33126    cx.assert_editor_state(indoc! {"
33127        1. item
33128          2. sub item
33129        ˇ
33130    "});
33131
33132    // Case 8: Cursor before or inside prefix does not add marker
33133    cx.set_state(indoc! {"
33134        ˇ1. item
33135    "});
33136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33137    cx.wait_for_autoindent_applied().await;
33138    cx.assert_editor_state(indoc! {"
33139
33140        ˇ1. item
33141    "});
33142
33143    cx.set_state(indoc! {"
33144        1ˇ. item
33145    "});
33146    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33147    cx.wait_for_autoindent_applied().await;
33148    cx.assert_editor_state(indoc! {"
33149        1
33150        ˇ. item
33151    "});
33152}
33153
33154#[gpui::test]
33155async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
33156    init_test(cx, |settings| {
33157        settings.defaults.tab_size = Some(2.try_into().unwrap());
33158    });
33159
33160    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33161    let mut cx = EditorTestContext::new(cx).await;
33162    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33163
33164    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
33165    cx.set_state(indoc! {"
33166        1. first item
33167          1. sub first item
33168          2. sub second item
33169          3. ˇ
33170    "});
33171    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33172    cx.wait_for_autoindent_applied().await;
33173    cx.assert_editor_state(indoc! {"
33174        1. first item
33175          1. sub first item
33176          2. sub second item
33177        1. ˇ
33178    "});
33179}
33180
33181#[gpui::test]
33182async fn test_tab_list_indent(cx: &mut TestAppContext) {
33183    init_test(cx, |settings| {
33184        settings.defaults.tab_size = Some(2.try_into().unwrap());
33185    });
33186
33187    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33188    let mut cx = EditorTestContext::new(cx).await;
33189    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33190
33191    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
33192    cx.set_state(indoc! {"
33193        - ˇitem
33194    "});
33195    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33196    cx.wait_for_autoindent_applied().await;
33197    let expected = indoc! {"
33198        $$- ˇitem
33199    "};
33200    cx.assert_editor_state(expected.replace("$", " ").as_str());
33201
33202    // Case 2: Task list - cursor after prefix
33203    cx.set_state(indoc! {"
33204        - [ ] ˇtask
33205    "});
33206    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33207    cx.wait_for_autoindent_applied().await;
33208    let expected = indoc! {"
33209        $$- [ ] ˇtask
33210    "};
33211    cx.assert_editor_state(expected.replace("$", " ").as_str());
33212
33213    // Case 3: Ordered list - cursor after prefix
33214    cx.set_state(indoc! {"
33215        1. ˇfirst
33216    "});
33217    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33218    cx.wait_for_autoindent_applied().await;
33219    let expected = indoc! {"
33220        $$1. ˇfirst
33221    "};
33222    cx.assert_editor_state(expected.replace("$", " ").as_str());
33223
33224    // Case 4: With existing indentation - adds more indent
33225    let initial = indoc! {"
33226        $$- ˇitem
33227    "};
33228    cx.set_state(initial.replace("$", " ").as_str());
33229    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33230    cx.wait_for_autoindent_applied().await;
33231    let expected = indoc! {"
33232        $$$$- ˇitem
33233    "};
33234    cx.assert_editor_state(expected.replace("$", " ").as_str());
33235
33236    // Case 5: Empty list item
33237    cx.set_state(indoc! {"
33238        - ˇ
33239    "});
33240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33241    cx.wait_for_autoindent_applied().await;
33242    let expected = indoc! {"
33243        $$- ˇ
33244    "};
33245    cx.assert_editor_state(expected.replace("$", " ").as_str());
33246
33247    // Case 6: Cursor at end of line with content
33248    cx.set_state(indoc! {"
33249        - itemˇ
33250    "});
33251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33252    cx.wait_for_autoindent_applied().await;
33253    let expected = indoc! {"
33254        $$- itemˇ
33255    "};
33256    cx.assert_editor_state(expected.replace("$", " ").as_str());
33257
33258    // Case 7: Cursor at start of list item, indents it
33259    cx.set_state(indoc! {"
33260        - item
33261        ˇ  - sub item
33262    "});
33263    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33264    cx.wait_for_autoindent_applied().await;
33265    let expected = indoc! {"
33266        - item
33267          ˇ  - sub item
33268    "};
33269    cx.assert_editor_state(expected);
33270
33271    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
33272    cx.update_editor(|_, _, cx| {
33273        SettingsStore::update_global(cx, |store, cx| {
33274            store.update_user_settings(cx, |settings| {
33275                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
33276            });
33277        });
33278    });
33279    cx.set_state(indoc! {"
33280        - item
33281        ˇ  - sub item
33282    "});
33283    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33284    cx.wait_for_autoindent_applied().await;
33285    let expected = indoc! {"
33286        - item
33287          ˇ- sub item
33288    "};
33289    cx.assert_editor_state(expected);
33290}
33291
33292#[gpui::test]
33293async fn test_local_worktree_trust(cx: &mut TestAppContext) {
33294    init_test(cx, |_| {});
33295    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
33296
33297    cx.update(|cx| {
33298        SettingsStore::update_global(cx, |store, cx| {
33299            store.update_user_settings(cx, |settings| {
33300                settings.project.all_languages.defaults.inlay_hints =
33301                    Some(InlayHintSettingsContent {
33302                        enabled: Some(true),
33303                        ..InlayHintSettingsContent::default()
33304                    });
33305            });
33306        });
33307    });
33308
33309    let fs = FakeFs::new(cx.executor());
33310    fs.insert_tree(
33311        path!("/project"),
33312        json!({
33313            ".zed": {
33314                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
33315            },
33316            "main.rs": "fn main() {}"
33317        }),
33318    )
33319    .await;
33320
33321    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
33322    let server_name = "override-rust-analyzer";
33323    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
33324
33325    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
33326    language_registry.add(rust_lang());
33327
33328    let capabilities = lsp::ServerCapabilities {
33329        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
33330        ..lsp::ServerCapabilities::default()
33331    };
33332    let mut fake_language_servers = language_registry.register_fake_lsp(
33333        "Rust",
33334        FakeLspAdapter {
33335            name: server_name,
33336            capabilities,
33337            initializer: Some(Box::new({
33338                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
33339                move |fake_server| {
33340                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
33341                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
33342                        move |_params, _| {
33343                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
33344                            async move {
33345                                Ok(Some(vec![lsp::InlayHint {
33346                                    position: lsp::Position::new(0, 0),
33347                                    label: lsp::InlayHintLabel::String("hint".to_string()),
33348                                    kind: None,
33349                                    text_edits: None,
33350                                    tooltip: None,
33351                                    padding_left: None,
33352                                    padding_right: None,
33353                                    data: None,
33354                                }]))
33355                            }
33356                        },
33357                    );
33358                }
33359            })),
33360            ..FakeLspAdapter::default()
33361        },
33362    );
33363
33364    cx.run_until_parked();
33365
33366    let worktree_id = project.read_with(cx, |project, cx| {
33367        project
33368            .worktrees(cx)
33369            .next()
33370            .map(|wt| wt.read(cx).id())
33371            .expect("should have a worktree")
33372    });
33373    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
33374
33375    let trusted_worktrees =
33376        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
33377
33378    let can_trust = trusted_worktrees.update(cx, |store, cx| {
33379        store.can_trust(&worktree_store, worktree_id, cx)
33380    });
33381    assert!(!can_trust, "worktree should be restricted initially");
33382
33383    let buffer_before_approval = project
33384        .update(cx, |project, cx| {
33385            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
33386        })
33387        .await
33388        .unwrap();
33389
33390    let (editor, cx) = cx.add_window_view(|window, cx| {
33391        Editor::new(
33392            EditorMode::full(),
33393            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
33394            Some(project.clone()),
33395            window,
33396            cx,
33397        )
33398    });
33399    cx.run_until_parked();
33400    let fake_language_server = fake_language_servers.next();
33401
33402    cx.read(|cx| {
33403        assert_eq!(
33404            language::language_settings::LanguageSettings::for_buffer(
33405                buffer_before_approval.read(cx),
33406                cx
33407            )
33408            .language_servers,
33409            ["...".to_string()],
33410            "local .zed/settings.json must not apply before trust approval"
33411        )
33412    });
33413
33414    editor.update_in(cx, |editor, window, cx| {
33415        editor.handle_input("1", window, cx);
33416    });
33417    cx.run_until_parked();
33418    cx.executor()
33419        .advance_clock(std::time::Duration::from_secs(1));
33420    assert_eq!(
33421        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
33422        0,
33423        "inlay hints must not be queried before trust approval"
33424    );
33425
33426    trusted_worktrees.update(cx, |store, cx| {
33427        store.trust(
33428            &worktree_store,
33429            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
33430            cx,
33431        );
33432    });
33433    cx.run_until_parked();
33434
33435    cx.read(|cx| {
33436        assert_eq!(
33437            language::language_settings::LanguageSettings::for_buffer(
33438                buffer_before_approval.read(cx),
33439                cx
33440            )
33441            .language_servers,
33442            ["override-rust-analyzer".to_string()],
33443            "local .zed/settings.json should apply after trust approval"
33444        )
33445    });
33446    let _fake_language_server = fake_language_server.await.unwrap();
33447    editor.update_in(cx, |editor, window, cx| {
33448        editor.handle_input("1", window, cx);
33449    });
33450    cx.run_until_parked();
33451    cx.executor()
33452        .advance_clock(std::time::Duration::from_secs(1));
33453    assert!(
33454        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
33455        "inlay hints should be queried after trust approval"
33456    );
33457
33458    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
33459        store.can_trust(&worktree_store, worktree_id, cx)
33460    });
33461    assert!(can_trust_after, "worktree should be trusted after trust()");
33462}
33463
33464#[gpui::test]
33465fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
33466    // This test reproduces a bug where drawing an editor at a position above the viewport
33467    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
33468    // causes an infinite loop in blocks_in_range.
33469    //
33470    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
33471    // the content mask intersection produces visible_bounds with origin at the viewport top.
33472    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
33473    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
33474    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
33475    init_test(cx, |_| {});
33476
33477    let window = cx.add_window(|_, _| gpui::Empty);
33478    let mut cx = VisualTestContext::from_window(*window, cx);
33479
33480    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
33481    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
33482
33483    // Simulate a small viewport (500x500 pixels at origin 0,0)
33484    cx.simulate_resize(gpui::size(px(500.), px(500.)));
33485
33486    // Draw the editor at a very negative Y position, simulating an editor that's been
33487    // scrolled way above the visible viewport (like in a List that has scrolled past it).
33488    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
33489    // This should NOT hang - it should just render nothing.
33490    cx.draw(
33491        gpui::point(px(0.), px(-10000.)),
33492        gpui::size(px(500.), px(3000.)),
33493        |_, _| editor.clone().into_any_element(),
33494    );
33495
33496    // If we get here without hanging, the test passes
33497}
33498
33499#[gpui::test]
33500async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
33501    init_test(cx, |_| {});
33502
33503    let fs = FakeFs::new(cx.executor());
33504    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33505        .await;
33506
33507    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33508    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33509    let workspace = window
33510        .read_with(cx, |mw, _| mw.workspace().clone())
33511        .unwrap();
33512    let cx = &mut VisualTestContext::from_window(*window, cx);
33513
33514    let editor = workspace
33515        .update_in(cx, |workspace, window, cx| {
33516            workspace.open_abs_path(
33517                PathBuf::from(path!("/root/file.txt")),
33518                OpenOptions::default(),
33519                window,
33520                cx,
33521            )
33522        })
33523        .await
33524        .unwrap()
33525        .downcast::<Editor>()
33526        .unwrap();
33527
33528    // Enable diff review button mode
33529    editor.update(cx, |editor, cx| {
33530        editor.set_show_diff_review_button(true, cx);
33531    });
33532
33533    // Initially, no indicator should be present
33534    editor.update(cx, |editor, _cx| {
33535        assert!(
33536            editor.gutter_diff_review_indicator.0.is_none(),
33537            "Indicator should be None initially"
33538        );
33539    });
33540}
33541
33542#[gpui::test]
33543async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
33544    init_test(cx, |_| {});
33545
33546    // Register DisableAiSettings and set disable_ai to true
33547    cx.update(|cx| {
33548        project::DisableAiSettings::register(cx);
33549        project::DisableAiSettings::override_global(
33550            project::DisableAiSettings { disable_ai: true },
33551            cx,
33552        );
33553    });
33554
33555    let fs = FakeFs::new(cx.executor());
33556    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33557        .await;
33558
33559    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33560    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33561    let workspace = window
33562        .read_with(cx, |mw, _| mw.workspace().clone())
33563        .unwrap();
33564    let cx = &mut VisualTestContext::from_window(*window, cx);
33565
33566    let editor = workspace
33567        .update_in(cx, |workspace, window, cx| {
33568            workspace.open_abs_path(
33569                PathBuf::from(path!("/root/file.txt")),
33570                OpenOptions::default(),
33571                window,
33572                cx,
33573            )
33574        })
33575        .await
33576        .unwrap()
33577        .downcast::<Editor>()
33578        .unwrap();
33579
33580    // Enable diff review button mode
33581    editor.update(cx, |editor, cx| {
33582        editor.set_show_diff_review_button(true, cx);
33583    });
33584
33585    // Verify AI is disabled
33586    cx.read(|cx| {
33587        assert!(
33588            project::DisableAiSettings::get_global(cx).disable_ai,
33589            "AI should be disabled"
33590        );
33591    });
33592
33593    // The indicator should not be created when AI is disabled
33594    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
33595    editor.update(cx, |editor, _cx| {
33596        assert!(
33597            editor.gutter_diff_review_indicator.0.is_none(),
33598            "Indicator should be None when AI is disabled"
33599        );
33600    });
33601}
33602
33603#[gpui::test]
33604async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
33605    init_test(cx, |_| {});
33606
33607    // Register DisableAiSettings and set disable_ai to false
33608    cx.update(|cx| {
33609        project::DisableAiSettings::register(cx);
33610        project::DisableAiSettings::override_global(
33611            project::DisableAiSettings { disable_ai: false },
33612            cx,
33613        );
33614    });
33615
33616    let fs = FakeFs::new(cx.executor());
33617    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33618        .await;
33619
33620    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33621    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33622    let workspace = window
33623        .read_with(cx, |mw, _| mw.workspace().clone())
33624        .unwrap();
33625    let cx = &mut VisualTestContext::from_window(*window, cx);
33626
33627    let editor = workspace
33628        .update_in(cx, |workspace, window, cx| {
33629            workspace.open_abs_path(
33630                PathBuf::from(path!("/root/file.txt")),
33631                OpenOptions::default(),
33632                window,
33633                cx,
33634            )
33635        })
33636        .await
33637        .unwrap()
33638        .downcast::<Editor>()
33639        .unwrap();
33640
33641    // Enable diff review button mode
33642    editor.update(cx, |editor, cx| {
33643        editor.set_show_diff_review_button(true, cx);
33644    });
33645
33646    // Verify AI is enabled
33647    cx.read(|cx| {
33648        assert!(
33649            !project::DisableAiSettings::get_global(cx).disable_ai,
33650            "AI should be enabled"
33651        );
33652    });
33653
33654    // The show_diff_review_button flag should be true
33655    editor.update(cx, |editor, _cx| {
33656        assert!(
33657            editor.show_diff_review_button(),
33658            "show_diff_review_button should be true"
33659        );
33660    });
33661}
33662
33663/// Helper function to create a DiffHunkKey for testing.
33664/// Uses Anchor::Min as a placeholder anchor since these tests don't need
33665/// real buffer positioning.
33666fn test_hunk_key(file_path: &str) -> DiffHunkKey {
33667    DiffHunkKey {
33668        file_path: if file_path.is_empty() {
33669            Arc::from(util::rel_path::RelPath::empty())
33670        } else {
33671            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
33672        },
33673        hunk_start_anchor: Anchor::Min,
33674    }
33675}
33676
33677/// Helper function to create a DiffHunkKey with a specific anchor for testing.
33678fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
33679    DiffHunkKey {
33680        file_path: if file_path.is_empty() {
33681            Arc::from(util::rel_path::RelPath::empty())
33682        } else {
33683            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
33684        },
33685        hunk_start_anchor: anchor,
33686    }
33687}
33688
33689/// Helper function to add a review comment with default anchors for testing.
33690fn add_test_comment(
33691    editor: &mut Editor,
33692    key: DiffHunkKey,
33693    comment: &str,
33694    cx: &mut Context<Editor>,
33695) -> usize {
33696    editor.add_review_comment(key, comment.to_string(), Anchor::Min..Anchor::Max, cx)
33697}
33698
33699#[gpui::test]
33700fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
33701    init_test(cx, |_| {});
33702
33703    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33704
33705    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33706        let key = test_hunk_key("");
33707
33708        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
33709
33710        let snapshot = editor.buffer().read(cx).snapshot(cx);
33711        assert_eq!(editor.total_review_comment_count(), 1);
33712        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
33713
33714        let comments = editor.comments_for_hunk(&key, &snapshot);
33715        assert_eq!(comments.len(), 1);
33716        assert_eq!(comments[0].comment, "Test comment");
33717        assert_eq!(comments[0].id, id);
33718    });
33719}
33720
33721#[gpui::test]
33722fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
33723    init_test(cx, |_| {});
33724
33725    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33726
33727    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33728        let snapshot = editor.buffer().read(cx).snapshot(cx);
33729        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
33730        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
33731        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
33732        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
33733
33734        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
33735        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
33736
33737        let snapshot = editor.buffer().read(cx).snapshot(cx);
33738        assert_eq!(editor.total_review_comment_count(), 2);
33739        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
33740        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
33741
33742        assert_eq!(
33743            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
33744            "Comment for file1"
33745        );
33746        assert_eq!(
33747            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
33748            "Comment for file2"
33749        );
33750    });
33751}
33752
33753#[gpui::test]
33754fn test_review_comment_remove(cx: &mut TestAppContext) {
33755    init_test(cx, |_| {});
33756
33757    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33758
33759    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33760        let key = test_hunk_key("");
33761
33762        let id = add_test_comment(editor, key, "To be removed", cx);
33763
33764        assert_eq!(editor.total_review_comment_count(), 1);
33765
33766        let removed = editor.remove_review_comment(id, cx);
33767        assert!(removed);
33768        assert_eq!(editor.total_review_comment_count(), 0);
33769
33770        // Try to remove again
33771        let removed_again = editor.remove_review_comment(id, cx);
33772        assert!(!removed_again);
33773    });
33774}
33775
33776#[gpui::test]
33777fn test_review_comment_update(cx: &mut TestAppContext) {
33778    init_test(cx, |_| {});
33779
33780    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33781
33782    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33783        let key = test_hunk_key("");
33784
33785        let id = add_test_comment(editor, key.clone(), "Original text", cx);
33786
33787        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
33788        assert!(updated);
33789
33790        let snapshot = editor.buffer().read(cx).snapshot(cx);
33791        let comments = editor.comments_for_hunk(&key, &snapshot);
33792        assert_eq!(comments[0].comment, "Updated text");
33793        assert!(!comments[0].is_editing); // Should clear editing flag
33794    });
33795}
33796
33797#[gpui::test]
33798fn test_review_comment_take_all(cx: &mut TestAppContext) {
33799    init_test(cx, |_| {});
33800
33801    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33802
33803    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33804        let snapshot = editor.buffer().read(cx).snapshot(cx);
33805        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
33806        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
33807        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
33808        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
33809
33810        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
33811        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
33812        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
33813
33814        // IDs should be sequential starting from 0
33815        assert_eq!(id1, 0);
33816        assert_eq!(id2, 1);
33817        assert_eq!(id3, 2);
33818
33819        assert_eq!(editor.total_review_comment_count(), 3);
33820
33821        let taken = editor.take_all_review_comments(cx);
33822
33823        // Should have 2 entries (one per hunk)
33824        assert_eq!(taken.len(), 2);
33825
33826        // Total comments should be 3
33827        let total: usize = taken
33828            .iter()
33829            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
33830            .sum();
33831        assert_eq!(total, 3);
33832
33833        // Storage should be empty
33834        assert_eq!(editor.total_review_comment_count(), 0);
33835
33836        // After taking all comments, ID counter should reset
33837        // New comments should get IDs starting from 0 again
33838        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
33839        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
33840
33841        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
33842        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
33843    });
33844}
33845
33846#[gpui::test]
33847fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
33848    init_test(cx, |_| {});
33849
33850    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33851
33852    // Show overlay
33853    editor
33854        .update(cx, |editor, window, cx| {
33855            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33856        })
33857        .unwrap();
33858
33859    // Verify overlay is shown
33860    editor
33861        .update(cx, |editor, _window, cx| {
33862            assert!(!editor.diff_review_overlays.is_empty());
33863            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
33864            assert!(editor.diff_review_prompt_editor().is_some());
33865        })
33866        .unwrap();
33867
33868    // Dismiss overlay
33869    editor
33870        .update(cx, |editor, _window, cx| {
33871            editor.dismiss_all_diff_review_overlays(cx);
33872        })
33873        .unwrap();
33874
33875    // Verify overlay is dismissed
33876    editor
33877        .update(cx, |editor, _window, cx| {
33878            assert!(editor.diff_review_overlays.is_empty());
33879            assert_eq!(editor.diff_review_line_range(cx), None);
33880            assert!(editor.diff_review_prompt_editor().is_none());
33881        })
33882        .unwrap();
33883}
33884
33885#[gpui::test]
33886fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
33887    init_test(cx, |_| {});
33888
33889    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33890
33891    // Show overlay
33892    editor
33893        .update(cx, |editor, window, cx| {
33894            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33895        })
33896        .unwrap();
33897
33898    // Verify overlay is shown
33899    editor
33900        .update(cx, |editor, _window, _cx| {
33901            assert!(!editor.diff_review_overlays.is_empty());
33902        })
33903        .unwrap();
33904
33905    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
33906    editor
33907        .update(cx, |editor, window, cx| {
33908            editor.dismiss_menus_and_popups(true, window, cx);
33909        })
33910        .unwrap();
33911
33912    // Verify overlay is dismissed
33913    editor
33914        .update(cx, |editor, _window, _cx| {
33915            assert!(editor.diff_review_overlays.is_empty());
33916        })
33917        .unwrap();
33918}
33919
33920#[gpui::test]
33921fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
33922    init_test(cx, |_| {});
33923
33924    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33925
33926    // Show overlay
33927    editor
33928        .update(cx, |editor, window, cx| {
33929            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33930        })
33931        .unwrap();
33932
33933    // Try to submit without typing anything (empty comment)
33934    editor
33935        .update(cx, |editor, window, cx| {
33936            editor.submit_diff_review_comment(window, cx);
33937        })
33938        .unwrap();
33939
33940    // Verify no comment was added
33941    editor
33942        .update(cx, |editor, _window, _cx| {
33943            assert_eq!(editor.total_review_comment_count(), 0);
33944        })
33945        .unwrap();
33946
33947    // Try to submit with whitespace-only comment
33948    editor
33949        .update(cx, |editor, window, cx| {
33950            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
33951                prompt_editor.update(cx, |pe, cx| {
33952                    pe.insert("   \n\t  ", window, cx);
33953                });
33954            }
33955            editor.submit_diff_review_comment(window, cx);
33956        })
33957        .unwrap();
33958
33959    // Verify still no comment was added
33960    editor
33961        .update(cx, |editor, _window, _cx| {
33962            assert_eq!(editor.total_review_comment_count(), 0);
33963        })
33964        .unwrap();
33965}
33966
33967#[gpui::test]
33968fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
33969    init_test(cx, |_| {});
33970
33971    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33972
33973    // Add a comment directly
33974    let comment_id = editor
33975        .update(cx, |editor, _window, cx| {
33976            let key = test_hunk_key("");
33977            add_test_comment(editor, key, "Original comment", cx)
33978        })
33979        .unwrap();
33980
33981    // Set comment to editing mode
33982    editor
33983        .update(cx, |editor, _window, cx| {
33984            editor.set_comment_editing(comment_id, true, cx);
33985        })
33986        .unwrap();
33987
33988    // Verify editing flag is set
33989    editor
33990        .update(cx, |editor, _window, cx| {
33991            let key = test_hunk_key("");
33992            let snapshot = editor.buffer().read(cx).snapshot(cx);
33993            let comments = editor.comments_for_hunk(&key, &snapshot);
33994            assert_eq!(comments.len(), 1);
33995            assert!(comments[0].is_editing);
33996        })
33997        .unwrap();
33998
33999    // Update the comment
34000    editor
34001        .update(cx, |editor, _window, cx| {
34002            let updated =
34003                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
34004            assert!(updated);
34005        })
34006        .unwrap();
34007
34008    // Verify comment was updated and editing flag is cleared
34009    editor
34010        .update(cx, |editor, _window, cx| {
34011            let key = test_hunk_key("");
34012            let snapshot = editor.buffer().read(cx).snapshot(cx);
34013            let comments = editor.comments_for_hunk(&key, &snapshot);
34014            assert_eq!(comments[0].comment, "Updated comment");
34015            assert!(!comments[0].is_editing);
34016        })
34017        .unwrap();
34018}
34019
34020#[gpui::test]
34021fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
34022    init_test(cx, |_| {});
34023
34024    // Create an editor with some text
34025    let editor = cx.add_window(|window, cx| {
34026        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
34027        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34028        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34029    });
34030
34031    // Add a comment with an anchor on line 2
34032    editor
34033        .update(cx, |editor, _window, cx| {
34034            let snapshot = editor.buffer().read(cx).snapshot(cx);
34035            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
34036            let key = DiffHunkKey {
34037                file_path: Arc::from(util::rel_path::RelPath::empty()),
34038                hunk_start_anchor: anchor,
34039            };
34040            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
34041            assert_eq!(editor.total_review_comment_count(), 1);
34042        })
34043        .unwrap();
34044
34045    // Delete all content (this should orphan the comment's anchor)
34046    editor
34047        .update(cx, |editor, window, cx| {
34048            editor.select_all(&SelectAll, window, cx);
34049            editor.insert("completely new content", window, cx);
34050        })
34051        .unwrap();
34052
34053    // Trigger cleanup
34054    editor
34055        .update(cx, |editor, _window, cx| {
34056            editor.cleanup_orphaned_review_comments(cx);
34057            // Comment should be removed because its anchor is invalid
34058            assert_eq!(editor.total_review_comment_count(), 0);
34059        })
34060        .unwrap();
34061}
34062
34063#[gpui::test]
34064fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
34065    init_test(cx, |_| {});
34066
34067    // Create an editor with some text
34068    let editor = cx.add_window(|window, cx| {
34069        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
34070        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34071        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34072    });
34073
34074    // Add a comment with an anchor on line 2
34075    editor
34076        .update(cx, |editor, _window, cx| {
34077            let snapshot = editor.buffer().read(cx).snapshot(cx);
34078            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
34079            let key = DiffHunkKey {
34080                file_path: Arc::from(util::rel_path::RelPath::empty()),
34081                hunk_start_anchor: anchor,
34082            };
34083            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
34084            assert_eq!(editor.total_review_comment_count(), 1);
34085        })
34086        .unwrap();
34087
34088    // Edit the buffer - this should trigger cleanup via on_buffer_event
34089    // Delete all content which orphans the anchor
34090    editor
34091        .update(cx, |editor, window, cx| {
34092            editor.select_all(&SelectAll, window, cx);
34093            editor.insert("completely new content", window, cx);
34094            // The cleanup is called automatically in on_buffer_event when Edited fires
34095        })
34096        .unwrap();
34097
34098    // Verify cleanup happened automatically (not manually triggered)
34099    editor
34100        .update(cx, |editor, _window, _cx| {
34101            // Comment should be removed because its anchor became invalid
34102            // and cleanup was called automatically on buffer edit
34103            assert_eq!(editor.total_review_comment_count(), 0);
34104        })
34105        .unwrap();
34106}
34107
34108#[gpui::test]
34109fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
34110    init_test(cx, |_| {});
34111
34112    // This test verifies that comments can be stored for multiple different hunks
34113    // and that hunk_comment_count correctly identifies comments per hunk.
34114    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34115
34116    _ = editor.update(cx, |editor, _window, cx| {
34117        let snapshot = editor.buffer().read(cx).snapshot(cx);
34118
34119        // Create two different hunk keys (simulating two different files)
34120        let anchor = snapshot.anchor_before(Point::new(0, 0));
34121        let key1 = DiffHunkKey {
34122            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
34123            hunk_start_anchor: anchor,
34124        };
34125        let key2 = DiffHunkKey {
34126            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
34127            hunk_start_anchor: anchor,
34128        };
34129
34130        // Add comments to first hunk
34131        editor.add_review_comment(
34132            key1.clone(),
34133            "Comment 1 for file1".to_string(),
34134            anchor..anchor,
34135            cx,
34136        );
34137        editor.add_review_comment(
34138            key1.clone(),
34139            "Comment 2 for file1".to_string(),
34140            anchor..anchor,
34141            cx,
34142        );
34143
34144        // Add comment to second hunk
34145        editor.add_review_comment(
34146            key2.clone(),
34147            "Comment for file2".to_string(),
34148            anchor..anchor,
34149            cx,
34150        );
34151
34152        // Verify total count
34153        assert_eq!(editor.total_review_comment_count(), 3);
34154
34155        // Verify per-hunk counts
34156        let snapshot = editor.buffer().read(cx).snapshot(cx);
34157        assert_eq!(
34158            editor.hunk_comment_count(&key1, &snapshot),
34159            2,
34160            "file1 should have 2 comments"
34161        );
34162        assert_eq!(
34163            editor.hunk_comment_count(&key2, &snapshot),
34164            1,
34165            "file2 should have 1 comment"
34166        );
34167
34168        // Verify comments_for_hunk returns correct comments
34169        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
34170        assert_eq!(file1_comments.len(), 2);
34171        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
34172        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
34173
34174        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
34175        assert_eq!(file2_comments.len(), 1);
34176        assert_eq!(file2_comments[0].comment, "Comment for file2");
34177    });
34178}
34179
34180#[gpui::test]
34181fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
34182    init_test(cx, |_| {});
34183
34184    // This test verifies that hunk_keys_match correctly identifies when two
34185    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
34186    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34187
34188    _ = editor.update(cx, |editor, _window, cx| {
34189        let snapshot = editor.buffer().read(cx).snapshot(cx);
34190        let anchor = snapshot.anchor_before(Point::new(0, 0));
34191
34192        // Create two keys with the same file path and anchor
34193        let key1 = DiffHunkKey {
34194            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
34195            hunk_start_anchor: anchor,
34196        };
34197        let key2 = DiffHunkKey {
34198            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
34199            hunk_start_anchor: anchor,
34200        };
34201
34202        // Add comment to first key
34203        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
34204
34205        // Verify second key (same hunk) finds the comment
34206        let snapshot = editor.buffer().read(cx).snapshot(cx);
34207        assert_eq!(
34208            editor.hunk_comment_count(&key2, &snapshot),
34209            1,
34210            "Same hunk should find the comment"
34211        );
34212
34213        // Create a key with different file path
34214        let different_file_key = DiffHunkKey {
34215            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
34216            hunk_start_anchor: anchor,
34217        };
34218
34219        // Different file should not find the comment
34220        assert_eq!(
34221            editor.hunk_comment_count(&different_file_key, &snapshot),
34222            0,
34223            "Different file should not find the comment"
34224        );
34225    });
34226}
34227
34228#[gpui::test]
34229fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
34230    init_test(cx, |_| {});
34231
34232    // This test verifies that set_diff_review_comments_expanded correctly
34233    // updates the expanded state of overlays.
34234    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34235
34236    // Show overlay
34237    editor
34238        .update(cx, |editor, window, cx| {
34239            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
34240        })
34241        .unwrap();
34242
34243    // Verify initially expanded (default)
34244    editor
34245        .update(cx, |editor, _window, _cx| {
34246            assert!(
34247                editor.diff_review_overlays[0].comments_expanded,
34248                "Should be expanded by default"
34249            );
34250        })
34251        .unwrap();
34252
34253    // Set to collapsed using the public method
34254    editor
34255        .update(cx, |editor, _window, cx| {
34256            editor.set_diff_review_comments_expanded(false, cx);
34257        })
34258        .unwrap();
34259
34260    // Verify collapsed
34261    editor
34262        .update(cx, |editor, _window, _cx| {
34263            assert!(
34264                !editor.diff_review_overlays[0].comments_expanded,
34265                "Should be collapsed after setting to false"
34266            );
34267        })
34268        .unwrap();
34269
34270    // Set back to expanded
34271    editor
34272        .update(cx, |editor, _window, cx| {
34273            editor.set_diff_review_comments_expanded(true, cx);
34274        })
34275        .unwrap();
34276
34277    // Verify expanded again
34278    editor
34279        .update(cx, |editor, _window, _cx| {
34280            assert!(
34281                editor.diff_review_overlays[0].comments_expanded,
34282                "Should be expanded after setting to true"
34283            );
34284        })
34285        .unwrap();
34286}
34287
34288#[gpui::test]
34289fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
34290    init_test(cx, |_| {});
34291
34292    // Create an editor with multiple lines of text
34293    let editor = cx.add_window(|window, cx| {
34294        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
34295        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34296        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34297    });
34298
34299    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
34300    editor
34301        .update(cx, |editor, window, cx| {
34302            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
34303        })
34304        .unwrap();
34305
34306    // Verify line range
34307    editor
34308        .update(cx, |editor, _window, cx| {
34309            assert!(!editor.diff_review_overlays.is_empty());
34310            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
34311        })
34312        .unwrap();
34313
34314    // Dismiss and test with reversed range (end < start)
34315    editor
34316        .update(cx, |editor, _window, cx| {
34317            editor.dismiss_all_diff_review_overlays(cx);
34318        })
34319        .unwrap();
34320
34321    // Show overlay with reversed range - should normalize it
34322    editor
34323        .update(cx, |editor, window, cx| {
34324            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
34325        })
34326        .unwrap();
34327
34328    // Verify range is normalized (start <= end)
34329    editor
34330        .update(cx, |editor, _window, cx| {
34331            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
34332        })
34333        .unwrap();
34334}
34335
34336#[gpui::test]
34337fn test_diff_review_drag_state(cx: &mut TestAppContext) {
34338    init_test(cx, |_| {});
34339
34340    let editor = cx.add_window(|window, cx| {
34341        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
34342        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34343        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34344    });
34345
34346    // Initially no drag state
34347    editor
34348        .update(cx, |editor, _window, _cx| {
34349            assert!(editor.diff_review_drag_state.is_none());
34350        })
34351        .unwrap();
34352
34353    // Start drag at row 1
34354    editor
34355        .update(cx, |editor, window, cx| {
34356            editor.start_diff_review_drag(DisplayRow(1), window, cx);
34357        })
34358        .unwrap();
34359
34360    // Verify drag state is set
34361    editor
34362        .update(cx, |editor, window, cx| {
34363            assert!(editor.diff_review_drag_state.is_some());
34364            let snapshot = editor.snapshot(window, cx);
34365            let range = editor
34366                .diff_review_drag_state
34367                .as_ref()
34368                .unwrap()
34369                .row_range(&snapshot.display_snapshot);
34370            assert_eq!(*range.start(), DisplayRow(1));
34371            assert_eq!(*range.end(), DisplayRow(1));
34372        })
34373        .unwrap();
34374
34375    // Update drag to row 3
34376    editor
34377        .update(cx, |editor, window, cx| {
34378            editor.update_diff_review_drag(DisplayRow(3), window, cx);
34379        })
34380        .unwrap();
34381
34382    // Verify drag state is updated
34383    editor
34384        .update(cx, |editor, window, cx| {
34385            assert!(editor.diff_review_drag_state.is_some());
34386            let snapshot = editor.snapshot(window, cx);
34387            let range = editor
34388                .diff_review_drag_state
34389                .as_ref()
34390                .unwrap()
34391                .row_range(&snapshot.display_snapshot);
34392            assert_eq!(*range.start(), DisplayRow(1));
34393            assert_eq!(*range.end(), DisplayRow(3));
34394        })
34395        .unwrap();
34396
34397    // End drag - should show overlay
34398    editor
34399        .update(cx, |editor, window, cx| {
34400            editor.end_diff_review_drag(window, cx);
34401        })
34402        .unwrap();
34403
34404    // Verify drag state is cleared and overlay is shown
34405    editor
34406        .update(cx, |editor, _window, cx| {
34407            assert!(editor.diff_review_drag_state.is_none());
34408            assert!(!editor.diff_review_overlays.is_empty());
34409            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
34410        })
34411        .unwrap();
34412}
34413
34414#[gpui::test]
34415fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
34416    init_test(cx, |_| {});
34417
34418    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34419
34420    // Start drag
34421    editor
34422        .update(cx, |editor, window, cx| {
34423            editor.start_diff_review_drag(DisplayRow(0), window, cx);
34424        })
34425        .unwrap();
34426
34427    // Verify drag state is set
34428    editor
34429        .update(cx, |editor, _window, _cx| {
34430            assert!(editor.diff_review_drag_state.is_some());
34431        })
34432        .unwrap();
34433
34434    // Cancel drag
34435    editor
34436        .update(cx, |editor, _window, cx| {
34437            editor.cancel_diff_review_drag(cx);
34438        })
34439        .unwrap();
34440
34441    // Verify drag state is cleared and no overlay was created
34442    editor
34443        .update(cx, |editor, _window, _cx| {
34444            assert!(editor.diff_review_drag_state.is_none());
34445            assert!(editor.diff_review_overlays.is_empty());
34446        })
34447        .unwrap();
34448}
34449
34450#[gpui::test]
34451fn test_calculate_overlay_height(cx: &mut TestAppContext) {
34452    init_test(cx, |_| {});
34453
34454    // This test verifies that calculate_overlay_height returns correct heights
34455    // based on comment count and expanded state.
34456    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34457
34458    _ = editor.update(cx, |editor, _window, cx| {
34459        let snapshot = editor.buffer().read(cx).snapshot(cx);
34460        let anchor = snapshot.anchor_before(Point::new(0, 0));
34461        let key = DiffHunkKey {
34462            file_path: Arc::from(util::rel_path::RelPath::empty()),
34463            hunk_start_anchor: anchor,
34464        };
34465
34466        // No comments: base height of 2
34467        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
34468        assert_eq!(
34469            height_no_comments, 2,
34470            "Base height should be 2 with no comments"
34471        );
34472
34473        // Add one comment
34474        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
34475
34476        let snapshot = editor.buffer().read(cx).snapshot(cx);
34477
34478        // With comments expanded: base (2) + header (1) + 2 per comment
34479        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
34480        assert_eq!(
34481            height_expanded,
34482            2 + 1 + 2, // base + header + 1 comment * 2
34483            "Height with 1 comment expanded"
34484        );
34485
34486        // With comments collapsed: base (2) + header (1)
34487        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
34488        assert_eq!(
34489            height_collapsed,
34490            2 + 1, // base + header only
34491            "Height with comments collapsed"
34492        );
34493
34494        // Add more comments
34495        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
34496        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
34497
34498        let snapshot = editor.buffer().read(cx).snapshot(cx);
34499
34500        // With 3 comments expanded
34501        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
34502        assert_eq!(
34503            height_3_expanded,
34504            2 + 1 + (3 * 2), // base + header + 3 comments * 2
34505            "Height with 3 comments expanded"
34506        );
34507
34508        // Collapsed height stays the same regardless of comment count
34509        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
34510        assert_eq!(
34511            height_3_collapsed,
34512            2 + 1, // base + header only
34513            "Height with 3 comments collapsed should be same as 1 comment collapsed"
34514        );
34515    });
34516}
34517
34518#[gpui::test]
34519async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
34520    init_test(cx, |_| {});
34521
34522    let language = Arc::new(Language::new(
34523        LanguageConfig::default(),
34524        Some(tree_sitter_rust::LANGUAGE.into()),
34525    ));
34526
34527    let text = r#"
34528        fn main() {
34529            let x = foo(1, 2);
34530        }
34531    "#
34532    .unindent();
34533
34534    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34535    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34536    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34537
34538    editor
34539        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34540        .await;
34541
34542    // Test case 1: Move to end of syntax nodes
34543    editor.update_in(cx, |editor, window, cx| {
34544        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34545            s.select_display_ranges([
34546                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
34547            ]);
34548        });
34549    });
34550    editor.update(cx, |editor, cx| {
34551        assert_text_with_selections(
34552            editor,
34553            indoc! {r#"
34554                fn main() {
34555                    let x = foo(ˇ1, 2);
34556                }
34557            "#},
34558            cx,
34559        );
34560    });
34561    editor.update_in(cx, |editor, window, cx| {
34562        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34563    });
34564    editor.update(cx, |editor, cx| {
34565        assert_text_with_selections(
34566            editor,
34567            indoc! {r#"
34568                fn main() {
34569                    let x = foo(1ˇ, 2);
34570                }
34571            "#},
34572            cx,
34573        );
34574    });
34575    editor.update_in(cx, |editor, window, cx| {
34576        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34577    });
34578    editor.update(cx, |editor, cx| {
34579        assert_text_with_selections(
34580            editor,
34581            indoc! {r#"
34582                fn main() {
34583                    let x = foo(1, 2)ˇ;
34584                }
34585            "#},
34586            cx,
34587        );
34588    });
34589    editor.update_in(cx, |editor, window, cx| {
34590        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34591    });
34592    editor.update(cx, |editor, cx| {
34593        assert_text_with_selections(
34594            editor,
34595            indoc! {r#"
34596                fn main() {
34597                    let x = foo(1, 2);ˇ
34598                }
34599            "#},
34600            cx,
34601        );
34602    });
34603    editor.update_in(cx, |editor, window, cx| {
34604        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34605    });
34606    editor.update(cx, |editor, cx| {
34607        assert_text_with_selections(
34608            editor,
34609            indoc! {r#"
34610                fn main() {
34611                    let x = foo(1, 2);
3461234613            "#},
34614            cx,
34615        );
34616    });
34617
34618    // Test case 2: Move to start of syntax nodes
34619    editor.update_in(cx, |editor, window, cx| {
34620        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34621            s.select_display_ranges([
34622                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
34623            ]);
34624        });
34625    });
34626    editor.update(cx, |editor, cx| {
34627        assert_text_with_selections(
34628            editor,
34629            indoc! {r#"
34630                fn main() {
34631                    let x = foo(1, 2ˇ);
34632                }
34633            "#},
34634            cx,
34635        );
34636    });
34637    editor.update_in(cx, |editor, window, cx| {
34638        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34639    });
34640    editor.update(cx, |editor, cx| {
34641        assert_text_with_selections(
34642            editor,
34643            indoc! {r#"
34644                fn main() {
34645                    let x = fooˇ(1, 2);
34646                }
34647            "#},
34648            cx,
34649        );
34650    });
34651    editor.update_in(cx, |editor, window, cx| {
34652        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34653    });
34654    editor.update(cx, |editor, cx| {
34655        assert_text_with_selections(
34656            editor,
34657            indoc! {r#"
34658                fn main() {
34659                    let x = ˇfoo(1, 2);
34660                }
34661            "#},
34662            cx,
34663        );
34664    });
34665    editor.update_in(cx, |editor, window, cx| {
34666        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34667    });
34668    editor.update(cx, |editor, cx| {
34669        assert_text_with_selections(
34670            editor,
34671            indoc! {r#"
34672                fn main() {
34673                    ˇlet x = foo(1, 2);
34674                }
34675            "#},
34676            cx,
34677        );
34678    });
34679    editor.update_in(cx, |editor, window, cx| {
34680        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34681    });
34682    editor.update(cx, |editor, cx| {
34683        assert_text_with_selections(
34684            editor,
34685            indoc! {r#"
34686                fn main() ˇ{
34687                    let x = foo(1, 2);
34688                }
34689            "#},
34690            cx,
34691        );
34692    });
34693    editor.update_in(cx, |editor, window, cx| {
34694        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34695    });
34696    editor.update(cx, |editor, cx| {
34697        assert_text_with_selections(
34698            editor,
34699            indoc! {r#"
34700                ˇfn main() {
34701                    let x = foo(1, 2);
34702                }
34703            "#},
34704            cx,
34705        );
34706    });
34707}
34708
34709#[gpui::test]
34710async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
34711    init_test(cx, |_| {});
34712
34713    let language = Arc::new(Language::new(
34714        LanguageConfig::default(),
34715        Some(tree_sitter_rust::LANGUAGE.into()),
34716    ));
34717
34718    let text = r#"
34719        fn main() {
34720            let x = foo(1, 2);
34721            let y = bar(3, 4);
34722        }
34723    "#
34724    .unindent();
34725
34726    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34727    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34728    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34729
34730    editor
34731        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34732        .await;
34733
34734    // Test case 1: Move to end of syntax nodes with two cursors
34735    editor.update_in(cx, |editor, window, cx| {
34736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34737            s.select_display_ranges([
34738                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
34739                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
34740            ]);
34741        });
34742    });
34743    editor.update(cx, |editor, cx| {
34744        assert_text_with_selections(
34745            editor,
34746            indoc! {r#"
34747                fn main() {
34748                    let x = foo(1, 2ˇ);
34749                    let y = bar(3, 4ˇ);
34750                }
34751            "#},
34752            cx,
34753        );
34754    });
34755    editor.update_in(cx, |editor, window, cx| {
34756        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34757    });
34758    editor.update(cx, |editor, cx| {
34759        assert_text_with_selections(
34760            editor,
34761            indoc! {r#"
34762                fn main() {
34763                    let x = foo(1, 2)ˇ;
34764                    let y = bar(3, 4)ˇ;
34765                }
34766            "#},
34767            cx,
34768        );
34769    });
34770    editor.update_in(cx, |editor, window, cx| {
34771        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34772    });
34773    editor.update(cx, |editor, cx| {
34774        assert_text_with_selections(
34775            editor,
34776            indoc! {r#"
34777                fn main() {
34778                    let x = foo(1, 2);ˇ
34779                    let y = bar(3, 4);ˇ
34780                }
34781            "#},
34782            cx,
34783        );
34784    });
34785
34786    // Test case 2: Move to start of syntax nodes with two cursors
34787    editor.update_in(cx, |editor, window, cx| {
34788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34789            s.select_display_ranges([
34790                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
34791                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
34792            ]);
34793        });
34794    });
34795    editor.update(cx, |editor, cx| {
34796        assert_text_with_selections(
34797            editor,
34798            indoc! {r#"
34799                fn main() {
34800                    let x = foo(1, ˇ2);
34801                    let y = bar(3, ˇ4);
34802                }
34803            "#},
34804            cx,
34805        );
34806    });
34807    editor.update_in(cx, |editor, window, cx| {
34808        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34809    });
34810    editor.update(cx, |editor, cx| {
34811        assert_text_with_selections(
34812            editor,
34813            indoc! {r#"
34814                fn main() {
34815                    let x = fooˇ(1, 2);
34816                    let y = barˇ(3, 4);
34817                }
34818            "#},
34819            cx,
34820        );
34821    });
34822    editor.update_in(cx, |editor, window, cx| {
34823        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34824    });
34825    editor.update(cx, |editor, cx| {
34826        assert_text_with_selections(
34827            editor,
34828            indoc! {r#"
34829                fn main() {
34830                    let x = ˇfoo(1, 2);
34831                    let y = ˇbar(3, 4);
34832                }
34833            "#},
34834            cx,
34835        );
34836    });
34837    editor.update_in(cx, |editor, window, cx| {
34838        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34839    });
34840    editor.update(cx, |editor, cx| {
34841        assert_text_with_selections(
34842            editor,
34843            indoc! {r#"
34844                fn main() {
34845                    ˇlet x = foo(1, 2);
34846                    ˇlet y = bar(3, 4);
34847                }
34848            "#},
34849            cx,
34850        );
34851    });
34852}
34853
34854#[gpui::test]
34855async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
34856    cx: &mut TestAppContext,
34857) {
34858    init_test(cx, |_| {});
34859
34860    let language = Arc::new(Language::new(
34861        LanguageConfig::default(),
34862        Some(tree_sitter_rust::LANGUAGE.into()),
34863    ));
34864
34865    let text = r#"
34866        fn main() {
34867            let x = foo(1, 2);
34868            let msg = "hello world";
34869        }
34870    "#
34871    .unindent();
34872
34873    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34874    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34875    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34876
34877    editor
34878        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34879        .await;
34880
34881    // Test case 1: With existing selection, move_to_end keeps selection
34882    editor.update_in(cx, |editor, window, cx| {
34883        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34884            s.select_display_ranges([
34885                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
34886            ]);
34887        });
34888    });
34889    editor.update(cx, |editor, cx| {
34890        assert_text_with_selections(
34891            editor,
34892            indoc! {r#"
34893                fn main() {
34894                    let x = «foo(1, 2)ˇ»;
34895                    let msg = "hello world";
34896                }
34897            "#},
34898            cx,
34899        );
34900    });
34901    editor.update_in(cx, |editor, window, cx| {
34902        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34903    });
34904    editor.update(cx, |editor, cx| {
34905        assert_text_with_selections(
34906            editor,
34907            indoc! {r#"
34908                fn main() {
34909                    let x = «foo(1, 2)ˇ»;
34910                    let msg = "hello world";
34911                }
34912            "#},
34913            cx,
34914        );
34915    });
34916
34917    // Test case 2: Move to end within a string
34918    editor.update_in(cx, |editor, window, cx| {
34919        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34920            s.select_display_ranges([
34921                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
34922            ]);
34923        });
34924    });
34925    editor.update(cx, |editor, cx| {
34926        assert_text_with_selections(
34927            editor,
34928            indoc! {r#"
34929                fn main() {
34930                    let x = foo(1, 2);
34931                    let msg = "ˇhello world";
34932                }
34933            "#},
34934            cx,
34935        );
34936    });
34937    editor.update_in(cx, |editor, window, cx| {
34938        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34939    });
34940    editor.update(cx, |editor, cx| {
34941        assert_text_with_selections(
34942            editor,
34943            indoc! {r#"
34944                fn main() {
34945                    let x = foo(1, 2);
34946                    let msg = "hello worldˇ";
34947                }
34948            "#},
34949            cx,
34950        );
34951    });
34952
34953    // Test case 3: Move to start within a string
34954    editor.update_in(cx, |editor, window, cx| {
34955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34956            s.select_display_ranges([
34957                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
34958            ]);
34959        });
34960    });
34961    editor.update(cx, |editor, cx| {
34962        assert_text_with_selections(
34963            editor,
34964            indoc! {r#"
34965                fn main() {
34966                    let x = foo(1, 2);
34967                    let msg = "hello ˇworld";
34968                }
34969            "#},
34970            cx,
34971        );
34972    });
34973    editor.update_in(cx, |editor, window, cx| {
34974        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34975    });
34976    editor.update(cx, |editor, cx| {
34977        assert_text_with_selections(
34978            editor,
34979            indoc! {r#"
34980                fn main() {
34981                    let x = foo(1, 2);
34982                    let msg = "ˇhello world";
34983                }
34984            "#},
34985            cx,
34986        );
34987    });
34988}
34989
34990#[gpui::test]
34991async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
34992    init_test(cx, |_| {});
34993
34994    let language = Arc::new(Language::new(
34995        LanguageConfig::default(),
34996        Some(tree_sitter_rust::LANGUAGE.into()),
34997    ));
34998
34999    // Test Group 1.1: Cursor in String - First Jump (Select to End)
35000    let text = r#"let msg = "foo bar baz";"#.unindent();
35001
35002    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35003    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35004    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35005
35006    editor
35007        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35008        .await;
35009
35010    editor.update_in(cx, |editor, window, cx| {
35011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35012            s.select_display_ranges([
35013                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
35014            ]);
35015        });
35016    });
35017    editor.update(cx, |editor, cx| {
35018        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
35019    });
35020    editor.update_in(cx, |editor, window, cx| {
35021        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35022    });
35023    editor.update(cx, |editor, cx| {
35024        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
35025    });
35026
35027    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
35028    editor.update_in(cx, |editor, window, cx| {
35029        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35030    });
35031    editor.update(cx, |editor, cx| {
35032        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
35033    });
35034
35035    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
35036    editor.update_in(cx, |editor, window, cx| {
35037        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35038    });
35039    editor.update(cx, |editor, cx| {
35040        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
35041    });
35042
35043    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
35044    editor.update_in(cx, |editor, window, cx| {
35045        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35046            s.select_display_ranges([
35047                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
35048            ]);
35049        });
35050    });
35051    editor.update(cx, |editor, cx| {
35052        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
35053    });
35054    editor.update_in(cx, |editor, window, cx| {
35055        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35056    });
35057    editor.update(cx, |editor, cx| {
35058        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
35059    });
35060
35061    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
35062    editor.update_in(cx, |editor, window, cx| {
35063        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35064    });
35065    editor.update(cx, |editor, cx| {
35066        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
35067    });
35068
35069    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
35070    editor.update_in(cx, |editor, window, cx| {
35071        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35072    });
35073    editor.update(cx, |editor, cx| {
35074        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
35075    });
35076
35077    // Test Group 2.1: Let Statement Progression (Select to End)
35078    let text = r#"
35079fn main() {
35080    let x = "hello";
35081}
35082"#
35083    .unindent();
35084
35085    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35086    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35087    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35088
35089    editor
35090        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35091        .await;
35092
35093    editor.update_in(cx, |editor, window, cx| {
35094        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35095            s.select_display_ranges([
35096                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
35097            ]);
35098        });
35099    });
35100    editor.update(cx, |editor, cx| {
35101        assert_text_with_selections(
35102            editor,
35103            indoc! {r#"
35104                fn main() {
35105                    let xˇ = "hello";
35106                }
35107            "#},
35108            cx,
35109        );
35110    });
35111    editor.update_in(cx, |editor, window, cx| {
35112        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35113    });
35114    editor.update(cx, |editor, cx| {
35115        assert_text_with_selections(
35116            editor,
35117            indoc! {r##"
35118                fn main() {
35119                    let x« = "hello";ˇ»
35120                }
35121            "##},
35122            cx,
35123        );
35124    });
35125    editor.update_in(cx, |editor, window, cx| {
35126        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35127    });
35128    editor.update(cx, |editor, cx| {
35129        assert_text_with_selections(
35130            editor,
35131            indoc! {r#"
35132                fn main() {
35133                    let x« = "hello";
35134                }ˇ»
35135            "#},
35136            cx,
35137        );
35138    });
35139
35140    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
35141    let text = r#"let x = "hello";"#.unindent();
35142
35143    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35146
35147    editor
35148        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35149        .await;
35150
35151    editor.update_in(cx, |editor, window, cx| {
35152        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35153            s.select_display_ranges([
35154                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
35155            ]);
35156        });
35157    });
35158    editor.update(cx, |editor, cx| {
35159        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
35160    });
35161    editor.update_in(cx, |editor, window, cx| {
35162        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35163    });
35164    editor.update(cx, |editor, cx| {
35165        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
35166    });
35167
35168    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
35169    editor.update_in(cx, |editor, window, cx| {
35170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35171            s.select_display_ranges([
35172                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
35173            ]);
35174        });
35175    });
35176    editor.update(cx, |editor, cx| {
35177        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
35178    });
35179    editor.update_in(cx, |editor, window, cx| {
35180        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35181    });
35182    editor.update(cx, |editor, cx| {
35183        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
35184    });
35185
35186    // Test Group 3.1: Create Selection from Cursor (Select to End)
35187    let text = r#"let x = "hello world";"#.unindent();
35188
35189    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35190    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35191    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35192
35193    editor
35194        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35195        .await;
35196
35197    editor.update_in(cx, |editor, window, cx| {
35198        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35199            s.select_display_ranges([
35200                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
35201            ]);
35202        });
35203    });
35204    editor.update(cx, |editor, cx| {
35205        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
35206    });
35207    editor.update_in(cx, |editor, window, cx| {
35208        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35209    });
35210    editor.update(cx, |editor, cx| {
35211        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
35212    });
35213
35214    // Test Group 3.2: Extend Existing Selection (Select to End)
35215    editor.update_in(cx, |editor, window, cx| {
35216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35217            s.select_display_ranges([
35218                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
35219            ]);
35220        });
35221    });
35222    editor.update(cx, |editor, cx| {
35223        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
35224    });
35225    editor.update_in(cx, |editor, window, cx| {
35226        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35227    });
35228    editor.update(cx, |editor, cx| {
35229        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
35230    });
35231
35232    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
35233    let text = r#"let x = "hello"; let y = 42;"#.unindent();
35234
35235    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35236    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35237    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35238
35239    editor
35240        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35241        .await;
35242
35243    editor.update_in(cx, |editor, window, cx| {
35244        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35245            s.select_display_ranges([
35246                // Cursor inside string content
35247                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
35248                // Cursor at let statement semicolon
35249                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
35250                // Cursor inside integer literal
35251                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
35252            ]);
35253        });
35254    });
35255    editor.update(cx, |editor, cx| {
35256        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
35257    });
35258    editor.update_in(cx, |editor, window, cx| {
35259        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35260    });
35261    editor.update(cx, |editor, cx| {
35262        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
35263    });
35264
35265    // Test Group 4.2: Multiple Cursors on Separate Lines
35266    let text = r#"
35267let x = "hello";
35268let y = 42;
35269"#
35270    .unindent();
35271
35272    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35273    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35274    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35275
35276    editor
35277        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35278        .await;
35279
35280    editor.update_in(cx, |editor, window, cx| {
35281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35282            s.select_display_ranges([
35283                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
35284                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
35285            ]);
35286        });
35287    });
35288
35289    editor.update(cx, |editor, cx| {
35290        assert_text_with_selections(
35291            editor,
35292            indoc! {r#"
35293                let x = "helˇlo";
35294                let y = 4ˇ2;
35295            "#},
35296            cx,
35297        );
35298    });
35299    editor.update_in(cx, |editor, window, cx| {
35300        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35301    });
35302    editor.update(cx, |editor, cx| {
35303        assert_text_with_selections(
35304            editor,
35305            indoc! {r#"
35306                let x = "hel«loˇ»";
35307                let y = 4«2ˇ»;
35308            "#},
35309            cx,
35310        );
35311    });
35312
35313    // Test Group 5.1: Nested Function Calls
35314    let text = r#"let result = foo(bar("arg"));"#.unindent();
35315
35316    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35317    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35318    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35319
35320    editor
35321        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35322        .await;
35323
35324    editor.update_in(cx, |editor, window, cx| {
35325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35326            s.select_display_ranges([
35327                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
35328            ]);
35329        });
35330    });
35331    editor.update(cx, |editor, cx| {
35332        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
35333    });
35334    editor.update_in(cx, |editor, window, cx| {
35335        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35336    });
35337    editor.update(cx, |editor, cx| {
35338        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
35339    });
35340    editor.update_in(cx, |editor, window, cx| {
35341        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35342    });
35343    editor.update(cx, |editor, cx| {
35344        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
35345    });
35346    editor.update_in(cx, |editor, window, cx| {
35347        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35348    });
35349    editor.update(cx, |editor, cx| {
35350        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
35351    });
35352
35353    // Test Group 6.1: Block Comments
35354    let text = r#"let x = /* multi
35355                             line
35356                             comment */;"#
35357        .unindent();
35358
35359    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35360    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35361    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35362
35363    editor
35364        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35365        .await;
35366
35367    editor.update_in(cx, |editor, window, cx| {
35368        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35369            s.select_display_ranges([
35370                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
35371            ]);
35372        });
35373    });
35374    editor.update(cx, |editor, cx| {
35375        assert_text_with_selections(
35376            editor,
35377            indoc! {r#"
35378let x = /* multiˇ
35379line
35380comment */;"#},
35381            cx,
35382        );
35383    });
35384    editor.update_in(cx, |editor, window, cx| {
35385        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35386    });
35387    editor.update(cx, |editor, cx| {
35388        assert_text_with_selections(
35389            editor,
35390            indoc! {r#"
35391let x = /* multi«
35392line
35393comment */ˇ»;"#},
35394            cx,
35395        );
35396    });
35397
35398    // Test Group 6.2: Array/Vector Literals
35399    let text = r#"let arr = [1, 2, 3];"#.unindent();
35400
35401    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35402    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35403    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35404
35405    editor
35406        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35407        .await;
35408
35409    editor.update_in(cx, |editor, window, cx| {
35410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35411            s.select_display_ranges([
35412                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
35413            ]);
35414        });
35415    });
35416    editor.update(cx, |editor, cx| {
35417        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
35418    });
35419    editor.update_in(cx, |editor, window, cx| {
35420        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35421    });
35422    editor.update(cx, |editor, cx| {
35423        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
35424    });
35425    editor.update_in(cx, |editor, window, cx| {
35426        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35427    });
35428    editor.update(cx, |editor, cx| {
35429        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
35430    });
35431}
35432
35433#[gpui::test]
35434async fn test_restore_and_next(cx: &mut TestAppContext) {
35435    init_test(cx, |_| {});
35436    let mut cx = EditorTestContext::new(cx).await;
35437
35438    let diff_base = r#"
35439        one
35440        two
35441        three
35442        four
35443        five
35444        "#
35445    .unindent();
35446
35447    cx.set_state(
35448        &r#"
35449        ONE
35450        two
35451        ˇTHREE
35452        four
35453        FIVE
35454        "#
35455        .unindent(),
35456    );
35457    cx.set_head_text(&diff_base);
35458
35459    cx.update_editor(|editor, window, cx| {
35460        editor.set_expand_all_diff_hunks(cx);
35461        editor.restore_and_next(&Default::default(), window, cx);
35462    });
35463    cx.run_until_parked();
35464
35465    cx.assert_state_with_diff(
35466        r#"
35467        - one
35468        + ONE
35469          two
35470          three
35471          four
35472        - ˇfive
35473        + FIVE
35474        "#
35475        .unindent(),
35476    );
35477
35478    cx.update_editor(|editor, window, cx| {
35479        editor.restore_and_next(&Default::default(), window, cx);
35480    });
35481    cx.run_until_parked();
35482
35483    cx.assert_state_with_diff(
35484        r#"
35485        - one
35486        + ONE
35487          two
35488          three
35489          four
35490          ˇfive
35491        "#
35492        .unindent(),
35493    );
35494}
35495
35496#[gpui::test]
35497async fn test_restore_hunk_with_stale_base_text(cx: &mut TestAppContext) {
35498    // Regression test: prepare_restore_change must read base_text from the same
35499    // snapshot the hunk came from, not from the live BufferDiff entity. The live
35500    // entity's base_text may have already been updated asynchronously (e.g.
35501    // because git HEAD changed) while the MultiBufferSnapshot still holds the
35502    // old hunk byte ranges — using both together causes Rope::slice to panic
35503    // when the old range exceeds the new base text length.
35504    init_test(cx, |_| {});
35505    let mut cx = EditorTestContext::new(cx).await;
35506
35507    let long_base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n";
35508    cx.set_state("ˇONE\ntwo\nTHREE\nfour\nFIVE\nsix\nseven\neight\nnine\nten\n");
35509    cx.set_head_text(long_base_text);
35510
35511    let buffer_id = cx.update_buffer(|buffer, _| buffer.remote_id());
35512
35513    // Verify we have hunks from the initial diff.
35514    let has_hunks = cx.update_editor(|editor, window, cx| {
35515        let snapshot = editor.snapshot(window, cx);
35516        let hunks = snapshot
35517            .buffer_snapshot()
35518            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len());
35519        hunks.count() > 0
35520    });
35521    assert!(has_hunks, "should have diff hunks before restoring");
35522
35523    // Now trigger a git HEAD change to a much shorter base text.
35524    // After this, the live BufferDiff entity's base_text buffer will be
35525    // updated synchronously (inside set_snapshot_with_secondary_inner),
35526    // but DiffChanged is deferred until parsing_idle completes.
35527    // We step the executor tick-by-tick to find the window where the
35528    // live base_text is already short but the MultiBuffer snapshot is
35529    // still stale (old hunks + old base_text).
35530    let short_base_text = "short\n";
35531    let fs = cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
35532    let path = cx.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
35533    fs.set_head_for_repo(
35534        &Path::new(path!("/root")).join(".git"),
35535        &[(path.as_unix_str(), short_base_text.to_string())],
35536        "newcommit",
35537    );
35538
35539    // Step the executor tick-by-tick. At each step, check whether the
35540    // race condition exists: live BufferDiff has short base text but
35541    // the MultiBuffer snapshot still has old (long) hunks.
35542    let mut found_race = false;
35543    for _ in 0..200 {
35544        cx.executor().tick();
35545
35546        let race_exists = cx.update_editor(|editor, _window, cx| {
35547            let multi_buffer = editor.buffer().read(cx);
35548            let diff_entity = match multi_buffer.diff_for(buffer_id) {
35549                Some(d) => d,
35550                None => return false,
35551            };
35552            let live_base_len = diff_entity.read(cx).base_text(cx).len();
35553            let snapshot = multi_buffer.snapshot(cx);
35554            let snapshot_base_len = snapshot
35555                .diff_for_buffer_id(buffer_id)
35556                .map(|d| d.base_text().len());
35557            // Race: live base text is shorter than what the snapshot knows.
35558            live_base_len < long_base_text.len() && snapshot_base_len == Some(long_base_text.len())
35559        });
35560
35561        if race_exists {
35562            found_race = true;
35563            // The race window is open: the live entity has new (short) base
35564            // text but the MultiBuffer snapshot still has old hunks with byte
35565            // ranges computed against the old long base text. Attempt restore.
35566            // Without the fix, this panics with "cannot summarize past end of
35567            // rope". With the fix, it reads base_text from the stale snapshot
35568            // (consistent with the stale hunks) and succeeds.
35569            cx.update_editor(|editor, window, cx| {
35570                editor.select_all(&SelectAll, window, cx);
35571                editor.git_restore(&Default::default(), window, cx);
35572            });
35573            break;
35574        }
35575    }
35576
35577    assert!(
35578        found_race,
35579        "failed to observe the race condition between \
35580        live BufferDiff base_text and stale MultiBuffer snapshot; \
35581        the test may need adjustment if the async diff pipeline changed"
35582    );
35583}
35584
35585#[gpui::test]
35586async fn test_align_selections(cx: &mut TestAppContext) {
35587    init_test(cx, |_| {});
35588    let mut cx = EditorTestContext::new(cx).await;
35589
35590    // 1) one cursor, no action
35591    let before = " abc\n  abc\nabc\n     ˇabc";
35592    cx.set_state(before);
35593    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35594    cx.assert_editor_state(before);
35595
35596    // 2) multiple cursors at different rows
35597    let before = indoc!(
35598        r#"
35599            let aˇbc = 123;
35600            let  xˇyz = 456;
35601            let   fˇoo = 789;
35602            let    bˇar = 0;
35603        "#
35604    );
35605    let after = indoc!(
35606        r#"
35607            let a   ˇbc = 123;
35608            let  x  ˇyz = 456;
35609            let   f ˇoo = 789;
35610            let    bˇar = 0;
35611        "#
35612    );
35613    cx.set_state(before);
35614    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35615    cx.assert_editor_state(after);
35616
35617    // 3) multiple selections at different rows
35618    let before = indoc!(
35619        r#"
35620            let «ˇabc» = 123;
35621            let  «ˇxyz» = 456;
35622            let   «ˇfoo» = 789;
35623            let    «ˇbar» = 0;
35624        "#
35625    );
35626    let after = indoc!(
35627        r#"
35628            let    «ˇabc» = 123;
35629            let    «ˇxyz» = 456;
35630            let    «ˇfoo» = 789;
35631            let    «ˇbar» = 0;
35632        "#
35633    );
35634    cx.set_state(before);
35635    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35636    cx.assert_editor_state(after);
35637
35638    // 4) multiple selections at different rows, inverted head
35639    let before = indoc!(
35640        r#"
35641            let    «abcˇ» = 123;
35642            // comment
35643            let  «xyzˇ» = 456;
35644            let «fooˇ» = 789;
35645            let    «barˇ» = 0;
35646        "#
35647    );
35648    let after = indoc!(
35649        r#"
35650            let    «abcˇ» = 123;
35651            // comment
35652            let    «xyzˇ» = 456;
35653            let    «fooˇ» = 789;
35654            let    «barˇ» = 0;
35655        "#
35656    );
35657    cx.set_state(before);
35658    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35659    cx.assert_editor_state(after);
35660}
35661
35662#[gpui::test]
35663async fn test_align_selections_multicolumn(cx: &mut TestAppContext) {
35664    init_test(cx, |_| {});
35665    let mut cx = EditorTestContext::new(cx).await;
35666
35667    // 1) Multicolumn, one non affected editor row
35668    let before = indoc!(
35669        r#"
35670            name «|ˇ» age «|ˇ» height «|ˇ» note
35671            Matthew «|ˇ» 7 «|ˇ» 2333 «|ˇ» smart
35672            Mike «|ˇ» 1234 «|ˇ» 567 «|ˇ» lazy
35673            Anything that is not selected
35674            Miles «|ˇ» 88 «|ˇ» 99 «|ˇ» funny
35675        "#
35676    );
35677    let after = indoc!(
35678        r#"
35679            name    «|ˇ» age  «|ˇ» height «|ˇ» note
35680            Matthew «|ˇ» 7    «|ˇ» 2333   «|ˇ» smart
35681            Mike    «|ˇ» 1234 «|ˇ» 567    «|ˇ» lazy
35682            Anything that is not selected
35683            Miles   «|ˇ» 88   «|ˇ» 99     «|ˇ» funny
35684        "#
35685    );
35686    cx.set_state(before);
35687    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35688    cx.assert_editor_state(after);
35689
35690    // 2) not all alignment rows has the number of alignment columns
35691    let before = indoc!(
35692        r#"
35693            name «|ˇ» age «|ˇ» height
35694            Matthew «|ˇ» 7 «|ˇ» 2333
35695            Mike «|ˇ» 1234
35696            Miles «|ˇ» 88 «|ˇ» 99
35697        "#
35698    );
35699    let after = indoc!(
35700        r#"
35701            name    «|ˇ» age «|ˇ» height
35702            Matthew «|ˇ» 7   «|ˇ» 2333
35703            Mike    «|ˇ» 1234
35704            Miles   «|ˇ» 88  «|ˇ» 99
35705        "#
35706    );
35707    cx.set_state(before);
35708    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35709    cx.assert_editor_state(after);
35710
35711    // 3) A aligned column shall stay aligned
35712    let before = indoc!(
35713        r#"
35714            $ ˇa    ˇa
35715            $  ˇa   ˇa
35716            $   ˇa  ˇa
35717            $    ˇa ˇa
35718        "#
35719    );
35720    let after = indoc!(
35721        r#"
35722            $    ˇa    ˇa
35723            $    ˇa    ˇa
35724            $    ˇa    ˇa
35725            $    ˇa    ˇa
35726        "#
35727    );
35728    cx.set_state(before);
35729    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35730    cx.assert_editor_state(after);
35731}