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, sync::Arc};
   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_syntax_highlighting(rust_lang(), &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_syntax_highlighting(rust_lang(), &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_syntax_highlighting(rust_lang(), &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_syntax_highlighting(rust_lang(), &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
19283#[gpui::test]
19284async fn test_following(cx: &mut TestAppContext) {
19285    init_test(cx, |_| {});
19286
19287    let fs = FakeFs::new(cx.executor());
19288    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
19289
19290    let buffer = project.update(cx, |project, cx| {
19291        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
19292        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
19293    });
19294    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
19295    let follower = cx.update(|cx| {
19296        cx.open_window(
19297            WindowOptions {
19298                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
19299                    gpui::Point::new(px(0.), px(0.)),
19300                    gpui::Point::new(px(10.), px(80.)),
19301                ))),
19302                ..Default::default()
19303            },
19304            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
19305        )
19306        .unwrap()
19307    });
19308
19309    let is_still_following = Rc::new(RefCell::new(true));
19310    let follower_edit_event_count = Rc::new(RefCell::new(0));
19311    let pending_update = Rc::new(RefCell::new(None));
19312    let leader_entity = leader.root(cx).unwrap();
19313    let follower_entity = follower.root(cx).unwrap();
19314    _ = follower.update(cx, {
19315        let update = pending_update.clone();
19316        let is_still_following = is_still_following.clone();
19317        let follower_edit_event_count = follower_edit_event_count.clone();
19318        |_, window, cx| {
19319            cx.subscribe_in(
19320                &leader_entity,
19321                window,
19322                move |_, leader, event, window, cx| {
19323                    leader.update(cx, |leader, cx| {
19324                        leader.add_event_to_update_proto(
19325                            event,
19326                            &mut update.borrow_mut(),
19327                            window,
19328                            cx,
19329                        );
19330                    });
19331                },
19332            )
19333            .detach();
19334
19335            cx.subscribe_in(
19336                &follower_entity,
19337                window,
19338                move |_, _, event: &EditorEvent, _window, _cx| {
19339                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
19340                        *is_still_following.borrow_mut() = false;
19341                    }
19342
19343                    if let EditorEvent::BufferEdited = event {
19344                        *follower_edit_event_count.borrow_mut() += 1;
19345                    }
19346                },
19347            )
19348            .detach();
19349        }
19350    });
19351
19352    // Update the selections only
19353    _ = leader.update(cx, |leader, window, cx| {
19354        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19355            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
19356        });
19357    });
19358    follower
19359        .update(cx, |follower, window, cx| {
19360            follower.apply_update_proto(
19361                &project,
19362                pending_update.borrow_mut().take().unwrap(),
19363                window,
19364                cx,
19365            )
19366        })
19367        .unwrap()
19368        .await
19369        .unwrap();
19370    _ = follower.update(cx, |follower, _, cx| {
19371        assert_eq!(
19372            follower.selections.ranges(&follower.display_snapshot(cx)),
19373            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
19374        );
19375    });
19376    assert!(*is_still_following.borrow());
19377    assert_eq!(*follower_edit_event_count.borrow(), 0);
19378
19379    // Update the scroll position only
19380    _ = leader.update(cx, |leader, window, cx| {
19381        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
19382    });
19383    follower
19384        .update(cx, |follower, window, cx| {
19385            follower.apply_update_proto(
19386                &project,
19387                pending_update.borrow_mut().take().unwrap(),
19388                window,
19389                cx,
19390            )
19391        })
19392        .unwrap()
19393        .await
19394        .unwrap();
19395    assert_eq!(
19396        follower
19397            .update(cx, |follower, _, cx| follower.scroll_position(cx))
19398            .unwrap(),
19399        gpui::Point::new(1.5, 3.5)
19400    );
19401    assert!(*is_still_following.borrow());
19402    assert_eq!(*follower_edit_event_count.borrow(), 0);
19403
19404    // Update the selections and scroll position. The follower's scroll position is updated
19405    // via autoscroll, not via the leader's exact scroll position.
19406    _ = leader.update(cx, |leader, window, cx| {
19407        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19408            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
19409        });
19410        leader.request_autoscroll(Autoscroll::newest(), cx);
19411        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
19412    });
19413    follower
19414        .update(cx, |follower, window, cx| {
19415            follower.apply_update_proto(
19416                &project,
19417                pending_update.borrow_mut().take().unwrap(),
19418                window,
19419                cx,
19420            )
19421        })
19422        .unwrap()
19423        .await
19424        .unwrap();
19425    _ = follower.update(cx, |follower, _, cx| {
19426        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
19427        assert_eq!(
19428            follower.selections.ranges(&follower.display_snapshot(cx)),
19429            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
19430        );
19431    });
19432    assert!(*is_still_following.borrow());
19433
19434    // Creating a pending selection that precedes another selection
19435    _ = leader.update(cx, |leader, window, cx| {
19436        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19437            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
19438        });
19439        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, 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!(
19455            follower.selections.ranges(&follower.display_snapshot(cx)),
19456            vec![
19457                MultiBufferOffset(0)..MultiBufferOffset(0),
19458                MultiBufferOffset(1)..MultiBufferOffset(1)
19459            ]
19460        );
19461    });
19462    assert!(*is_still_following.borrow());
19463
19464    // Extend the pending selection so that it surrounds another selection
19465    _ = leader.update(cx, |leader, window, cx| {
19466        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
19467    });
19468    follower
19469        .update(cx, |follower, window, cx| {
19470            follower.apply_update_proto(
19471                &project,
19472                pending_update.borrow_mut().take().unwrap(),
19473                window,
19474                cx,
19475            )
19476        })
19477        .unwrap()
19478        .await
19479        .unwrap();
19480    _ = follower.update(cx, |follower, _, cx| {
19481        assert_eq!(
19482            follower.selections.ranges(&follower.display_snapshot(cx)),
19483            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
19484        );
19485    });
19486
19487    // Scrolling locally breaks the follow
19488    _ = follower.update(cx, |follower, window, cx| {
19489        let top_anchor = follower
19490            .buffer()
19491            .read(cx)
19492            .read(cx)
19493            .anchor_after(MultiBufferOffset(0));
19494        follower.set_scroll_anchor(
19495            ScrollAnchor {
19496                anchor: top_anchor,
19497                offset: gpui::Point::new(0.0, 0.5),
19498            },
19499            window,
19500            cx,
19501        );
19502    });
19503    assert!(!(*is_still_following.borrow()));
19504}
19505
19506#[gpui::test]
19507async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
19508    init_test(cx, |_| {});
19509
19510    let fs = FakeFs::new(cx.executor());
19511    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
19512    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19513    let workspace = window
19514        .read_with(cx, |mw, _| mw.workspace().clone())
19515        .unwrap();
19516    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
19517
19518    let cx = &mut VisualTestContext::from_window(*window, cx);
19519
19520    let leader = pane.update_in(cx, |_, window, cx| {
19521        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
19522        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
19523    });
19524
19525    // Start following the editor when it has no excerpts.
19526    let mut state_message =
19527        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
19528    let workspace_entity = workspace.clone();
19529    let follower_1 = cx
19530        .update_window(*window, |_, window, cx| {
19531            Editor::from_state_proto(
19532                workspace_entity,
19533                ViewId {
19534                    creator: CollaboratorId::PeerId(PeerId::default()),
19535                    id: 0,
19536                },
19537                &mut state_message,
19538                window,
19539                cx,
19540            )
19541        })
19542        .unwrap()
19543        .unwrap()
19544        .await
19545        .unwrap();
19546
19547    let update_message = Rc::new(RefCell::new(None));
19548    follower_1.update_in(cx, {
19549        let update = update_message.clone();
19550        |_, window, cx| {
19551            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
19552                leader.update(cx, |leader, cx| {
19553                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
19554                });
19555            })
19556            .detach();
19557        }
19558    });
19559
19560    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
19561        (
19562            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),
19563            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),
19564        )
19565    });
19566
19567    // Insert some excerpts.
19568    leader.update(cx, |leader, cx| {
19569        leader.buffer.update(cx, |multibuffer, cx| {
19570            multibuffer.set_excerpts_for_path(
19571                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
19572                buffer_1.clone(),
19573                vec![
19574                    Point::row_range(0..3),
19575                    Point::row_range(1..6),
19576                    Point::row_range(12..15),
19577                ],
19578                0,
19579                cx,
19580            );
19581            multibuffer.set_excerpts_for_path(
19582                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
19583                buffer_2.clone(),
19584                vec![Point::row_range(0..6), Point::row_range(8..12)],
19585                0,
19586                cx,
19587            );
19588        });
19589    });
19590
19591    // Apply the update of adding the excerpts.
19592    follower_1
19593        .update_in(cx, |follower, window, cx| {
19594            follower.apply_update_proto(
19595                &project,
19596                update_message.borrow().clone().unwrap(),
19597                window,
19598                cx,
19599            )
19600        })
19601        .await
19602        .unwrap();
19603    assert_eq!(
19604        follower_1.update(cx, |editor, cx| editor.text(cx)),
19605        leader.update(cx, |editor, cx| editor.text(cx))
19606    );
19607    update_message.borrow_mut().take();
19608
19609    // Start following separately after it already has excerpts.
19610    let mut state_message =
19611        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
19612    let workspace_entity = workspace.clone();
19613    let follower_2 = cx
19614        .update_window(*window, |_, window, cx| {
19615            Editor::from_state_proto(
19616                workspace_entity,
19617                ViewId {
19618                    creator: CollaboratorId::PeerId(PeerId::default()),
19619                    id: 0,
19620                },
19621                &mut state_message,
19622                window,
19623                cx,
19624            )
19625        })
19626        .unwrap()
19627        .unwrap()
19628        .await
19629        .unwrap();
19630    assert_eq!(
19631        follower_2.update(cx, |editor, cx| editor.text(cx)),
19632        leader.update(cx, |editor, cx| editor.text(cx))
19633    );
19634
19635    // Remove some excerpts.
19636    leader.update(cx, |leader, cx| {
19637        leader.buffer.update(cx, |multibuffer, cx| {
19638            multibuffer.remove_excerpts(
19639                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
19640                cx,
19641            );
19642        });
19643    });
19644
19645    // Apply the update of removing the excerpts.
19646    follower_1
19647        .update_in(cx, |follower, window, cx| {
19648            follower.apply_update_proto(
19649                &project,
19650                update_message.borrow().clone().unwrap(),
19651                window,
19652                cx,
19653            )
19654        })
19655        .await
19656        .unwrap();
19657    follower_2
19658        .update_in(cx, |follower, window, cx| {
19659            follower.apply_update_proto(
19660                &project,
19661                update_message.borrow().clone().unwrap(),
19662                window,
19663                cx,
19664            )
19665        })
19666        .await
19667        .unwrap();
19668    update_message.borrow_mut().take();
19669    assert_eq!(
19670        follower_1.update(cx, |editor, cx| editor.text(cx)),
19671        leader.update(cx, |editor, cx| editor.text(cx))
19672    );
19673}
19674
19675#[gpui::test]
19676async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19677    init_test(cx, |_| {});
19678
19679    let mut cx = EditorTestContext::new(cx).await;
19680    let lsp_store =
19681        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
19682
19683    cx.set_state(indoc! {"
19684        ˇfn func(abc def: i32) -> u32 {
19685        }
19686    "});
19687
19688    cx.update(|_, cx| {
19689        lsp_store.update(cx, |lsp_store, cx| {
19690            lsp_store
19691                .update_diagnostics(
19692                    LanguageServerId(0),
19693                    lsp::PublishDiagnosticsParams {
19694                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
19695                        version: None,
19696                        diagnostics: vec![
19697                            lsp::Diagnostic {
19698                                range: lsp::Range::new(
19699                                    lsp::Position::new(0, 11),
19700                                    lsp::Position::new(0, 12),
19701                                ),
19702                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19703                                ..Default::default()
19704                            },
19705                            lsp::Diagnostic {
19706                                range: lsp::Range::new(
19707                                    lsp::Position::new(0, 12),
19708                                    lsp::Position::new(0, 15),
19709                                ),
19710                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19711                                ..Default::default()
19712                            },
19713                            lsp::Diagnostic {
19714                                range: lsp::Range::new(
19715                                    lsp::Position::new(0, 25),
19716                                    lsp::Position::new(0, 28),
19717                                ),
19718                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19719                                ..Default::default()
19720                            },
19721                        ],
19722                    },
19723                    None,
19724                    DiagnosticSourceKind::Pushed,
19725                    &[],
19726                    cx,
19727                )
19728                .unwrap()
19729        });
19730    });
19731
19732    executor.run_until_parked();
19733
19734    cx.update_editor(|editor, window, cx| {
19735        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19736    });
19737
19738    cx.assert_editor_state(indoc! {"
19739        fn func(abc def: i32) -> ˇu32 {
19740        }
19741    "});
19742
19743    cx.update_editor(|editor, window, cx| {
19744        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19745    });
19746
19747    cx.assert_editor_state(indoc! {"
19748        fn func(abc ˇdef: i32) -> u32 {
19749        }
19750    "});
19751
19752    cx.update_editor(|editor, window, cx| {
19753        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19754    });
19755
19756    cx.assert_editor_state(indoc! {"
19757        fn func(abcˇ def: i32) -> u32 {
19758        }
19759    "});
19760
19761    cx.update_editor(|editor, window, cx| {
19762        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19763    });
19764
19765    cx.assert_editor_state(indoc! {"
19766        fn func(abc def: i32) -> ˇu32 {
19767        }
19768    "});
19769}
19770
19771#[gpui::test]
19772async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19773    init_test(cx, |_| {});
19774
19775    let mut cx = EditorTestContext::new(cx).await;
19776
19777    let diff_base = r#"
19778        use some::mod;
19779
19780        const A: u32 = 42;
19781
19782        fn main() {
19783            println!("hello");
19784
19785            println!("world");
19786        }
19787        "#
19788    .unindent();
19789
19790    // Edits are modified, removed, modified, added
19791    cx.set_state(
19792        &r#"
19793        use some::modified;
19794
19795        ˇ
19796        fn main() {
19797            println!("hello there");
19798
19799            println!("around the");
19800            println!("world");
19801        }
19802        "#
19803        .unindent(),
19804    );
19805
19806    cx.set_head_text(&diff_base);
19807    executor.run_until_parked();
19808
19809    cx.update_editor(|editor, window, cx| {
19810        //Wrap around the bottom of the buffer
19811        for _ in 0..3 {
19812            editor.go_to_next_hunk(&GoToHunk, window, cx);
19813        }
19814    });
19815
19816    cx.assert_editor_state(
19817        &r#"
19818        ˇuse some::modified;
19819
19820
19821        fn main() {
19822            println!("hello there");
19823
19824            println!("around the");
19825            println!("world");
19826        }
19827        "#
19828        .unindent(),
19829    );
19830
19831    cx.update_editor(|editor, window, cx| {
19832        //Wrap around the top of the buffer
19833        for _ in 0..2 {
19834            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19835        }
19836    });
19837
19838    cx.assert_editor_state(
19839        &r#"
19840        use some::modified;
19841
19842
19843        fn main() {
19844        ˇ    println!("hello there");
19845
19846            println!("around the");
19847            println!("world");
19848        }
19849        "#
19850        .unindent(),
19851    );
19852
19853    cx.update_editor(|editor, window, cx| {
19854        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19855    });
19856
19857    cx.assert_editor_state(
19858        &r#"
19859        use some::modified;
19860
19861        ˇ
19862        fn main() {
19863            println!("hello there");
19864
19865            println!("around the");
19866            println!("world");
19867        }
19868        "#
19869        .unindent(),
19870    );
19871
19872    cx.update_editor(|editor, window, cx| {
19873        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19874    });
19875
19876    cx.assert_editor_state(
19877        &r#"
19878        ˇuse some::modified;
19879
19880
19881        fn main() {
19882            println!("hello there");
19883
19884            println!("around the");
19885            println!("world");
19886        }
19887        "#
19888        .unindent(),
19889    );
19890
19891    cx.update_editor(|editor, window, cx| {
19892        for _ in 0..2 {
19893            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19894        }
19895    });
19896
19897    cx.assert_editor_state(
19898        &r#"
19899        use some::modified;
19900
19901
19902        fn main() {
19903        ˇ    println!("hello there");
19904
19905            println!("around the");
19906            println!("world");
19907        }
19908        "#
19909        .unindent(),
19910    );
19911
19912    cx.update_editor(|editor, window, cx| {
19913        editor.fold(&Fold, window, cx);
19914    });
19915
19916    cx.update_editor(|editor, window, cx| {
19917        editor.go_to_next_hunk(&GoToHunk, window, cx);
19918    });
19919
19920    cx.assert_editor_state(
19921        &r#"
19922        ˇuse some::modified;
19923
19924
19925        fn main() {
19926            println!("hello there");
19927
19928            println!("around the");
19929            println!("world");
19930        }
19931        "#
19932        .unindent(),
19933    );
19934}
19935
19936#[test]
19937fn test_split_words() {
19938    fn split(text: &str) -> Vec<&str> {
19939        split_words(text).collect()
19940    }
19941
19942    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
19943    assert_eq!(split("hello_world"), &["hello_", "world"]);
19944    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
19945    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
19946    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
19947    assert_eq!(split("helloworld"), &["helloworld"]);
19948
19949    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
19950}
19951
19952#[test]
19953fn test_split_words_for_snippet_prefix() {
19954    fn split(text: &str) -> Vec<&str> {
19955        snippet_candidate_suffixes(text, &|c| c.is_alphanumeric() || c == '_').collect()
19956    }
19957
19958    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
19959    assert_eq!(split("hello_world"), &["hello_world"]);
19960    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
19961    assert_eq!(split("Hello_World"), &["Hello_World"]);
19962    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
19963    assert_eq!(split("helloworld"), &["helloworld"]);
19964    assert_eq!(
19965        split("this@is!@#$^many   . symbols"),
19966        &[
19967            "symbols",
19968            " symbols",
19969            ". symbols",
19970            " . symbols",
19971            "  . symbols",
19972            "   . symbols",
19973            "many   . symbols",
19974            "^many   . symbols",
19975            "$^many   . symbols",
19976            "#$^many   . symbols",
19977            "@#$^many   . symbols",
19978            "!@#$^many   . symbols",
19979            "is!@#$^many   . symbols",
19980            "@is!@#$^many   . symbols",
19981            "this@is!@#$^many   . symbols",
19982        ],
19983    );
19984    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
19985}
19986
19987#[gpui::test]
19988async fn test_move_to_syntax_node_relative_jumps(tcx: &mut TestAppContext) {
19989    init_test(tcx, |_| {});
19990
19991    let mut cx = EditorLspTestContext::new(
19992        Arc::into_inner(markdown_lang()).unwrap(),
19993        Default::default(),
19994        tcx,
19995    )
19996    .await;
19997
19998    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
19999        let _state_context = cx.set_state(before);
20000        cx.run_until_parked();
20001        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
20002            .await
20003            .unwrap();
20004        cx.run_until_parked();
20005        cx.assert_editor_state(after);
20006    }
20007
20008    const ABOVE: i8 = -1;
20009    const BELOW: i8 = 1;
20010
20011    assert(
20012        ABOVE,
20013        indoc! {"
20014        # Foo
20015
20016        ˇFoo foo foo
20017
20018        # Bar
20019
20020        Bar bar bar
20021    "},
20022        indoc! {"
20023        ˇ# Foo
20024
20025        Foo foo foo
20026
20027        # Bar
20028
20029        Bar bar bar
20030    "},
20031        &mut cx,
20032    )
20033    .await;
20034
20035    assert(
20036        ABOVE,
20037        indoc! {"
20038        ˇ# Foo
20039
20040        Foo foo foo
20041
20042        # Bar
20043
20044        Bar bar bar
20045    "},
20046        indoc! {"
20047        ˇ# Foo
20048
20049        Foo foo foo
20050
20051        # Bar
20052
20053        Bar bar bar
20054    "},
20055        &mut cx,
20056    )
20057    .await;
20058
20059    assert(
20060        BELOW,
20061        indoc! {"
20062        ˇ# Foo
20063
20064        Foo foo foo
20065
20066        # Bar
20067
20068        Bar bar bar
20069    "},
20070        indoc! {"
20071        # Foo
20072
20073        Foo foo foo
20074
20075        ˇ# Bar
20076
20077        Bar bar bar
20078    "},
20079        &mut cx,
20080    )
20081    .await;
20082
20083    assert(
20084        BELOW,
20085        indoc! {"
20086        # Foo
20087
20088        ˇFoo foo foo
20089
20090        # Bar
20091
20092        Bar bar bar
20093    "},
20094        indoc! {"
20095        # Foo
20096
20097        Foo foo foo
20098
20099        ˇ# Bar
20100
20101        Bar bar bar
20102    "},
20103        &mut cx,
20104    )
20105    .await;
20106
20107    assert(
20108        BELOW,
20109        indoc! {"
20110        # Foo
20111
20112        Foo foo foo
20113
20114        ˇ# Bar
20115
20116        Bar bar bar
20117    "},
20118        indoc! {"
20119        # Foo
20120
20121        Foo foo foo
20122
20123        ˇ# Bar
20124
20125        Bar bar bar
20126    "},
20127        &mut cx,
20128    )
20129    .await;
20130
20131    assert(
20132        BELOW,
20133        indoc! {"
20134        # Foo
20135
20136        Foo foo foo
20137
20138        # Bar
20139        ˇ
20140        Bar bar bar
20141    "},
20142        indoc! {"
20143        # Foo
20144
20145        Foo foo foo
20146
20147        # Bar
20148        ˇ
20149        Bar bar bar
20150    "},
20151        &mut cx,
20152    )
20153    .await;
20154}
20155
20156#[gpui::test]
20157async fn test_move_to_syntax_node_relative_dead_zone(tcx: &mut TestAppContext) {
20158    init_test(tcx, |_| {});
20159
20160    let mut cx = EditorLspTestContext::new(
20161        Arc::into_inner(rust_lang()).unwrap(),
20162        Default::default(),
20163        tcx,
20164    )
20165    .await;
20166
20167    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
20168        let _state_context = cx.set_state(before);
20169        cx.run_until_parked();
20170        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
20171            .await
20172            .unwrap();
20173        cx.run_until_parked();
20174        cx.assert_editor_state(after);
20175    }
20176
20177    const ABOVE: i8 = -1;
20178    const BELOW: i8 = 1;
20179
20180    assert(
20181        ABOVE,
20182        indoc! {"
20183        fn foo() {
20184            // foo fn
20185        }
20186
20187        ˇ// this zone is not inside any top level outline node
20188
20189        fn bar() {
20190            // bar fn
20191            let _ = 2;
20192        }
20193    "},
20194        indoc! {"
20195        ˇfn foo() {
20196            // foo fn
20197        }
20198
20199        // this zone is not inside any top level outline node
20200
20201        fn bar() {
20202            // bar fn
20203            let _ = 2;
20204        }
20205    "},
20206        &mut cx,
20207    )
20208    .await;
20209
20210    assert(
20211        BELOW,
20212        indoc! {"
20213        fn foo() {
20214            // foo fn
20215        }
20216
20217        ˇ// this zone is not inside any top level outline node
20218
20219        fn bar() {
20220            // bar fn
20221            let _ = 2;
20222        }
20223    "},
20224        indoc! {"
20225        fn foo() {
20226            // foo fn
20227        }
20228
20229        // this zone is not inside any top level outline node
20230
20231        ˇfn bar() {
20232            // bar fn
20233            let _ = 2;
20234        }
20235    "},
20236        &mut cx,
20237    )
20238    .await;
20239}
20240
20241#[gpui::test]
20242async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
20243    init_test(cx, |_| {});
20244
20245    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
20246
20247    #[track_caller]
20248    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
20249        let _state_context = cx.set_state(before);
20250        cx.run_until_parked();
20251        cx.update_editor(|editor, window, cx| {
20252            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
20253        });
20254        cx.run_until_parked();
20255        cx.assert_editor_state(after);
20256    }
20257
20258    // Outside bracket jumps to outside of matching bracket
20259    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
20260    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
20261
20262    // Inside bracket jumps to inside of matching bracket
20263    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
20264    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
20265
20266    // When outside a bracket and inside, favor jumping to the inside bracket
20267    assert(
20268        "console.log('foo', [1, 2, 3]ˇ);",
20269        "console.log('foo', ˇ[1, 2, 3]);",
20270        &mut cx,
20271    );
20272    assert(
20273        "console.log(ˇ'foo', [1, 2, 3]);",
20274        "console.log('foo'ˇ, [1, 2, 3]);",
20275        &mut cx,
20276    );
20277
20278    // Bias forward if two options are equally likely
20279    assert(
20280        "let result = curried_fun()ˇ();",
20281        "let result = curried_fun()()ˇ;",
20282        &mut cx,
20283    );
20284
20285    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
20286    assert(
20287        indoc! {"
20288            function test() {
20289                console.log('test')ˇ
20290            }"},
20291        indoc! {"
20292            function test() {
20293                console.logˇ('test')
20294            }"},
20295        &mut cx,
20296    );
20297}
20298
20299#[gpui::test]
20300async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
20301    init_test(cx, |_| {});
20302    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
20303    language_registry.add(markdown_lang());
20304    language_registry.add(rust_lang());
20305    let buffer = cx.new(|cx| {
20306        let mut buffer = language::Buffer::local(
20307            indoc! {"
20308            ```rs
20309            impl Worktree {
20310                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20311                }
20312            }
20313            ```
20314        "},
20315            cx,
20316        );
20317        buffer.set_language_registry(language_registry.clone());
20318        buffer.set_language(Some(markdown_lang()), cx);
20319        buffer
20320    });
20321    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20322    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
20323    cx.executor().run_until_parked();
20324    _ = editor.update(cx, |editor, window, cx| {
20325        // Case 1: Test outer enclosing brackets
20326        select_ranges(
20327            editor,
20328            &indoc! {"
20329                ```rs
20330                impl Worktree {
20331                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20332                    }
2033320334                ```
20335            "},
20336            window,
20337            cx,
20338        );
20339        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
20340        assert_text_with_selections(
20341            editor,
20342            &indoc! {"
20343                ```rs
20344                impl Worktree ˇ{
20345                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
20346                    }
20347                }
20348                ```
20349            "},
20350            cx,
20351        );
20352        // Case 2: Test inner enclosing brackets
20353        select_ranges(
20354            editor,
20355            &indoc! {"
20356                ```rs
20357                impl Worktree {
20358                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
2035920360                }
20361                ```
20362            "},
20363            window,
20364            cx,
20365        );
20366        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
20367        assert_text_with_selections(
20368            editor,
20369            &indoc! {"
20370                ```rs
20371                impl Worktree {
20372                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
20373                    }
20374                }
20375                ```
20376            "},
20377            cx,
20378        );
20379    });
20380}
20381
20382#[gpui::test]
20383async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
20384    init_test(cx, |_| {});
20385
20386    let fs = FakeFs::new(cx.executor());
20387    fs.insert_tree(
20388        path!("/a"),
20389        json!({
20390            "main.rs": "fn main() { let a = 5; }",
20391            "other.rs": "// Test file",
20392        }),
20393    )
20394    .await;
20395    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20396
20397    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20398    language_registry.add(Arc::new(Language::new(
20399        LanguageConfig {
20400            name: "Rust".into(),
20401            matcher: LanguageMatcher {
20402                path_suffixes: vec!["rs".to_string()],
20403                ..Default::default()
20404            },
20405            brackets: BracketPairConfig {
20406                pairs: vec![BracketPair {
20407                    start: "{".to_string(),
20408                    end: "}".to_string(),
20409                    close: true,
20410                    surround: true,
20411                    newline: true,
20412                }],
20413                disabled_scopes_by_bracket_ix: Vec::new(),
20414            },
20415            ..Default::default()
20416        },
20417        Some(tree_sitter_rust::LANGUAGE.into()),
20418    )));
20419    let mut fake_servers = language_registry.register_fake_lsp(
20420        "Rust",
20421        FakeLspAdapter {
20422            capabilities: lsp::ServerCapabilities {
20423                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
20424                    first_trigger_character: "{".to_string(),
20425                    more_trigger_character: None,
20426                }),
20427                ..Default::default()
20428            },
20429            ..Default::default()
20430        },
20431    );
20432
20433    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
20434    let workspace = window
20435        .read_with(cx, |mw, _| mw.workspace().clone())
20436        .unwrap();
20437
20438    let cx = &mut VisualTestContext::from_window(*window, cx);
20439
20440    let worktree_id = workspace.update_in(cx, |workspace, _, cx| {
20441        workspace.project().update(cx, |project, cx| {
20442            project.worktrees(cx).next().unwrap().read(cx).id()
20443        })
20444    });
20445
20446    let buffer = project
20447        .update(cx, |project, cx| {
20448            project.open_local_buffer(path!("/a/main.rs"), cx)
20449        })
20450        .await
20451        .unwrap();
20452    let editor_handle = workspace
20453        .update_in(cx, |workspace, window, cx| {
20454            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
20455        })
20456        .await
20457        .unwrap()
20458        .downcast::<Editor>()
20459        .unwrap();
20460
20461    let fake_server = fake_servers.next().await.unwrap();
20462
20463    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
20464        |params, _| async move {
20465            assert_eq!(
20466                params.text_document_position.text_document.uri,
20467                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
20468            );
20469            assert_eq!(
20470                params.text_document_position.position,
20471                lsp::Position::new(0, 21),
20472            );
20473
20474            Ok(Some(vec![lsp::TextEdit {
20475                new_text: "]".to_string(),
20476                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20477            }]))
20478        },
20479    );
20480
20481    editor_handle.update_in(cx, |editor, window, cx| {
20482        window.focus(&editor.focus_handle(cx), cx);
20483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20484            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
20485        });
20486        editor.handle_input("{", window, cx);
20487    });
20488
20489    cx.executor().run_until_parked();
20490
20491    buffer.update(cx, |buffer, _| {
20492        assert_eq!(
20493            buffer.text(),
20494            "fn main() { let a = {5}; }",
20495            "No extra braces from on type formatting should appear in the buffer"
20496        )
20497    });
20498}
20499
20500#[gpui::test(iterations = 20, seeds(31))]
20501async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
20502    init_test(cx, |_| {});
20503
20504    let mut cx = EditorLspTestContext::new_rust(
20505        lsp::ServerCapabilities {
20506            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
20507                first_trigger_character: ".".to_string(),
20508                more_trigger_character: None,
20509            }),
20510            ..Default::default()
20511        },
20512        cx,
20513    )
20514    .await;
20515
20516    cx.update_buffer(|buffer, _| {
20517        // This causes autoindent to be async.
20518        buffer.set_sync_parse_timeout(None)
20519    });
20520
20521    cx.set_state("fn c() {\n    d()ˇ\n}\n");
20522    cx.simulate_keystroke("\n");
20523    cx.run_until_parked();
20524
20525    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
20526    let mut request =
20527        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
20528            let buffer_cloned = buffer_cloned.clone();
20529            async move {
20530                buffer_cloned.update(&mut cx, |buffer, _| {
20531                    assert_eq!(
20532                        buffer.text(),
20533                        "fn c() {\n    d()\n        .\n}\n",
20534                        "OnTypeFormatting should triggered after autoindent applied"
20535                    )
20536                });
20537
20538                Ok(Some(vec![]))
20539            }
20540        });
20541
20542    cx.simulate_keystroke(".");
20543    cx.run_until_parked();
20544
20545    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
20546    assert!(request.next().await.is_some());
20547    request.close();
20548    assert!(request.next().await.is_none());
20549}
20550
20551#[gpui::test]
20552async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
20553    init_test(cx, |_| {});
20554
20555    let fs = FakeFs::new(cx.executor());
20556    fs.insert_tree(
20557        path!("/a"),
20558        json!({
20559            "main.rs": "fn main() { let a = 5; }",
20560            "other.rs": "// Test file",
20561        }),
20562    )
20563    .await;
20564
20565    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20566
20567    let server_restarts = Arc::new(AtomicUsize::new(0));
20568    let closure_restarts = Arc::clone(&server_restarts);
20569    let language_server_name = "test language server";
20570    let language_name: LanguageName = "Rust".into();
20571
20572    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20573    language_registry.add(Arc::new(Language::new(
20574        LanguageConfig {
20575            name: language_name.clone(),
20576            matcher: LanguageMatcher {
20577                path_suffixes: vec!["rs".to_string()],
20578                ..Default::default()
20579            },
20580            ..Default::default()
20581        },
20582        Some(tree_sitter_rust::LANGUAGE.into()),
20583    )));
20584    let mut fake_servers = language_registry.register_fake_lsp(
20585        "Rust",
20586        FakeLspAdapter {
20587            name: language_server_name,
20588            initialization_options: Some(json!({
20589                "testOptionValue": true
20590            })),
20591            initializer: Some(Box::new(move |fake_server| {
20592                let task_restarts = Arc::clone(&closure_restarts);
20593                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
20594                    task_restarts.fetch_add(1, atomic::Ordering::Release);
20595                    futures::future::ready(Ok(()))
20596                });
20597            })),
20598            ..Default::default()
20599        },
20600    );
20601
20602    let _window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
20603    let _buffer = project
20604        .update(cx, |project, cx| {
20605            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
20606        })
20607        .await
20608        .unwrap();
20609    let _fake_server = fake_servers.next().await.unwrap();
20610    update_test_language_settings(cx, &|language_settings| {
20611        language_settings.languages.0.insert(
20612            language_name.clone().0.to_string(),
20613            LanguageSettingsContent {
20614                tab_size: NonZeroU32::new(8),
20615                ..Default::default()
20616            },
20617        );
20618    });
20619    cx.executor().run_until_parked();
20620    assert_eq!(
20621        server_restarts.load(atomic::Ordering::Acquire),
20622        0,
20623        "Should not restart LSP server on an unrelated change"
20624    );
20625
20626    update_test_project_settings(cx, &|project_settings| {
20627        project_settings.lsp.0.insert(
20628            "Some other server name".into(),
20629            LspSettings {
20630                binary: None,
20631                settings: None,
20632                initialization_options: Some(json!({
20633                    "some other init value": false
20634                })),
20635                enable_lsp_tasks: false,
20636                fetch: None,
20637            },
20638        );
20639    });
20640    cx.executor().run_until_parked();
20641    assert_eq!(
20642        server_restarts.load(atomic::Ordering::Acquire),
20643        0,
20644        "Should not restart LSP server on an unrelated LSP settings change"
20645    );
20646
20647    update_test_project_settings(cx, &|project_settings| {
20648        project_settings.lsp.0.insert(
20649            language_server_name.into(),
20650            LspSettings {
20651                binary: None,
20652                settings: None,
20653                initialization_options: Some(json!({
20654                    "anotherInitValue": false
20655                })),
20656                enable_lsp_tasks: false,
20657                fetch: None,
20658            },
20659        );
20660    });
20661    cx.executor().run_until_parked();
20662    assert_eq!(
20663        server_restarts.load(atomic::Ordering::Acquire),
20664        1,
20665        "Should restart LSP server on a related LSP settings change"
20666    );
20667
20668    update_test_project_settings(cx, &|project_settings| {
20669        project_settings.lsp.0.insert(
20670            language_server_name.into(),
20671            LspSettings {
20672                binary: None,
20673                settings: None,
20674                initialization_options: Some(json!({
20675                    "anotherInitValue": false
20676                })),
20677                enable_lsp_tasks: false,
20678                fetch: None,
20679            },
20680        );
20681    });
20682    cx.executor().run_until_parked();
20683    assert_eq!(
20684        server_restarts.load(atomic::Ordering::Acquire),
20685        1,
20686        "Should not restart LSP server on a related LSP settings change that is the same"
20687    );
20688
20689    update_test_project_settings(cx, &|project_settings| {
20690        project_settings.lsp.0.insert(
20691            language_server_name.into(),
20692            LspSettings {
20693                binary: None,
20694                settings: None,
20695                initialization_options: None,
20696                enable_lsp_tasks: false,
20697                fetch: None,
20698            },
20699        );
20700    });
20701    cx.executor().run_until_parked();
20702    assert_eq!(
20703        server_restarts.load(atomic::Ordering::Acquire),
20704        2,
20705        "Should restart LSP server on another related LSP settings change"
20706    );
20707}
20708
20709#[gpui::test]
20710async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
20711    init_test(cx, |_| {});
20712
20713    let mut cx = EditorLspTestContext::new_rust(
20714        lsp::ServerCapabilities {
20715            completion_provider: Some(lsp::CompletionOptions {
20716                trigger_characters: Some(vec![".".to_string()]),
20717                resolve_provider: Some(true),
20718                ..Default::default()
20719            }),
20720            ..Default::default()
20721        },
20722        cx,
20723    )
20724    .await;
20725
20726    cx.set_state("fn main() { let a = 2ˇ; }");
20727    cx.simulate_keystroke(".");
20728    let completion_item = lsp::CompletionItem {
20729        label: "some".into(),
20730        kind: Some(lsp::CompletionItemKind::SNIPPET),
20731        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
20732        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
20733            kind: lsp::MarkupKind::Markdown,
20734            value: "```rust\nSome(2)\n```".to_string(),
20735        })),
20736        deprecated: Some(false),
20737        sort_text: Some("fffffff2".to_string()),
20738        filter_text: Some("some".to_string()),
20739        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
20740        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20741            range: lsp::Range {
20742                start: lsp::Position {
20743                    line: 0,
20744                    character: 22,
20745                },
20746                end: lsp::Position {
20747                    line: 0,
20748                    character: 22,
20749                },
20750            },
20751            new_text: "Some(2)".to_string(),
20752        })),
20753        additional_text_edits: Some(vec![lsp::TextEdit {
20754            range: lsp::Range {
20755                start: lsp::Position {
20756                    line: 0,
20757                    character: 20,
20758                },
20759                end: lsp::Position {
20760                    line: 0,
20761                    character: 22,
20762                },
20763            },
20764            new_text: "".to_string(),
20765        }]),
20766        ..Default::default()
20767    };
20768
20769    let closure_completion_item = completion_item.clone();
20770    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20771        let task_completion_item = closure_completion_item.clone();
20772        async move {
20773            Ok(Some(lsp::CompletionResponse::Array(vec![
20774                task_completion_item,
20775            ])))
20776        }
20777    });
20778
20779    request.next().await;
20780
20781    cx.condition(|editor, _| editor.context_menu_visible())
20782        .await;
20783    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20784        editor
20785            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20786            .unwrap()
20787    });
20788    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
20789
20790    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20791        let task_completion_item = completion_item.clone();
20792        async move { Ok(task_completion_item) }
20793    })
20794    .next()
20795    .await
20796    .unwrap();
20797    apply_additional_edits.await.unwrap();
20798    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
20799}
20800
20801#[gpui::test]
20802async fn test_completions_with_additional_edits_undo(cx: &mut TestAppContext) {
20803    init_test(cx, |_| {});
20804
20805    let mut cx = EditorLspTestContext::new_rust(
20806        lsp::ServerCapabilities {
20807            completion_provider: Some(lsp::CompletionOptions {
20808                trigger_characters: Some(vec![".".to_string()]),
20809                resolve_provider: Some(true),
20810                ..Default::default()
20811            }),
20812            ..Default::default()
20813        },
20814        cx,
20815    )
20816    .await;
20817
20818    cx.set_state("fn main() { let a = 2ˇ; }");
20819    cx.simulate_keystroke(".");
20820    let completion_item = lsp::CompletionItem {
20821        label: "some".into(),
20822        kind: Some(lsp::CompletionItemKind::SNIPPET),
20823        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
20824        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
20825            kind: lsp::MarkupKind::Markdown,
20826            value: "```rust\nSome(2)\n```".to_string(),
20827        })),
20828        deprecated: Some(false),
20829        sort_text: Some("fffffff2".to_string()),
20830        filter_text: Some("some".to_string()),
20831        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
20832        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20833            range: lsp::Range {
20834                start: lsp::Position {
20835                    line: 0,
20836                    character: 22,
20837                },
20838                end: lsp::Position {
20839                    line: 0,
20840                    character: 22,
20841                },
20842            },
20843            new_text: "Some(2)".to_string(),
20844        })),
20845        additional_text_edits: Some(vec![lsp::TextEdit {
20846            range: lsp::Range {
20847                start: lsp::Position {
20848                    line: 0,
20849                    character: 20,
20850                },
20851                end: lsp::Position {
20852                    line: 0,
20853                    character: 22,
20854                },
20855            },
20856            new_text: "".to_string(),
20857        }]),
20858        ..Default::default()
20859    };
20860
20861    let closure_completion_item = completion_item.clone();
20862    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20863        let task_completion_item = closure_completion_item.clone();
20864        async move {
20865            Ok(Some(lsp::CompletionResponse::Array(vec![
20866                task_completion_item,
20867            ])))
20868        }
20869    });
20870
20871    request.next().await;
20872
20873    cx.condition(|editor, _| editor.context_menu_visible())
20874        .await;
20875    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20876        editor
20877            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20878            .unwrap()
20879    });
20880    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
20881
20882    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20883        let task_completion_item = completion_item.clone();
20884        async move { Ok(task_completion_item) }
20885    })
20886    .next()
20887    .await
20888    .unwrap();
20889    apply_additional_edits.await.unwrap();
20890    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
20891
20892    cx.update_editor(|editor, window, cx| {
20893        editor.undo(&crate::Undo, window, cx);
20894    });
20895    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
20896}
20897
20898#[gpui::test]
20899async fn test_completions_with_additional_edits_and_multiple_cursors(cx: &mut TestAppContext) {
20900    init_test(cx, |_| {});
20901
20902    let mut cx = EditorLspTestContext::new_typescript(
20903        lsp::ServerCapabilities {
20904            completion_provider: Some(lsp::CompletionOptions {
20905                resolve_provider: Some(true),
20906                ..Default::default()
20907            }),
20908            ..Default::default()
20909        },
20910        cx,
20911    )
20912    .await;
20913
20914    cx.set_state(
20915        "import { «Fooˇ» } from './types';\n\nclass Bar {\n    method(): «Fooˇ» { return new Foo(); }\n}",
20916    );
20917
20918    cx.simulate_keystroke("F");
20919    cx.simulate_keystroke("o");
20920
20921    let completion_item = lsp::CompletionItem {
20922        label: "FooBar".into(),
20923        kind: Some(lsp::CompletionItemKind::CLASS),
20924        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20925            range: lsp::Range {
20926                start: lsp::Position {
20927                    line: 3,
20928                    character: 14,
20929                },
20930                end: lsp::Position {
20931                    line: 3,
20932                    character: 16,
20933                },
20934            },
20935            new_text: "FooBar".to_string(),
20936        })),
20937        additional_text_edits: Some(vec![lsp::TextEdit {
20938            range: lsp::Range {
20939                start: lsp::Position {
20940                    line: 0,
20941                    character: 9,
20942                },
20943                end: lsp::Position {
20944                    line: 0,
20945                    character: 11,
20946                },
20947            },
20948            new_text: "FooBar".to_string(),
20949        }]),
20950        ..Default::default()
20951    };
20952
20953    let closure_completion_item = completion_item.clone();
20954    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20955        let task_completion_item = closure_completion_item.clone();
20956        async move {
20957            Ok(Some(lsp::CompletionResponse::Array(vec![
20958                task_completion_item,
20959            ])))
20960        }
20961    });
20962
20963    request.next().await;
20964
20965    cx.condition(|editor, _| editor.context_menu_visible())
20966        .await;
20967    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20968        editor
20969            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20970            .unwrap()
20971    });
20972
20973    cx.assert_editor_state(
20974        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
20975    );
20976
20977    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20978        let task_completion_item = completion_item.clone();
20979        async move { Ok(task_completion_item) }
20980    })
20981    .next()
20982    .await
20983    .unwrap();
20984
20985    apply_additional_edits.await.unwrap();
20986
20987    cx.assert_editor_state(
20988        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
20989    );
20990}
20991
20992#[gpui::test]
20993async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
20994    init_test(cx, |_| {});
20995
20996    let mut cx = EditorLspTestContext::new_rust(
20997        lsp::ServerCapabilities {
20998            completion_provider: Some(lsp::CompletionOptions {
20999                trigger_characters: Some(vec![".".to_string()]),
21000                resolve_provider: Some(true),
21001                ..Default::default()
21002            }),
21003            ..Default::default()
21004        },
21005        cx,
21006    )
21007    .await;
21008
21009    cx.set_state("fn main() { let a = 2ˇ; }");
21010    cx.simulate_keystroke(".");
21011
21012    let item1 = lsp::CompletionItem {
21013        label: "method id()".to_string(),
21014        filter_text: Some("id".to_string()),
21015        detail: None,
21016        documentation: None,
21017        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21018            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21019            new_text: ".id".to_string(),
21020        })),
21021        ..lsp::CompletionItem::default()
21022    };
21023
21024    let item2 = lsp::CompletionItem {
21025        label: "other".to_string(),
21026        filter_text: Some("other".to_string()),
21027        detail: None,
21028        documentation: None,
21029        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21030            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21031            new_text: ".other".to_string(),
21032        })),
21033        ..lsp::CompletionItem::default()
21034    };
21035
21036    let item1 = item1.clone();
21037    cx.set_request_handler::<lsp::request::Completion, _, _>({
21038        let item1 = item1.clone();
21039        move |_, _, _| {
21040            let item1 = item1.clone();
21041            let item2 = item2.clone();
21042            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
21043        }
21044    })
21045    .next()
21046    .await;
21047
21048    cx.condition(|editor, _| editor.context_menu_visible())
21049        .await;
21050    cx.update_editor(|editor, _, _| {
21051        let context_menu = editor.context_menu.borrow_mut();
21052        let context_menu = context_menu
21053            .as_ref()
21054            .expect("Should have the context menu deployed");
21055        match context_menu {
21056            CodeContextMenu::Completions(completions_menu) => {
21057                let completions = completions_menu.completions.borrow_mut();
21058                assert_eq!(
21059                    completions
21060                        .iter()
21061                        .map(|completion| &completion.label.text)
21062                        .collect::<Vec<_>>(),
21063                    vec!["method id()", "other"]
21064                )
21065            }
21066            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21067        }
21068    });
21069
21070    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
21071        let item1 = item1.clone();
21072        move |_, item_to_resolve, _| {
21073            let item1 = item1.clone();
21074            async move {
21075                if item1 == item_to_resolve {
21076                    Ok(lsp::CompletionItem {
21077                        label: "method id()".to_string(),
21078                        filter_text: Some("id".to_string()),
21079                        detail: Some("Now resolved!".to_string()),
21080                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
21081                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21082                            range: lsp::Range::new(
21083                                lsp::Position::new(0, 22),
21084                                lsp::Position::new(0, 22),
21085                            ),
21086                            new_text: ".id".to_string(),
21087                        })),
21088                        ..lsp::CompletionItem::default()
21089                    })
21090                } else {
21091                    Ok(item_to_resolve)
21092                }
21093            }
21094        }
21095    })
21096    .next()
21097    .await
21098    .unwrap();
21099    cx.run_until_parked();
21100
21101    cx.update_editor(|editor, window, cx| {
21102        editor.context_menu_next(&Default::default(), window, cx);
21103    });
21104    cx.run_until_parked();
21105
21106    cx.update_editor(|editor, _, _| {
21107        let context_menu = editor.context_menu.borrow_mut();
21108        let context_menu = context_menu
21109            .as_ref()
21110            .expect("Should have the context menu deployed");
21111        match context_menu {
21112            CodeContextMenu::Completions(completions_menu) => {
21113                let completions = completions_menu.completions.borrow_mut();
21114                assert_eq!(
21115                    completions
21116                        .iter()
21117                        .map(|completion| &completion.label.text)
21118                        .collect::<Vec<_>>(),
21119                    vec!["method id() Now resolved!", "other"],
21120                    "Should update first completion label, but not second as the filter text did not match."
21121                );
21122            }
21123            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21124        }
21125    });
21126}
21127
21128#[gpui::test]
21129async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
21130    init_test(cx, |_| {});
21131    let mut cx = EditorLspTestContext::new_rust(
21132        lsp::ServerCapabilities {
21133            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
21134            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
21135            completion_provider: Some(lsp::CompletionOptions {
21136                resolve_provider: Some(true),
21137                ..Default::default()
21138            }),
21139            ..Default::default()
21140        },
21141        cx,
21142    )
21143    .await;
21144    cx.set_state(indoc! {"
21145        struct TestStruct {
21146            field: i32
21147        }
21148
21149        fn mainˇ() {
21150            let unused_var = 42;
21151            let test_struct = TestStruct { field: 42 };
21152        }
21153    "});
21154    let symbol_range = cx.lsp_range(indoc! {"
21155        struct TestStruct {
21156            field: i32
21157        }
21158
21159        «fn main»() {
21160            let unused_var = 42;
21161            let test_struct = TestStruct { field: 42 };
21162        }
21163    "});
21164    let mut hover_requests =
21165        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
21166            Ok(Some(lsp::Hover {
21167                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
21168                    kind: lsp::MarkupKind::Markdown,
21169                    value: "Function documentation".to_string(),
21170                }),
21171                range: Some(symbol_range),
21172            }))
21173        });
21174
21175    // Case 1: Test that code action menu hide hover popover
21176    cx.dispatch_action(Hover);
21177    hover_requests.next().await;
21178    cx.condition(|editor, _| editor.hover_state.visible()).await;
21179    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
21180        move |_, _, _| async move {
21181            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
21182                lsp::CodeAction {
21183                    title: "Remove unused variable".to_string(),
21184                    kind: Some(CodeActionKind::QUICKFIX),
21185                    edit: Some(lsp::WorkspaceEdit {
21186                        changes: Some(
21187                            [(
21188                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
21189                                vec![lsp::TextEdit {
21190                                    range: lsp::Range::new(
21191                                        lsp::Position::new(5, 4),
21192                                        lsp::Position::new(5, 27),
21193                                    ),
21194                                    new_text: "".to_string(),
21195                                }],
21196                            )]
21197                            .into_iter()
21198                            .collect(),
21199                        ),
21200                        ..Default::default()
21201                    }),
21202                    ..Default::default()
21203                },
21204            )]))
21205        },
21206    );
21207    cx.update_editor(|editor, window, cx| {
21208        editor.toggle_code_actions(
21209            &ToggleCodeActions {
21210                deployed_from: None,
21211                quick_launch: false,
21212            },
21213            window,
21214            cx,
21215        );
21216    });
21217    code_action_requests.next().await;
21218    cx.run_until_parked();
21219    cx.condition(|editor, _| editor.context_menu_visible())
21220        .await;
21221    cx.update_editor(|editor, _, _| {
21222        assert!(
21223            !editor.hover_state.visible(),
21224            "Hover popover should be hidden when code action menu is shown"
21225        );
21226        // Hide code actions
21227        editor.context_menu.take();
21228    });
21229
21230    // Case 2: Test that code completions hide hover popover
21231    cx.dispatch_action(Hover);
21232    hover_requests.next().await;
21233    cx.condition(|editor, _| editor.hover_state.visible()).await;
21234    let counter = Arc::new(AtomicUsize::new(0));
21235    let mut completion_requests =
21236        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21237            let counter = counter.clone();
21238            async move {
21239                counter.fetch_add(1, atomic::Ordering::Release);
21240                Ok(Some(lsp::CompletionResponse::Array(vec![
21241                    lsp::CompletionItem {
21242                        label: "main".into(),
21243                        kind: Some(lsp::CompletionItemKind::FUNCTION),
21244                        detail: Some("() -> ()".to_string()),
21245                        ..Default::default()
21246                    },
21247                    lsp::CompletionItem {
21248                        label: "TestStruct".into(),
21249                        kind: Some(lsp::CompletionItemKind::STRUCT),
21250                        detail: Some("struct TestStruct".to_string()),
21251                        ..Default::default()
21252                    },
21253                ])))
21254            }
21255        });
21256    cx.update_editor(|editor, window, cx| {
21257        editor.show_completions(&ShowCompletions, window, cx);
21258    });
21259    completion_requests.next().await;
21260    cx.condition(|editor, _| editor.context_menu_visible())
21261        .await;
21262    cx.update_editor(|editor, _, _| {
21263        assert!(
21264            !editor.hover_state.visible(),
21265            "Hover popover should be hidden when completion menu is shown"
21266        );
21267    });
21268}
21269
21270#[gpui::test]
21271async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
21272    init_test(cx, |_| {});
21273
21274    let mut cx = EditorLspTestContext::new_rust(
21275        lsp::ServerCapabilities {
21276            completion_provider: Some(lsp::CompletionOptions {
21277                trigger_characters: Some(vec![".".to_string()]),
21278                resolve_provider: Some(true),
21279                ..Default::default()
21280            }),
21281            ..Default::default()
21282        },
21283        cx,
21284    )
21285    .await;
21286
21287    cx.set_state("fn main() { let a = 2ˇ; }");
21288    cx.simulate_keystroke(".");
21289
21290    let unresolved_item_1 = lsp::CompletionItem {
21291        label: "id".to_string(),
21292        filter_text: Some("id".to_string()),
21293        detail: None,
21294        documentation: None,
21295        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21296            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21297            new_text: ".id".to_string(),
21298        })),
21299        ..lsp::CompletionItem::default()
21300    };
21301    let resolved_item_1 = lsp::CompletionItem {
21302        additional_text_edits: Some(vec![lsp::TextEdit {
21303            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
21304            new_text: "!!".to_string(),
21305        }]),
21306        ..unresolved_item_1.clone()
21307    };
21308    let unresolved_item_2 = lsp::CompletionItem {
21309        label: "other".to_string(),
21310        filter_text: Some("other".to_string()),
21311        detail: None,
21312        documentation: None,
21313        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
21314            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
21315            new_text: ".other".to_string(),
21316        })),
21317        ..lsp::CompletionItem::default()
21318    };
21319    let resolved_item_2 = lsp::CompletionItem {
21320        additional_text_edits: Some(vec![lsp::TextEdit {
21321            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
21322            new_text: "??".to_string(),
21323        }]),
21324        ..unresolved_item_2.clone()
21325    };
21326
21327    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
21328    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
21329    cx.lsp
21330        .server
21331        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
21332            let unresolved_item_1 = unresolved_item_1.clone();
21333            let resolved_item_1 = resolved_item_1.clone();
21334            let unresolved_item_2 = unresolved_item_2.clone();
21335            let resolved_item_2 = resolved_item_2.clone();
21336            let resolve_requests_1 = resolve_requests_1.clone();
21337            let resolve_requests_2 = resolve_requests_2.clone();
21338            move |unresolved_request, _| {
21339                let unresolved_item_1 = unresolved_item_1.clone();
21340                let resolved_item_1 = resolved_item_1.clone();
21341                let unresolved_item_2 = unresolved_item_2.clone();
21342                let resolved_item_2 = resolved_item_2.clone();
21343                let resolve_requests_1 = resolve_requests_1.clone();
21344                let resolve_requests_2 = resolve_requests_2.clone();
21345                async move {
21346                    if unresolved_request == unresolved_item_1 {
21347                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
21348                        Ok(resolved_item_1.clone())
21349                    } else if unresolved_request == unresolved_item_2 {
21350                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
21351                        Ok(resolved_item_2.clone())
21352                    } else {
21353                        panic!("Unexpected completion item {unresolved_request:?}")
21354                    }
21355                }
21356            }
21357        })
21358        .detach();
21359
21360    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21361        let unresolved_item_1 = unresolved_item_1.clone();
21362        let unresolved_item_2 = unresolved_item_2.clone();
21363        async move {
21364            Ok(Some(lsp::CompletionResponse::Array(vec![
21365                unresolved_item_1,
21366                unresolved_item_2,
21367            ])))
21368        }
21369    })
21370    .next()
21371    .await;
21372
21373    cx.condition(|editor, _| editor.context_menu_visible())
21374        .await;
21375    cx.update_editor(|editor, _, _| {
21376        let context_menu = editor.context_menu.borrow_mut();
21377        let context_menu = context_menu
21378            .as_ref()
21379            .expect("Should have the context menu deployed");
21380        match context_menu {
21381            CodeContextMenu::Completions(completions_menu) => {
21382                let completions = completions_menu.completions.borrow_mut();
21383                assert_eq!(
21384                    completions
21385                        .iter()
21386                        .map(|completion| &completion.label.text)
21387                        .collect::<Vec<_>>(),
21388                    vec!["id", "other"]
21389                )
21390            }
21391            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
21392        }
21393    });
21394    cx.run_until_parked();
21395
21396    cx.update_editor(|editor, window, cx| {
21397        editor.context_menu_next(&ContextMenuNext, window, cx);
21398    });
21399    cx.run_until_parked();
21400    cx.update_editor(|editor, window, cx| {
21401        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
21402    });
21403    cx.run_until_parked();
21404    cx.update_editor(|editor, window, cx| {
21405        editor.context_menu_next(&ContextMenuNext, window, cx);
21406    });
21407    cx.run_until_parked();
21408    cx.update_editor(|editor, window, cx| {
21409        editor
21410            .compose_completion(&ComposeCompletion::default(), window, cx)
21411            .expect("No task returned")
21412    })
21413    .await
21414    .expect("Completion failed");
21415    cx.run_until_parked();
21416
21417    cx.update_editor(|editor, _, cx| {
21418        assert_eq!(
21419            resolve_requests_1.load(atomic::Ordering::Acquire),
21420            1,
21421            "Should always resolve once despite multiple selections"
21422        );
21423        assert_eq!(
21424            resolve_requests_2.load(atomic::Ordering::Acquire),
21425            1,
21426            "Should always resolve once after multiple selections and applying the completion"
21427        );
21428        assert_eq!(
21429            editor.text(cx),
21430            "fn main() { let a = ??.other; }",
21431            "Should use resolved data when applying the completion"
21432        );
21433    });
21434}
21435
21436#[gpui::test]
21437async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
21438    init_test(cx, |_| {});
21439
21440    let item_0 = lsp::CompletionItem {
21441        label: "abs".into(),
21442        insert_text: Some("abs".into()),
21443        data: Some(json!({ "very": "special"})),
21444        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
21445        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21446            lsp::InsertReplaceEdit {
21447                new_text: "abs".to_string(),
21448                insert: lsp::Range::default(),
21449                replace: lsp::Range::default(),
21450            },
21451        )),
21452        ..lsp::CompletionItem::default()
21453    };
21454    let items = iter::once(item_0.clone())
21455        .chain((11..51).map(|i| lsp::CompletionItem {
21456            label: format!("item_{}", i),
21457            insert_text: Some(format!("item_{}", i)),
21458            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
21459            ..lsp::CompletionItem::default()
21460        }))
21461        .collect::<Vec<_>>();
21462
21463    let default_commit_characters = vec!["?".to_string()];
21464    let default_data = json!({ "default": "data"});
21465    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
21466    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
21467    let default_edit_range = lsp::Range {
21468        start: lsp::Position {
21469            line: 0,
21470            character: 5,
21471        },
21472        end: lsp::Position {
21473            line: 0,
21474            character: 5,
21475        },
21476    };
21477
21478    let mut cx = EditorLspTestContext::new_rust(
21479        lsp::ServerCapabilities {
21480            completion_provider: Some(lsp::CompletionOptions {
21481                trigger_characters: Some(vec![".".to_string()]),
21482                resolve_provider: Some(true),
21483                ..Default::default()
21484            }),
21485            ..Default::default()
21486        },
21487        cx,
21488    )
21489    .await;
21490
21491    cx.set_state("fn main() { let a = 2ˇ; }");
21492    cx.simulate_keystroke(".");
21493
21494    let completion_data = default_data.clone();
21495    let completion_characters = default_commit_characters.clone();
21496    let completion_items = items.clone();
21497    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
21498        let default_data = completion_data.clone();
21499        let default_commit_characters = completion_characters.clone();
21500        let items = completion_items.clone();
21501        async move {
21502            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
21503                items,
21504                item_defaults: Some(lsp::CompletionListItemDefaults {
21505                    data: Some(default_data.clone()),
21506                    commit_characters: Some(default_commit_characters.clone()),
21507                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
21508                        default_edit_range,
21509                    )),
21510                    insert_text_format: Some(default_insert_text_format),
21511                    insert_text_mode: Some(default_insert_text_mode),
21512                }),
21513                ..lsp::CompletionList::default()
21514            })))
21515        }
21516    })
21517    .next()
21518    .await;
21519
21520    let resolved_items = Arc::new(Mutex::new(Vec::new()));
21521    cx.lsp
21522        .server
21523        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
21524            let closure_resolved_items = resolved_items.clone();
21525            move |item_to_resolve, _| {
21526                let closure_resolved_items = closure_resolved_items.clone();
21527                async move {
21528                    closure_resolved_items.lock().push(item_to_resolve.clone());
21529                    Ok(item_to_resolve)
21530                }
21531            }
21532        })
21533        .detach();
21534
21535    cx.condition(|editor, _| editor.context_menu_visible())
21536        .await;
21537    cx.run_until_parked();
21538    cx.update_editor(|editor, _, _| {
21539        let menu = editor.context_menu.borrow_mut();
21540        match menu.as_ref().expect("should have the completions menu") {
21541            CodeContextMenu::Completions(completions_menu) => {
21542                assert_eq!(
21543                    completions_menu
21544                        .entries
21545                        .borrow()
21546                        .iter()
21547                        .map(|mat| mat.string.clone())
21548                        .collect::<Vec<String>>(),
21549                    items
21550                        .iter()
21551                        .map(|completion| completion.label.clone())
21552                        .collect::<Vec<String>>()
21553                );
21554            }
21555            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
21556        }
21557    });
21558    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
21559    // with 4 from the end.
21560    assert_eq!(
21561        *resolved_items.lock(),
21562        [&items[0..16], &items[items.len() - 4..items.len()]]
21563            .concat()
21564            .iter()
21565            .cloned()
21566            .map(|mut item| {
21567                if item.data.is_none() {
21568                    item.data = Some(default_data.clone());
21569                }
21570                item
21571            })
21572            .collect::<Vec<lsp::CompletionItem>>(),
21573        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
21574    );
21575    resolved_items.lock().clear();
21576
21577    cx.update_editor(|editor, window, cx| {
21578        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
21579    });
21580    cx.run_until_parked();
21581    // Completions that have already been resolved are skipped.
21582    assert_eq!(
21583        *resolved_items.lock(),
21584        items[items.len() - 17..items.len() - 4]
21585            .iter()
21586            .cloned()
21587            .map(|mut item| {
21588                if item.data.is_none() {
21589                    item.data = Some(default_data.clone());
21590                }
21591                item
21592            })
21593            .collect::<Vec<lsp::CompletionItem>>()
21594    );
21595    resolved_items.lock().clear();
21596}
21597
21598#[gpui::test]
21599async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
21600    init_test(cx, |_| {});
21601
21602    let mut cx = EditorLspTestContext::new(
21603        Language::new(
21604            LanguageConfig {
21605                matcher: LanguageMatcher {
21606                    path_suffixes: vec!["jsx".into()],
21607                    ..Default::default()
21608                },
21609                overrides: [(
21610                    "element".into(),
21611                    LanguageConfigOverride {
21612                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
21613                        ..Default::default()
21614                    },
21615                )]
21616                .into_iter()
21617                .collect(),
21618                ..Default::default()
21619            },
21620            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
21621        )
21622        .with_override_query("(jsx_self_closing_element) @element")
21623        .unwrap(),
21624        lsp::ServerCapabilities {
21625            completion_provider: Some(lsp::CompletionOptions {
21626                trigger_characters: Some(vec![":".to_string()]),
21627                ..Default::default()
21628            }),
21629            ..Default::default()
21630        },
21631        cx,
21632    )
21633    .await;
21634
21635    cx.lsp
21636        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21637            Ok(Some(lsp::CompletionResponse::Array(vec![
21638                lsp::CompletionItem {
21639                    label: "bg-blue".into(),
21640                    ..Default::default()
21641                },
21642                lsp::CompletionItem {
21643                    label: "bg-red".into(),
21644                    ..Default::default()
21645                },
21646                lsp::CompletionItem {
21647                    label: "bg-yellow".into(),
21648                    ..Default::default()
21649                },
21650            ])))
21651        });
21652
21653    cx.set_state(r#"<p class="bgˇ" />"#);
21654
21655    // Trigger completion when typing a dash, because the dash is an extra
21656    // word character in the 'element' scope, which contains the cursor.
21657    cx.simulate_keystroke("-");
21658    cx.executor().run_until_parked();
21659    cx.update_editor(|editor, _, _| {
21660        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
21661        {
21662            assert_eq!(
21663                completion_menu_entries(menu),
21664                &["bg-blue", "bg-red", "bg-yellow"]
21665            );
21666        } else {
21667            panic!("expected completion menu to be open");
21668        }
21669    });
21670
21671    cx.simulate_keystroke("l");
21672    cx.executor().run_until_parked();
21673    cx.update_editor(|editor, _, _| {
21674        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
21675        {
21676            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
21677        } else {
21678            panic!("expected completion menu to be open");
21679        }
21680    });
21681
21682    // When filtering completions, consider the character after the '-' to
21683    // be the start of a subword.
21684    cx.set_state(r#"<p class="yelˇ" />"#);
21685    cx.simulate_keystroke("l");
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!(completion_menu_entries(menu), &["bg-yellow"]);
21691        } else {
21692            panic!("expected completion menu to be open");
21693        }
21694    });
21695}
21696
21697fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
21698    let entries = menu.entries.borrow();
21699    entries.iter().map(|mat| mat.string.clone()).collect()
21700}
21701
21702#[gpui::test]
21703async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
21704    init_test(cx, |settings| {
21705        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21706    });
21707
21708    let fs = FakeFs::new(cx.executor());
21709    fs.insert_file(path!("/file.ts"), Default::default()).await;
21710
21711    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
21712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21713
21714    language_registry.add(Arc::new(Language::new(
21715        LanguageConfig {
21716            name: "TypeScript".into(),
21717            matcher: LanguageMatcher {
21718                path_suffixes: vec!["ts".to_string()],
21719                ..Default::default()
21720            },
21721            ..Default::default()
21722        },
21723        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21724    )));
21725    update_test_language_settings(cx, &|settings| {
21726        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21727    });
21728
21729    let test_plugin = "test_plugin";
21730    let _ = language_registry.register_fake_lsp(
21731        "TypeScript",
21732        FakeLspAdapter {
21733            prettier_plugins: vec![test_plugin],
21734            ..Default::default()
21735        },
21736    );
21737
21738    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21739    let buffer = project
21740        .update(cx, |project, cx| {
21741            project.open_local_buffer(path!("/file.ts"), cx)
21742        })
21743        .await
21744        .unwrap();
21745
21746    let buffer_text = "one\ntwo\nthree\n";
21747    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21748    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21749    editor.update_in(cx, |editor, window, cx| {
21750        editor.set_text(buffer_text, window, cx)
21751    });
21752
21753    editor
21754        .update_in(cx, |editor, window, cx| {
21755            editor.perform_format(
21756                project.clone(),
21757                FormatTrigger::Manual,
21758                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21759                window,
21760                cx,
21761            )
21762        })
21763        .unwrap()
21764        .await;
21765    assert_eq!(
21766        editor.update(cx, |editor, cx| editor.text(cx)),
21767        buffer_text.to_string() + prettier_format_suffix,
21768        "Test prettier formatting was not applied to the original buffer text",
21769    );
21770
21771    update_test_language_settings(cx, &|settings| {
21772        settings.defaults.formatter = Some(FormatterList::default())
21773    });
21774    let format = editor.update_in(cx, |editor, window, cx| {
21775        editor.perform_format(
21776            project.clone(),
21777            FormatTrigger::Manual,
21778            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21779            window,
21780            cx,
21781        )
21782    });
21783    format.await.unwrap();
21784    assert_eq!(
21785        editor.update(cx, |editor, cx| editor.text(cx)),
21786        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
21787        "Autoformatting (via test prettier) was not applied to the original buffer text",
21788    );
21789}
21790
21791#[gpui::test]
21792async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
21793    init_test(cx, |settings| {
21794        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21795    });
21796
21797    let fs = FakeFs::new(cx.executor());
21798    fs.insert_file(path!("/file.settings"), Default::default())
21799        .await;
21800
21801    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
21802    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21803
21804    let ts_lang = Arc::new(Language::new(
21805        LanguageConfig {
21806            name: "TypeScript".into(),
21807            matcher: LanguageMatcher {
21808                path_suffixes: vec!["ts".to_string()],
21809                ..LanguageMatcher::default()
21810            },
21811            prettier_parser_name: Some("typescript".to_string()),
21812            ..LanguageConfig::default()
21813        },
21814        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21815    ));
21816
21817    language_registry.add(ts_lang.clone());
21818
21819    update_test_language_settings(cx, &|settings| {
21820        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21821    });
21822
21823    let test_plugin = "test_plugin";
21824    let _ = language_registry.register_fake_lsp(
21825        "TypeScript",
21826        FakeLspAdapter {
21827            prettier_plugins: vec![test_plugin],
21828            ..Default::default()
21829        },
21830    );
21831
21832    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21833    let buffer = project
21834        .update(cx, |project, cx| {
21835            project.open_local_buffer(path!("/file.settings"), cx)
21836        })
21837        .await
21838        .unwrap();
21839
21840    project.update(cx, |project, cx| {
21841        project.set_language_for_buffer(&buffer, ts_lang, cx)
21842    });
21843
21844    let buffer_text = "one\ntwo\nthree\n";
21845    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21846    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21847    editor.update_in(cx, |editor, window, cx| {
21848        editor.set_text(buffer_text, window, cx)
21849    });
21850
21851    editor
21852        .update_in(cx, |editor, window, cx| {
21853            editor.perform_format(
21854                project.clone(),
21855                FormatTrigger::Manual,
21856                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21857                window,
21858                cx,
21859            )
21860        })
21861        .unwrap()
21862        .await;
21863    assert_eq!(
21864        editor.update(cx, |editor, cx| editor.text(cx)),
21865        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
21866        "Test prettier formatting was not applied to the original buffer text",
21867    );
21868
21869    update_test_language_settings(cx, &|settings| {
21870        settings.defaults.formatter = Some(FormatterList::default())
21871    });
21872    let format = editor.update_in(cx, |editor, window, cx| {
21873        editor.perform_format(
21874            project.clone(),
21875            FormatTrigger::Manual,
21876            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21877            window,
21878            cx,
21879        )
21880    });
21881    format.await.unwrap();
21882
21883    assert_eq!(
21884        editor.update(cx, |editor, cx| editor.text(cx)),
21885        buffer_text.to_string()
21886            + prettier_format_suffix
21887            + "\ntypescript\n"
21888            + prettier_format_suffix
21889            + "\ntypescript",
21890        "Autoformatting (via test prettier) was not applied to the original buffer text",
21891    );
21892}
21893
21894#[gpui::test]
21895async fn test_range_format_with_prettier(cx: &mut TestAppContext) {
21896    init_test(cx, |settings| {
21897        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21898    });
21899
21900    let fs = FakeFs::new(cx.executor());
21901    fs.insert_file(path!("/file.ts"), Default::default()).await;
21902
21903    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
21904    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21905
21906    language_registry.add(Arc::new(Language::new(
21907        LanguageConfig {
21908            name: "TypeScript".into(),
21909            matcher: LanguageMatcher {
21910                path_suffixes: vec!["ts".to_string()],
21911                ..Default::default()
21912            },
21913            ..Default::default()
21914        },
21915        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21916    )));
21917    update_test_language_settings(cx, &|settings| {
21918        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21919    });
21920
21921    let test_plugin = "test_plugin";
21922    let _ = language_registry.register_fake_lsp(
21923        "TypeScript",
21924        FakeLspAdapter {
21925            prettier_plugins: vec![test_plugin],
21926            ..Default::default()
21927        },
21928    );
21929
21930    let prettier_range_format_suffix = project::TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
21931    let buffer = project
21932        .update(cx, |project, cx| {
21933            project.open_local_buffer(path!("/file.ts"), cx)
21934        })
21935        .await
21936        .unwrap();
21937
21938    let buffer_text = "one\ntwo\nthree\nfour\nfive\n";
21939    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21940    let (editor, cx) = cx.add_window_view(|window, cx| {
21941        build_editor_with_project(project.clone(), buffer, window, cx)
21942    });
21943    editor.update_in(cx, |editor, window, cx| {
21944        editor.set_text(buffer_text, window, cx)
21945    });
21946
21947    cx.executor().run_until_parked();
21948
21949    editor.update_in(cx, |editor, window, cx| {
21950        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
21951            s.select_ranges([Point::new(1, 0)..Point::new(3, 0)])
21952        });
21953    });
21954
21955    let format = editor
21956        .update_in(cx, |editor, window, cx| {
21957            editor.format_selections(&FormatSelections, window, cx)
21958        })
21959        .unwrap();
21960    format.await.unwrap();
21961
21962    assert_eq!(
21963        editor.update(cx, |editor, cx| editor.text(cx)),
21964        format!("one\ntwo{prettier_range_format_suffix}\nthree\nfour\nfive\n"),
21965        "Range formatting (via test prettier) was not applied to the buffer text",
21966    );
21967}
21968
21969#[gpui::test]
21970async fn test_range_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
21971    init_test(cx, |settings| {
21972        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21973    });
21974
21975    let fs = FakeFs::new(cx.executor());
21976    fs.insert_file(path!("/file.settings"), Default::default())
21977        .await;
21978
21979    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
21980    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21981
21982    let ts_lang = Arc::new(Language::new(
21983        LanguageConfig {
21984            name: "TypeScript".into(),
21985            matcher: LanguageMatcher {
21986                path_suffixes: vec!["ts".to_string()],
21987                ..LanguageMatcher::default()
21988            },
21989            prettier_parser_name: Some("typescript".to_string()),
21990            ..LanguageConfig::default()
21991        },
21992        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21993    ));
21994
21995    language_registry.add(ts_lang.clone());
21996
21997    update_test_language_settings(cx, &|settings| {
21998        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21999    });
22000
22001    let test_plugin = "test_plugin";
22002    let _ = language_registry.register_fake_lsp(
22003        "TypeScript",
22004        FakeLspAdapter {
22005            prettier_plugins: vec![test_plugin],
22006            ..Default::default()
22007        },
22008    );
22009
22010    let prettier_range_format_suffix = project::TEST_PRETTIER_RANGE_FORMAT_SUFFIX;
22011    let buffer = project
22012        .update(cx, |project, cx| {
22013            project.open_local_buffer(path!("/file.settings"), cx)
22014        })
22015        .await
22016        .unwrap();
22017
22018    project.update(cx, |project, cx| {
22019        project.set_language_for_buffer(&buffer, ts_lang, cx)
22020    });
22021
22022    let buffer_text = "one\ntwo\nthree\nfour\nfive\n";
22023    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22024    let (editor, cx) = cx.add_window_view(|window, cx| {
22025        build_editor_with_project(project.clone(), buffer, window, cx)
22026    });
22027    editor.update_in(cx, |editor, window, cx| {
22028        editor.set_text(buffer_text, window, cx)
22029    });
22030
22031    cx.executor().run_until_parked();
22032
22033    editor.update_in(cx, |editor, window, cx| {
22034        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
22035            s.select_ranges([Point::new(1, 0)..Point::new(3, 0)])
22036        });
22037    });
22038
22039    let format = editor
22040        .update_in(cx, |editor, window, cx| {
22041            editor.format_selections(&FormatSelections, window, cx)
22042        })
22043        .unwrap();
22044    format.await.unwrap();
22045
22046    assert_eq!(
22047        editor.update(cx, |editor, cx| editor.text(cx)),
22048        format!("one\ntwo{prettier_range_format_suffix}\ntypescript\nthree\nfour\nfive\n"),
22049        "Range formatting (via test prettier) was not applied with explicit language",
22050    );
22051}
22052
22053#[gpui::test]
22054async fn test_addition_reverts(cx: &mut TestAppContext) {
22055    init_test(cx, |_| {});
22056    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22057    let base_text = indoc! {r#"
22058        struct Row;
22059        struct Row1;
22060        struct Row2;
22061
22062        struct Row4;
22063        struct Row5;
22064        struct Row6;
22065
22066        struct Row8;
22067        struct Row9;
22068        struct Row10;"#};
22069
22070    // When addition hunks are not adjacent to carets, no hunk revert is performed
22071    assert_hunk_revert(
22072        indoc! {r#"struct Row;
22073                   struct Row1;
22074                   struct Row1.1;
22075                   struct Row1.2;
22076                   struct Row2;ˇ
22077
22078                   struct Row4;
22079                   struct Row5;
22080                   struct Row6;
22081
22082                   struct Row8;
22083                   ˇstruct Row9;
22084                   struct Row9.1;
22085                   struct Row9.2;
22086                   struct Row9.3;
22087                   struct Row10;"#},
22088        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
22089        indoc! {r#"struct Row;
22090                   struct Row1;
22091                   struct Row1.1;
22092                   struct Row1.2;
22093                   struct Row2;ˇ
22094
22095                   struct Row4;
22096                   struct Row5;
22097                   struct Row6;
22098
22099                   struct Row8;
22100                   ˇstruct Row9;
22101                   struct Row9.1;
22102                   struct Row9.2;
22103                   struct Row9.3;
22104                   struct Row10;"#},
22105        base_text,
22106        &mut cx,
22107    );
22108    // Same for selections
22109    assert_hunk_revert(
22110        indoc! {r#"struct Row;
22111                   struct Row1;
22112                   struct Row2;
22113                   struct Row2.1;
22114                   struct Row2.2;
22115                   «ˇ
22116                   struct Row4;
22117                   struct» Row5;
22118                   «struct Row6;
22119                   ˇ»
22120                   struct Row9.1;
22121                   struct Row9.2;
22122                   struct Row9.3;
22123                   struct Row8;
22124                   struct Row9;
22125                   struct Row10;"#},
22126        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
22127        indoc! {r#"struct Row;
22128                   struct Row1;
22129                   struct Row2;
22130                   struct Row2.1;
22131                   struct Row2.2;
22132                   «ˇ
22133                   struct Row4;
22134                   struct» Row5;
22135                   «struct Row6;
22136                   ˇ»
22137                   struct Row9.1;
22138                   struct Row9.2;
22139                   struct Row9.3;
22140                   struct Row8;
22141                   struct Row9;
22142                   struct Row10;"#},
22143        base_text,
22144        &mut cx,
22145    );
22146
22147    // When carets and selections intersect the addition hunks, those are reverted.
22148    // Adjacent carets got merged.
22149    assert_hunk_revert(
22150        indoc! {r#"struct Row;
22151                   ˇ// something on the top
22152                   struct Row1;
22153                   struct Row2;
22154                   struct Roˇw3.1;
22155                   struct Row2.2;
22156                   struct Row2.3;ˇ
22157
22158                   struct Row4;
22159                   struct ˇRow5.1;
22160                   struct Row5.2;
22161                   struct «Rowˇ»5.3;
22162                   struct Row5;
22163                   struct Row6;
22164                   ˇ
22165                   struct Row9.1;
22166                   struct «Rowˇ»9.2;
22167                   struct «ˇRow»9.3;
22168                   struct Row8;
22169                   struct Row9;
22170                   «ˇ// something on bottom»
22171                   struct Row10;"#},
22172        vec![
22173            DiffHunkStatusKind::Added,
22174            DiffHunkStatusKind::Added,
22175            DiffHunkStatusKind::Added,
22176            DiffHunkStatusKind::Added,
22177            DiffHunkStatusKind::Added,
22178        ],
22179        indoc! {r#"struct Row;
22180                   ˇstruct Row1;
22181                   struct Row2;
22182                   ˇ
22183                   struct Row4;
22184                   ˇstruct Row5;
22185                   struct Row6;
22186                   ˇ
22187                   ˇstruct Row8;
22188                   struct Row9;
22189                   ˇstruct Row10;"#},
22190        base_text,
22191        &mut cx,
22192    );
22193}
22194
22195#[gpui::test]
22196async fn test_modification_reverts(cx: &mut TestAppContext) {
22197    init_test(cx, |_| {});
22198    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22199    let base_text = indoc! {r#"
22200        struct Row;
22201        struct Row1;
22202        struct Row2;
22203
22204        struct Row4;
22205        struct Row5;
22206        struct Row6;
22207
22208        struct Row8;
22209        struct Row9;
22210        struct Row10;"#};
22211
22212    // Modification hunks behave the same as the addition ones.
22213    assert_hunk_revert(
22214        indoc! {r#"struct Row;
22215                   struct Row1;
22216                   struct Row33;
22217                   ˇ
22218                   struct Row4;
22219                   struct Row5;
22220                   struct Row6;
22221                   ˇ
22222                   struct Row99;
22223                   struct Row9;
22224                   struct Row10;"#},
22225        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
22226        indoc! {r#"struct Row;
22227                   struct Row1;
22228                   struct Row33;
22229                   ˇ
22230                   struct Row4;
22231                   struct Row5;
22232                   struct Row6;
22233                   ˇ
22234                   struct Row99;
22235                   struct Row9;
22236                   struct Row10;"#},
22237        base_text,
22238        &mut cx,
22239    );
22240    assert_hunk_revert(
22241        indoc! {r#"struct Row;
22242                   struct Row1;
22243                   struct Row33;
22244                   «ˇ
22245                   struct Row4;
22246                   struct» Row5;
22247                   «struct Row6;
22248                   ˇ»
22249                   struct Row99;
22250                   struct Row9;
22251                   struct Row10;"#},
22252        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
22253        indoc! {r#"struct Row;
22254                   struct Row1;
22255                   struct Row33;
22256                   «ˇ
22257                   struct Row4;
22258                   struct» Row5;
22259                   «struct Row6;
22260                   ˇ»
22261                   struct Row99;
22262                   struct Row9;
22263                   struct Row10;"#},
22264        base_text,
22265        &mut cx,
22266    );
22267
22268    assert_hunk_revert(
22269        indoc! {r#"ˇstruct Row1.1;
22270                   struct Row1;
22271                   «ˇstr»uct Row22;
22272
22273                   struct ˇRow44;
22274                   struct Row5;
22275                   struct «Rˇ»ow66;ˇ
22276
22277                   «struˇ»ct Row88;
22278                   struct Row9;
22279                   struct Row1011;ˇ"#},
22280        vec![
22281            DiffHunkStatusKind::Modified,
22282            DiffHunkStatusKind::Modified,
22283            DiffHunkStatusKind::Modified,
22284            DiffHunkStatusKind::Modified,
22285            DiffHunkStatusKind::Modified,
22286            DiffHunkStatusKind::Modified,
22287        ],
22288        indoc! {r#"struct Row;
22289                   ˇstruct Row1;
22290                   struct Row2;
22291                   ˇ
22292                   struct Row4;
22293                   ˇstruct Row5;
22294                   struct Row6;
22295                   ˇ
22296                   struct Row8;
22297                   ˇstruct Row9;
22298                   struct Row10;ˇ"#},
22299        base_text,
22300        &mut cx,
22301    );
22302}
22303
22304#[gpui::test]
22305async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
22306    init_test(cx, |_| {});
22307    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22308    let base_text = indoc! {r#"
22309        one
22310
22311        two
22312        three
22313        "#};
22314
22315    cx.set_head_text(base_text);
22316    cx.set_state("\nˇ\n");
22317    cx.executor().run_until_parked();
22318    cx.update_editor(|editor, _window, cx| {
22319        editor.expand_selected_diff_hunks(cx);
22320    });
22321    cx.executor().run_until_parked();
22322    cx.update_editor(|editor, window, cx| {
22323        editor.backspace(&Default::default(), window, cx);
22324    });
22325    cx.run_until_parked();
22326    cx.assert_state_with_diff(
22327        indoc! {r#"
22328
22329        - two
22330        - threeˇ
22331        +
22332        "#}
22333        .to_string(),
22334    );
22335}
22336
22337#[gpui::test]
22338async fn test_deletion_reverts(cx: &mut TestAppContext) {
22339    init_test(cx, |_| {});
22340    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
22341    let base_text = indoc! {r#"struct Row;
22342struct Row1;
22343struct Row2;
22344
22345struct Row4;
22346struct Row5;
22347struct Row6;
22348
22349struct Row8;
22350struct Row9;
22351struct Row10;"#};
22352
22353    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
22354    assert_hunk_revert(
22355        indoc! {r#"struct Row;
22356                   struct Row2;
22357
22358                   ˇstruct Row4;
22359                   struct Row5;
22360                   struct Row6;
22361                   ˇ
22362                   struct Row8;
22363                   struct Row10;"#},
22364        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22365        indoc! {r#"struct Row;
22366                   struct Row2;
22367
22368                   ˇstruct Row4;
22369                   struct Row5;
22370                   struct Row6;
22371                   ˇ
22372                   struct Row8;
22373                   struct Row10;"#},
22374        base_text,
22375        &mut cx,
22376    );
22377    assert_hunk_revert(
22378        indoc! {r#"struct Row;
22379                   struct Row2;
22380
22381                   «ˇstruct Row4;
22382                   struct» Row5;
22383                   «struct Row6;
22384                   ˇ»
22385                   struct Row8;
22386                   struct Row10;"#},
22387        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22388        indoc! {r#"struct Row;
22389                   struct Row2;
22390
22391                   «ˇstruct Row4;
22392                   struct» Row5;
22393                   «struct Row6;
22394                   ˇ»
22395                   struct Row8;
22396                   struct Row10;"#},
22397        base_text,
22398        &mut cx,
22399    );
22400
22401    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
22402    assert_hunk_revert(
22403        indoc! {r#"struct Row;
22404                   ˇstruct Row2;
22405
22406                   struct Row4;
22407                   struct Row5;
22408                   struct Row6;
22409
22410                   struct Row8;ˇ
22411                   struct Row10;"#},
22412        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
22413        indoc! {r#"struct Row;
22414                   struct Row1;
22415                   ˇstruct Row2;
22416
22417                   struct Row4;
22418                   struct Row5;
22419                   struct Row6;
22420
22421                   struct Row8;ˇ
22422                   struct Row9;
22423                   struct Row10;"#},
22424        base_text,
22425        &mut cx,
22426    );
22427    assert_hunk_revert(
22428        indoc! {r#"struct Row;
22429                   struct Row2«ˇ;
22430                   struct Row4;
22431                   struct» Row5;
22432                   «struct Row6;
22433
22434                   struct Row8;ˇ»
22435                   struct Row10;"#},
22436        vec![
22437            DiffHunkStatusKind::Deleted,
22438            DiffHunkStatusKind::Deleted,
22439            DiffHunkStatusKind::Deleted,
22440        ],
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}
22456
22457#[gpui::test]
22458async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
22459    init_test(cx, |_| {});
22460
22461    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
22462    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
22463    let base_text_3 =
22464        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
22465
22466    let text_1 = edit_first_char_of_every_line(base_text_1);
22467    let text_2 = edit_first_char_of_every_line(base_text_2);
22468    let text_3 = edit_first_char_of_every_line(base_text_3);
22469
22470    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
22471    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
22472    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
22473
22474    let multibuffer = cx.new(|cx| {
22475        let mut multibuffer = MultiBuffer::new(ReadWrite);
22476        multibuffer.set_excerpts_for_path(
22477            PathKey::sorted(0),
22478            buffer_1.clone(),
22479            [
22480                Point::new(0, 0)..Point::new(2, 0),
22481                Point::new(5, 0)..Point::new(6, 0),
22482                Point::new(9, 0)..Point::new(9, 4),
22483            ],
22484            0,
22485            cx,
22486        );
22487        multibuffer.set_excerpts_for_path(
22488            PathKey::sorted(1),
22489            buffer_2.clone(),
22490            [
22491                Point::new(0, 0)..Point::new(2, 0),
22492                Point::new(5, 0)..Point::new(6, 0),
22493                Point::new(9, 0)..Point::new(9, 4),
22494            ],
22495            0,
22496            cx,
22497        );
22498        multibuffer.set_excerpts_for_path(
22499            PathKey::sorted(2),
22500            buffer_3.clone(),
22501            [
22502                Point::new(0, 0)..Point::new(2, 0),
22503                Point::new(5, 0)..Point::new(6, 0),
22504                Point::new(9, 0)..Point::new(9, 4),
22505            ],
22506            0,
22507            cx,
22508        );
22509        multibuffer
22510    });
22511
22512    let fs = FakeFs::new(cx.executor());
22513    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22514    let (editor, cx) = cx
22515        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
22516    editor.update_in(cx, |editor, _window, cx| {
22517        for (buffer, diff_base) in [
22518            (buffer_1.clone(), base_text_1),
22519            (buffer_2.clone(), base_text_2),
22520            (buffer_3.clone(), base_text_3),
22521        ] {
22522            let diff = cx.new(|cx| {
22523                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
22524            });
22525            editor
22526                .buffer
22527                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
22528        }
22529    });
22530    cx.executor().run_until_parked();
22531
22532    editor.update_in(cx, |editor, window, cx| {
22533        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}");
22534        editor.select_all(&SelectAll, window, cx);
22535        editor.git_restore(&Default::default(), window, cx);
22536    });
22537    cx.executor().run_until_parked();
22538
22539    // When all ranges are selected, all buffer hunks are reverted.
22540    editor.update(cx, |editor, cx| {
22541        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");
22542    });
22543    buffer_1.update(cx, |buffer, _| {
22544        assert_eq!(buffer.text(), base_text_1);
22545    });
22546    buffer_2.update(cx, |buffer, _| {
22547        assert_eq!(buffer.text(), base_text_2);
22548    });
22549    buffer_3.update(cx, |buffer, _| {
22550        assert_eq!(buffer.text(), base_text_3);
22551    });
22552
22553    editor.update_in(cx, |editor, window, cx| {
22554        editor.undo(&Default::default(), window, cx);
22555    });
22556
22557    editor.update_in(cx, |editor, window, cx| {
22558        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22559            s.select_ranges(Some(Point::new(0, 0)..Point::new(5, 0)));
22560        });
22561        editor.git_restore(&Default::default(), window, cx);
22562    });
22563
22564    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
22565    // but not affect buffer_2 and its related excerpts.
22566    editor.update(cx, |editor, cx| {
22567        assert_eq!(
22568            editor.display_text(cx),
22569            "\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}"
22570        );
22571    });
22572    buffer_1.update(cx, |buffer, _| {
22573        assert_eq!(buffer.text(), base_text_1);
22574    });
22575    buffer_2.update(cx, |buffer, _| {
22576        assert_eq!(
22577            buffer.text(),
22578            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
22579        );
22580    });
22581    buffer_3.update(cx, |buffer, _| {
22582        assert_eq!(
22583            buffer.text(),
22584            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
22585        );
22586    });
22587
22588    fn edit_first_char_of_every_line(text: &str) -> String {
22589        text.split('\n')
22590            .map(|line| format!("X{}", &line[1..]))
22591            .collect::<Vec<_>>()
22592            .join("\n")
22593    }
22594}
22595
22596#[gpui::test]
22597async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
22598    init_test(cx, |_| {});
22599
22600    let cols = 4;
22601    let rows = 10;
22602    let sample_text_1 = sample_text(rows, cols, 'a');
22603    assert_eq!(
22604        sample_text_1,
22605        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
22606    );
22607    let sample_text_2 = sample_text(rows, cols, 'l');
22608    assert_eq!(
22609        sample_text_2,
22610        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
22611    );
22612    let sample_text_3 = sample_text(rows, cols, 'v');
22613    assert_eq!(
22614        sample_text_3,
22615        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
22616    );
22617
22618    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
22619    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
22620    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
22621
22622    let multi_buffer = cx.new(|cx| {
22623        let mut multibuffer = MultiBuffer::new(ReadWrite);
22624        multibuffer.set_excerpts_for_path(
22625            PathKey::sorted(0),
22626            buffer_1.clone(),
22627            [
22628                Point::new(0, 0)..Point::new(2, 0),
22629                Point::new(5, 0)..Point::new(6, 0),
22630                Point::new(9, 0)..Point::new(9, 4),
22631            ],
22632            0,
22633            cx,
22634        );
22635        multibuffer.set_excerpts_for_path(
22636            PathKey::sorted(1),
22637            buffer_2.clone(),
22638            [
22639                Point::new(0, 0)..Point::new(2, 0),
22640                Point::new(5, 0)..Point::new(6, 0),
22641                Point::new(9, 0)..Point::new(9, 4),
22642            ],
22643            0,
22644            cx,
22645        );
22646        multibuffer.set_excerpts_for_path(
22647            PathKey::sorted(2),
22648            buffer_3.clone(),
22649            [
22650                Point::new(0, 0)..Point::new(2, 0),
22651                Point::new(5, 0)..Point::new(6, 0),
22652                Point::new(9, 0)..Point::new(9, 4),
22653            ],
22654            0,
22655            cx,
22656        );
22657        multibuffer
22658    });
22659
22660    let fs = FakeFs::new(cx.executor());
22661    fs.insert_tree(
22662        "/a",
22663        json!({
22664            "main.rs": sample_text_1,
22665            "other.rs": sample_text_2,
22666            "lib.rs": sample_text_3,
22667        }),
22668    )
22669    .await;
22670    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22671    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
22672    let workspace = window
22673        .read_with(cx, |mw, _| mw.workspace().clone())
22674        .unwrap();
22675    let cx = &mut VisualTestContext::from_window(*window, cx);
22676    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22677        Editor::new(
22678            EditorMode::full(),
22679            multi_buffer,
22680            Some(project.clone()),
22681            window,
22682            cx,
22683        )
22684    });
22685    let multibuffer_item_id = workspace.update_in(cx, |workspace, window, cx| {
22686        assert!(
22687            workspace.active_item(cx).is_none(),
22688            "active item should be None before the first item is added"
22689        );
22690        workspace.add_item_to_active_pane(
22691            Box::new(multi_buffer_editor.clone()),
22692            None,
22693            true,
22694            window,
22695            cx,
22696        );
22697        let active_item = workspace
22698            .active_item(cx)
22699            .expect("should have an active item after adding the multi buffer");
22700        assert_eq!(
22701            active_item.buffer_kind(cx),
22702            ItemBufferKind::Multibuffer,
22703            "A multi buffer was expected to active after adding"
22704        );
22705        active_item.item_id()
22706    });
22707
22708    cx.executor().run_until_parked();
22709
22710    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22711        editor.change_selections(
22712            SelectionEffects::scroll(Autoscroll::Next),
22713            window,
22714            cx,
22715            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
22716        );
22717        editor.open_excerpts(&OpenExcerpts, window, cx);
22718    });
22719    cx.executor().run_until_parked();
22720    let first_item_id = workspace.update_in(cx, |workspace, window, cx| {
22721        let active_item = workspace
22722            .active_item(cx)
22723            .expect("should have an active item after navigating into the 1st buffer");
22724        let first_item_id = active_item.item_id();
22725        assert_ne!(
22726            first_item_id, multibuffer_item_id,
22727            "Should navigate into the 1st buffer and activate it"
22728        );
22729        assert_eq!(
22730            active_item.buffer_kind(cx),
22731            ItemBufferKind::Singleton,
22732            "New active item should be a singleton buffer"
22733        );
22734        assert_eq!(
22735            active_item
22736                .act_as::<Editor>(cx)
22737                .expect("should have navigated into an editor for the 1st buffer")
22738                .read(cx)
22739                .text(cx),
22740            sample_text_1
22741        );
22742
22743        workspace
22744            .go_back(workspace.active_pane().downgrade(), window, cx)
22745            .detach_and_log_err(cx);
22746
22747        first_item_id
22748    });
22749
22750    cx.executor().run_until_parked();
22751    workspace.update_in(cx, |workspace, _, cx| {
22752        let active_item = workspace
22753            .active_item(cx)
22754            .expect("should have an active item after navigating back");
22755        assert_eq!(
22756            active_item.item_id(),
22757            multibuffer_item_id,
22758            "Should navigate back to the multi buffer"
22759        );
22760        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22761    });
22762
22763    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22764        editor.change_selections(
22765            SelectionEffects::scroll(Autoscroll::Next),
22766            window,
22767            cx,
22768            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
22769        );
22770        editor.open_excerpts(&OpenExcerpts, window, cx);
22771    });
22772    cx.executor().run_until_parked();
22773    let second_item_id = workspace.update_in(cx, |workspace, window, cx| {
22774        let active_item = workspace
22775            .active_item(cx)
22776            .expect("should have an active item after navigating into the 2nd buffer");
22777        let second_item_id = active_item.item_id();
22778        assert_ne!(
22779            second_item_id, multibuffer_item_id,
22780            "Should navigate away from the multibuffer"
22781        );
22782        assert_ne!(
22783            second_item_id, first_item_id,
22784            "Should navigate into the 2nd buffer and activate it"
22785        );
22786        assert_eq!(
22787            active_item.buffer_kind(cx),
22788            ItemBufferKind::Singleton,
22789            "New active item should be a singleton buffer"
22790        );
22791        assert_eq!(
22792            active_item
22793                .act_as::<Editor>(cx)
22794                .expect("should have navigated into an editor")
22795                .read(cx)
22796                .text(cx),
22797            sample_text_2
22798        );
22799
22800        workspace
22801            .go_back(workspace.active_pane().downgrade(), window, cx)
22802            .detach_and_log_err(cx);
22803
22804        second_item_id
22805    });
22806
22807    cx.executor().run_until_parked();
22808    workspace.update_in(cx, |workspace, _, cx| {
22809        let active_item = workspace
22810            .active_item(cx)
22811            .expect("should have an active item after navigating back from the 2nd buffer");
22812        assert_eq!(
22813            active_item.item_id(),
22814            multibuffer_item_id,
22815            "Should navigate back from the 2nd buffer to the multi buffer"
22816        );
22817        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22818    });
22819
22820    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22821        editor.change_selections(
22822            SelectionEffects::scroll(Autoscroll::Next),
22823            window,
22824            cx,
22825            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
22826        );
22827        editor.open_excerpts(&OpenExcerpts, window, cx);
22828    });
22829    cx.executor().run_until_parked();
22830    workspace.update_in(cx, |workspace, window, cx| {
22831        let active_item = workspace
22832            .active_item(cx)
22833            .expect("should have an active item after navigating into the 3rd buffer");
22834        let third_item_id = active_item.item_id();
22835        assert_ne!(
22836            third_item_id, multibuffer_item_id,
22837            "Should navigate into the 3rd buffer and activate it"
22838        );
22839        assert_ne!(third_item_id, first_item_id);
22840        assert_ne!(third_item_id, second_item_id);
22841        assert_eq!(
22842            active_item.buffer_kind(cx),
22843            ItemBufferKind::Singleton,
22844            "New active item should be a singleton buffer"
22845        );
22846        assert_eq!(
22847            active_item
22848                .act_as::<Editor>(cx)
22849                .expect("should have navigated into an editor")
22850                .read(cx)
22851                .text(cx),
22852            sample_text_3
22853        );
22854
22855        workspace
22856            .go_back(workspace.active_pane().downgrade(), window, cx)
22857            .detach_and_log_err(cx);
22858    });
22859
22860    cx.executor().run_until_parked();
22861    workspace.update_in(cx, |workspace, _, cx| {
22862        let active_item = workspace
22863            .active_item(cx)
22864            .expect("should have an active item after navigating back from the 3rd buffer");
22865        assert_eq!(
22866            active_item.item_id(),
22867            multibuffer_item_id,
22868            "Should navigate back from the 3rd buffer to the multi buffer"
22869        );
22870        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
22871    });
22872}
22873
22874#[gpui::test]
22875async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22876    init_test(cx, |_| {});
22877
22878    let mut cx = EditorTestContext::new(cx).await;
22879
22880    let diff_base = r#"
22881        use some::mod;
22882
22883        const A: u32 = 42;
22884
22885        fn main() {
22886            println!("hello");
22887
22888            println!("world");
22889        }
22890        "#
22891    .unindent();
22892
22893    cx.set_state(
22894        &r#"
22895        use some::modified;
22896
22897        ˇ
22898        fn main() {
22899            println!("hello there");
22900
22901            println!("around the");
22902            println!("world");
22903        }
22904        "#
22905        .unindent(),
22906    );
22907
22908    cx.set_head_text(&diff_base);
22909    executor.run_until_parked();
22910
22911    cx.update_editor(|editor, window, cx| {
22912        editor.go_to_next_hunk(&GoToHunk, window, cx);
22913        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22914    });
22915    executor.run_until_parked();
22916    cx.assert_state_with_diff(
22917        r#"
22918          use some::modified;
22919
22920
22921          fn main() {
22922        -     println!("hello");
22923        + ˇ    println!("hello there");
22924
22925              println!("around the");
22926              println!("world");
22927          }
22928        "#
22929        .unindent(),
22930    );
22931
22932    cx.update_editor(|editor, window, cx| {
22933        for _ in 0..2 {
22934            editor.go_to_next_hunk(&GoToHunk, window, cx);
22935            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22936        }
22937    });
22938    executor.run_until_parked();
22939    cx.assert_state_with_diff(
22940        r#"
22941        - use some::mod;
22942        + ˇuse some::modified;
22943
22944
22945          fn main() {
22946        -     println!("hello");
22947        +     println!("hello there");
22948
22949        +     println!("around the");
22950              println!("world");
22951          }
22952        "#
22953        .unindent(),
22954    );
22955
22956    cx.update_editor(|editor, window, cx| {
22957        editor.go_to_next_hunk(&GoToHunk, window, cx);
22958        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22959    });
22960    executor.run_until_parked();
22961    cx.assert_state_with_diff(
22962        r#"
22963        - use some::mod;
22964        + use some::modified;
22965
22966        - const A: u32 = 42;
22967          ˇ
22968          fn main() {
22969        -     println!("hello");
22970        +     println!("hello there");
22971
22972        +     println!("around the");
22973              println!("world");
22974          }
22975        "#
22976        .unindent(),
22977    );
22978
22979    cx.update_editor(|editor, window, cx| {
22980        editor.cancel(&Cancel, window, cx);
22981    });
22982
22983    cx.assert_state_with_diff(
22984        r#"
22985          use some::modified;
22986
22987          ˇ
22988          fn main() {
22989              println!("hello there");
22990
22991              println!("around the");
22992              println!("world");
22993          }
22994        "#
22995        .unindent(),
22996    );
22997}
22998
22999#[gpui::test]
23000async fn test_diff_base_change_with_expanded_diff_hunks(
23001    executor: BackgroundExecutor,
23002    cx: &mut TestAppContext,
23003) {
23004    init_test(cx, |_| {});
23005
23006    let mut cx = EditorTestContext::new(cx).await;
23007
23008    let diff_base = r#"
23009        use some::mod1;
23010        use some::mod2;
23011
23012        const A: u32 = 42;
23013        const B: u32 = 42;
23014        const C: u32 = 42;
23015
23016        fn main() {
23017            println!("hello");
23018
23019            println!("world");
23020        }
23021        "#
23022    .unindent();
23023
23024    cx.set_state(
23025        &r#"
23026        use some::mod2;
23027
23028        const A: u32 = 42;
23029        const C: u32 = 42;
23030
23031        fn main(ˇ) {
23032            //println!("hello");
23033
23034            println!("world");
23035            //
23036            //
23037        }
23038        "#
23039        .unindent(),
23040    );
23041
23042    cx.set_head_text(&diff_base);
23043    executor.run_until_parked();
23044
23045    cx.update_editor(|editor, window, cx| {
23046        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23047    });
23048    executor.run_until_parked();
23049    cx.assert_state_with_diff(
23050        r#"
23051        - use some::mod1;
23052          use some::mod2;
23053
23054          const A: u32 = 42;
23055        - const B: u32 = 42;
23056          const C: u32 = 42;
23057
23058          fn main(ˇ) {
23059        -     println!("hello");
23060        +     //println!("hello");
23061
23062              println!("world");
23063        +     //
23064        +     //
23065          }
23066        "#
23067        .unindent(),
23068    );
23069
23070    cx.set_head_text("new diff base!");
23071    executor.run_until_parked();
23072    cx.assert_state_with_diff(
23073        r#"
23074        - new diff base!
23075        + use some::mod2;
23076        +
23077        + const A: u32 = 42;
23078        + const C: u32 = 42;
23079        +
23080        + fn main(ˇ) {
23081        +     //println!("hello");
23082        +
23083        +     println!("world");
23084        +     //
23085        +     //
23086        + }
23087        "#
23088        .unindent(),
23089    );
23090}
23091
23092#[gpui::test]
23093async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
23094    init_test(cx, |_| {});
23095
23096    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
23097    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
23098    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
23099    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
23100    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
23101    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
23102
23103    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
23104    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
23105    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
23106
23107    let multi_buffer = cx.new(|cx| {
23108        let mut multibuffer = MultiBuffer::new(ReadWrite);
23109        multibuffer.set_excerpts_for_path(
23110            PathKey::sorted(0),
23111            buffer_1.clone(),
23112            [
23113                Point::new(0, 0)..Point::new(2, 3),
23114                Point::new(5, 0)..Point::new(6, 3),
23115                Point::new(9, 0)..Point::new(10, 3),
23116            ],
23117            0,
23118            cx,
23119        );
23120        multibuffer.set_excerpts_for_path(
23121            PathKey::sorted(1),
23122            buffer_2.clone(),
23123            [
23124                Point::new(0, 0)..Point::new(2, 3),
23125                Point::new(5, 0)..Point::new(6, 3),
23126                Point::new(9, 0)..Point::new(10, 3),
23127            ],
23128            0,
23129            cx,
23130        );
23131        multibuffer.set_excerpts_for_path(
23132            PathKey::sorted(2),
23133            buffer_3.clone(),
23134            [
23135                Point::new(0, 0)..Point::new(2, 3),
23136                Point::new(5, 0)..Point::new(6, 3),
23137                Point::new(9, 0)..Point::new(10, 3),
23138            ],
23139            0,
23140            cx,
23141        );
23142        assert_eq!(multibuffer.read(cx).excerpts().count(), 9);
23143        multibuffer
23144    });
23145
23146    let editor =
23147        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
23148    editor
23149        .update(cx, |editor, _window, cx| {
23150            for (buffer, diff_base) in [
23151                (buffer_1.clone(), file_1_old),
23152                (buffer_2.clone(), file_2_old),
23153                (buffer_3.clone(), file_3_old),
23154            ] {
23155                let diff = cx.new(|cx| {
23156                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
23157                });
23158                editor
23159                    .buffer
23160                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
23161            }
23162        })
23163        .unwrap();
23164
23165    let mut cx = EditorTestContext::for_editor(editor, cx).await;
23166    cx.run_until_parked();
23167
23168    cx.assert_editor_state(
23169        &"
23170            ˇaaa
23171            ccc
23172            ddd
23173            ggg
23174            hhh
23175
23176            lll
23177            mmm
23178            NNN
23179            qqq
23180            rrr
23181            uuu
23182            111
23183            222
23184            333
23185            666
23186            777
23187            000
23188            !!!"
23189        .unindent(),
23190    );
23191
23192    cx.update_editor(|editor, window, cx| {
23193        editor.select_all(&SelectAll, window, cx);
23194        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23195    });
23196    cx.executor().run_until_parked();
23197
23198    cx.assert_state_with_diff(
23199        "
23200            «aaa
23201          - bbb
23202            ccc
23203            ddd
23204            ggg
23205            hhh
23206
23207            lll
23208            mmm
23209          - nnn
23210          + NNN
23211            qqq
23212            rrr
23213            uuu
23214            111
23215            222
23216            333
23217          + 666
23218            777
23219            000
23220            !!!ˇ»"
23221            .unindent(),
23222    );
23223}
23224
23225#[gpui::test]
23226async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
23227    init_test(cx, |_| {});
23228
23229    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
23230    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
23231
23232    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
23233    let multi_buffer = cx.new(|cx| {
23234        let mut multibuffer = MultiBuffer::new(ReadWrite);
23235        multibuffer.set_excerpts_for_path(
23236            PathKey::sorted(0),
23237            buffer.clone(),
23238            [
23239                Point::new(0, 0)..Point::new(1, 3),
23240                Point::new(4, 0)..Point::new(6, 3),
23241                Point::new(9, 0)..Point::new(9, 3),
23242            ],
23243            0,
23244            cx,
23245        );
23246        assert_eq!(multibuffer.read(cx).excerpts().count(), 3);
23247        multibuffer
23248    });
23249
23250    let editor =
23251        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
23252    editor
23253        .update(cx, |editor, _window, cx| {
23254            let diff = cx.new(|cx| {
23255                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
23256            });
23257            editor
23258                .buffer
23259                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
23260        })
23261        .unwrap();
23262
23263    let mut cx = EditorTestContext::for_editor(editor, cx).await;
23264    cx.run_until_parked();
23265
23266    cx.update_editor(|editor, window, cx| {
23267        editor.expand_all_diff_hunks(&Default::default(), window, cx)
23268    });
23269    cx.executor().run_until_parked();
23270
23271    // When the start of a hunk coincides with the start of its excerpt,
23272    // the hunk is expanded. When the start of a hunk is earlier than
23273    // the start of its excerpt, the hunk is not expanded.
23274    cx.assert_state_with_diff(
23275        "
23276            ˇaaa
23277          - bbb
23278          + BBB
23279          - ddd
23280          - eee
23281          + DDD
23282          + EEE
23283            fff
23284            iii"
23285        .unindent(),
23286    );
23287}
23288
23289#[gpui::test]
23290async fn test_edits_around_expanded_insertion_hunks(
23291    executor: BackgroundExecutor,
23292    cx: &mut TestAppContext,
23293) {
23294    init_test(cx, |_| {});
23295
23296    let mut cx = EditorTestContext::new(cx).await;
23297
23298    let diff_base = r#"
23299        use some::mod1;
23300        use some::mod2;
23301
23302        const A: u32 = 42;
23303
23304        fn main() {
23305            println!("hello");
23306
23307            println!("world");
23308        }
23309        "#
23310    .unindent();
23311    executor.run_until_parked();
23312    cx.set_state(
23313        &r#"
23314        use some::mod1;
23315        use some::mod2;
23316
23317        const A: u32 = 42;
23318        const B: u32 = 42;
23319        const C: u32 = 42;
23320        ˇ
23321
23322        fn main() {
23323            println!("hello");
23324
23325            println!("world");
23326        }
23327        "#
23328        .unindent(),
23329    );
23330
23331    cx.set_head_text(&diff_base);
23332    executor.run_until_parked();
23333
23334    cx.update_editor(|editor, window, cx| {
23335        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23336    });
23337    executor.run_until_parked();
23338
23339    cx.assert_state_with_diff(
23340        r#"
23341        use some::mod1;
23342        use some::mod2;
23343
23344        const A: u32 = 42;
23345      + const B: u32 = 42;
23346      + const C: u32 = 42;
23347      + ˇ
23348
23349        fn main() {
23350            println!("hello");
23351
23352            println!("world");
23353        }
23354      "#
23355        .unindent(),
23356    );
23357
23358    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
23359    executor.run_until_parked();
23360
23361    cx.assert_state_with_diff(
23362        r#"
23363        use some::mod1;
23364        use some::mod2;
23365
23366        const A: u32 = 42;
23367      + const B: u32 = 42;
23368      + const C: u32 = 42;
23369      + const D: u32 = 42;
23370      + ˇ
23371
23372        fn main() {
23373            println!("hello");
23374
23375            println!("world");
23376        }
23377      "#
23378        .unindent(),
23379    );
23380
23381    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
23382    executor.run_until_parked();
23383
23384    cx.assert_state_with_diff(
23385        r#"
23386        use some::mod1;
23387        use some::mod2;
23388
23389        const A: u32 = 42;
23390      + const B: u32 = 42;
23391      + const C: u32 = 42;
23392      + const D: u32 = 42;
23393      + const E: u32 = 42;
23394      + ˇ
23395
23396        fn main() {
23397            println!("hello");
23398
23399            println!("world");
23400        }
23401      "#
23402        .unindent(),
23403    );
23404
23405    cx.update_editor(|editor, window, cx| {
23406        editor.delete_line(&DeleteLine, window, cx);
23407    });
23408    executor.run_until_parked();
23409
23410    cx.assert_state_with_diff(
23411        r#"
23412        use some::mod1;
23413        use some::mod2;
23414
23415        const A: u32 = 42;
23416      + const B: u32 = 42;
23417      + const C: u32 = 42;
23418      + const D: u32 = 42;
23419      + const E: u32 = 42;
23420        ˇ
23421        fn main() {
23422            println!("hello");
23423
23424            println!("world");
23425        }
23426      "#
23427        .unindent(),
23428    );
23429
23430    cx.update_editor(|editor, window, cx| {
23431        editor.move_up(&MoveUp, window, cx);
23432        editor.delete_line(&DeleteLine, window, cx);
23433        editor.move_up(&MoveUp, window, cx);
23434        editor.delete_line(&DeleteLine, window, cx);
23435        editor.move_up(&MoveUp, window, cx);
23436        editor.delete_line(&DeleteLine, window, cx);
23437    });
23438    executor.run_until_parked();
23439    cx.assert_state_with_diff(
23440        r#"
23441        use some::mod1;
23442        use some::mod2;
23443
23444        const A: u32 = 42;
23445      + const B: u32 = 42;
23446        ˇ
23447        fn main() {
23448            println!("hello");
23449
23450            println!("world");
23451        }
23452      "#
23453        .unindent(),
23454    );
23455
23456    cx.update_editor(|editor, window, cx| {
23457        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
23458        editor.delete_line(&DeleteLine, window, cx);
23459    });
23460    executor.run_until_parked();
23461    cx.assert_state_with_diff(
23462        r#"
23463        ˇ
23464        fn main() {
23465            println!("hello");
23466
23467            println!("world");
23468        }
23469      "#
23470        .unindent(),
23471    );
23472}
23473
23474#[gpui::test]
23475async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
23476    init_test(cx, |_| {});
23477
23478    let mut cx = EditorTestContext::new(cx).await;
23479    cx.set_head_text(indoc! { "
23480        one
23481        two
23482        three
23483        four
23484        five
23485        "
23486    });
23487    cx.set_state(indoc! { "
23488        one
23489        ˇthree
23490        five
23491    "});
23492    cx.run_until_parked();
23493    cx.update_editor(|editor, window, cx| {
23494        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23495    });
23496    cx.assert_state_with_diff(
23497        indoc! { "
23498        one
23499      - two
23500        ˇthree
23501      - four
23502        five
23503    "}
23504        .to_string(),
23505    );
23506    cx.update_editor(|editor, window, cx| {
23507        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23508    });
23509
23510    cx.assert_state_with_diff(
23511        indoc! { "
23512        one
23513        ˇthree
23514        five
23515    "}
23516        .to_string(),
23517    );
23518
23519    cx.update_editor(|editor, window, cx| {
23520        editor.move_up(&MoveUp, window, cx);
23521        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23522    });
23523    cx.assert_state_with_diff(
23524        indoc! { "
23525        ˇone
23526      - two
23527        three
23528        five
23529    "}
23530        .to_string(),
23531    );
23532
23533    cx.update_editor(|editor, window, cx| {
23534        editor.move_down(&MoveDown, window, cx);
23535        editor.move_down(&MoveDown, window, cx);
23536        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23537    });
23538    cx.assert_state_with_diff(
23539        indoc! { "
23540        one
23541      - two
23542        ˇthree
23543      - four
23544        five
23545    "}
23546        .to_string(),
23547    );
23548
23549    cx.set_state(indoc! { "
23550        one
23551        ˇTWO
23552        three
23553        four
23554        five
23555    "});
23556    cx.run_until_parked();
23557    cx.update_editor(|editor, window, cx| {
23558        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23559    });
23560
23561    cx.assert_state_with_diff(
23562        indoc! { "
23563            one
23564          - two
23565          + ˇTWO
23566            three
23567            four
23568            five
23569        "}
23570        .to_string(),
23571    );
23572    cx.update_editor(|editor, window, cx| {
23573        editor.move_up(&Default::default(), window, cx);
23574        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
23575    });
23576    cx.assert_state_with_diff(
23577        indoc! { "
23578            one
23579            ˇTWO
23580            three
23581            four
23582            five
23583        "}
23584        .to_string(),
23585    );
23586}
23587
23588#[gpui::test]
23589async fn test_toggling_adjacent_diff_hunks_2(
23590    executor: BackgroundExecutor,
23591    cx: &mut TestAppContext,
23592) {
23593    init_test(cx, |_| {});
23594
23595    let mut cx = EditorTestContext::new(cx).await;
23596
23597    let diff_base = r#"
23598        lineA
23599        lineB
23600        lineC
23601        lineD
23602        "#
23603    .unindent();
23604
23605    cx.set_state(
23606        &r#"
23607        ˇlineA1
23608        lineB
23609        lineD
23610        "#
23611        .unindent(),
23612    );
23613    cx.set_head_text(&diff_base);
23614    executor.run_until_parked();
23615
23616    cx.update_editor(|editor, window, cx| {
23617        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23618    });
23619    executor.run_until_parked();
23620    cx.assert_state_with_diff(
23621        r#"
23622        - lineA
23623        + ˇlineA1
23624          lineB
23625          lineD
23626        "#
23627        .unindent(),
23628    );
23629
23630    cx.update_editor(|editor, window, cx| {
23631        editor.move_down(&MoveDown, window, cx);
23632        editor.move_right(&MoveRight, window, cx);
23633        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
23634    });
23635    executor.run_until_parked();
23636    cx.assert_state_with_diff(
23637        r#"
23638        - lineA
23639        + lineA1
23640          lˇineB
23641        - lineC
23642          lineD
23643        "#
23644        .unindent(),
23645    );
23646}
23647
23648#[gpui::test]
23649async fn test_edits_around_expanded_deletion_hunks(
23650    executor: BackgroundExecutor,
23651    cx: &mut TestAppContext,
23652) {
23653    init_test(cx, |_| {});
23654
23655    let mut cx = EditorTestContext::new(cx).await;
23656
23657    let diff_base = r#"
23658        use some::mod1;
23659        use some::mod2;
23660
23661        const A: u32 = 42;
23662        const B: u32 = 42;
23663        const C: u32 = 42;
23664
23665
23666        fn main() {
23667            println!("hello");
23668
23669            println!("world");
23670        }
23671    "#
23672    .unindent();
23673    executor.run_until_parked();
23674    cx.set_state(
23675        &r#"
23676        use some::mod1;
23677        use some::mod2;
23678
23679        ˇconst B: u32 = 42;
23680        const C: u32 = 42;
23681
23682
23683        fn main() {
23684            println!("hello");
23685
23686            println!("world");
23687        }
23688        "#
23689        .unindent(),
23690    );
23691
23692    cx.set_head_text(&diff_base);
23693    executor.run_until_parked();
23694
23695    cx.update_editor(|editor, window, cx| {
23696        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23697    });
23698    executor.run_until_parked();
23699
23700    cx.assert_state_with_diff(
23701        r#"
23702        use some::mod1;
23703        use some::mod2;
23704
23705      - const A: u32 = 42;
23706        ˇconst B: u32 = 42;
23707        const C: u32 = 42;
23708
23709
23710        fn main() {
23711            println!("hello");
23712
23713            println!("world");
23714        }
23715      "#
23716        .unindent(),
23717    );
23718
23719    cx.update_editor(|editor, window, cx| {
23720        editor.delete_line(&DeleteLine, window, cx);
23721    });
23722    executor.run_until_parked();
23723    cx.assert_state_with_diff(
23724        r#"
23725        use some::mod1;
23726        use some::mod2;
23727
23728      - const A: u32 = 42;
23729      - const B: u32 = 42;
23730        ˇconst C: u32 = 42;
23731
23732
23733        fn main() {
23734            println!("hello");
23735
23736            println!("world");
23737        }
23738      "#
23739        .unindent(),
23740    );
23741
23742    cx.update_editor(|editor, window, cx| {
23743        editor.delete_line(&DeleteLine, window, cx);
23744    });
23745    executor.run_until_parked();
23746    cx.assert_state_with_diff(
23747        r#"
23748        use some::mod1;
23749        use some::mod2;
23750
23751      - const A: u32 = 42;
23752      - const B: u32 = 42;
23753      - const C: u32 = 42;
23754        ˇ
23755
23756        fn main() {
23757            println!("hello");
23758
23759            println!("world");
23760        }
23761      "#
23762        .unindent(),
23763    );
23764
23765    cx.update_editor(|editor, window, cx| {
23766        editor.handle_input("replacement", window, cx);
23767    });
23768    executor.run_until_parked();
23769    cx.assert_state_with_diff(
23770        r#"
23771        use some::mod1;
23772        use some::mod2;
23773
23774      - const A: u32 = 42;
23775      - const B: u32 = 42;
23776      - const C: u32 = 42;
23777      -
23778      + replacementˇ
23779
23780        fn main() {
23781            println!("hello");
23782
23783            println!("world");
23784        }
23785      "#
23786        .unindent(),
23787    );
23788}
23789
23790#[gpui::test]
23791async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
23792    init_test(cx, |_| {});
23793
23794    let mut cx = EditorTestContext::new(cx).await;
23795
23796    let base_text = r#"
23797        one
23798        two
23799        three
23800        four
23801        five
23802    "#
23803    .unindent();
23804    executor.run_until_parked();
23805    cx.set_state(
23806        &r#"
23807        one
23808        two
23809        fˇour
23810        five
23811        "#
23812        .unindent(),
23813    );
23814
23815    cx.set_head_text(&base_text);
23816    executor.run_until_parked();
23817
23818    cx.update_editor(|editor, window, cx| {
23819        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23820    });
23821    executor.run_until_parked();
23822
23823    cx.assert_state_with_diff(
23824        r#"
23825          one
23826          two
23827        - three
23828          fˇour
23829          five
23830        "#
23831        .unindent(),
23832    );
23833
23834    cx.update_editor(|editor, window, cx| {
23835        editor.backspace(&Backspace, window, cx);
23836        editor.backspace(&Backspace, window, cx);
23837    });
23838    executor.run_until_parked();
23839    cx.assert_state_with_diff(
23840        r#"
23841          one
23842          two
23843        - threeˇ
23844        - four
23845        + our
23846          five
23847        "#
23848        .unindent(),
23849    );
23850}
23851
23852#[gpui::test]
23853async fn test_edit_after_expanded_modification_hunk(
23854    executor: BackgroundExecutor,
23855    cx: &mut TestAppContext,
23856) {
23857    init_test(cx, |_| {});
23858
23859    let mut cx = EditorTestContext::new(cx).await;
23860
23861    let diff_base = r#"
23862        use some::mod1;
23863        use some::mod2;
23864
23865        const A: u32 = 42;
23866        const B: u32 = 42;
23867        const C: u32 = 42;
23868        const D: u32 = 42;
23869
23870
23871        fn main() {
23872            println!("hello");
23873
23874            println!("world");
23875        }"#
23876    .unindent();
23877
23878    cx.set_state(
23879        &r#"
23880        use some::mod1;
23881        use some::mod2;
23882
23883        const A: u32 = 42;
23884        const B: u32 = 42;
23885        const C: u32 = 43ˇ
23886        const D: u32 = 42;
23887
23888
23889        fn main() {
23890            println!("hello");
23891
23892            println!("world");
23893        }"#
23894        .unindent(),
23895    );
23896
23897    cx.set_head_text(&diff_base);
23898    executor.run_until_parked();
23899    cx.update_editor(|editor, window, cx| {
23900        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23901    });
23902    executor.run_until_parked();
23903
23904    cx.assert_state_with_diff(
23905        r#"
23906        use some::mod1;
23907        use some::mod2;
23908
23909        const A: u32 = 42;
23910        const B: u32 = 42;
23911      - const C: u32 = 42;
23912      + const C: u32 = 43ˇ
23913        const D: u32 = 42;
23914
23915
23916        fn main() {
23917            println!("hello");
23918
23919            println!("world");
23920        }"#
23921        .unindent(),
23922    );
23923
23924    cx.update_editor(|editor, window, cx| {
23925        editor.handle_input("\nnew_line\n", window, cx);
23926    });
23927    executor.run_until_parked();
23928
23929    cx.assert_state_with_diff(
23930        r#"
23931        use some::mod1;
23932        use some::mod2;
23933
23934        const A: u32 = 42;
23935        const B: u32 = 42;
23936      - const C: u32 = 42;
23937      + const C: u32 = 43
23938      + new_line
23939      + ˇ
23940        const D: u32 = 42;
23941
23942
23943        fn main() {
23944            println!("hello");
23945
23946            println!("world");
23947        }"#
23948        .unindent(),
23949    );
23950}
23951
23952#[gpui::test]
23953async fn test_stage_and_unstage_added_file_hunk(
23954    executor: BackgroundExecutor,
23955    cx: &mut TestAppContext,
23956) {
23957    init_test(cx, |_| {});
23958
23959    let mut cx = EditorTestContext::new(cx).await;
23960    cx.update_editor(|editor, _, cx| {
23961        editor.set_expand_all_diff_hunks(cx);
23962    });
23963
23964    let working_copy = r#"
23965            ˇfn main() {
23966                println!("hello, world!");
23967            }
23968        "#
23969    .unindent();
23970
23971    cx.set_state(&working_copy);
23972    executor.run_until_parked();
23973
23974    cx.assert_state_with_diff(
23975        r#"
23976            + ˇfn main() {
23977            +     println!("hello, world!");
23978            + }
23979        "#
23980        .unindent(),
23981    );
23982    cx.assert_index_text(None);
23983
23984    cx.update_editor(|editor, window, cx| {
23985        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23986    });
23987    executor.run_until_parked();
23988    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
23989    cx.assert_state_with_diff(
23990        r#"
23991            + ˇfn main() {
23992            +     println!("hello, world!");
23993            + }
23994        "#
23995        .unindent(),
23996    );
23997
23998    cx.update_editor(|editor, window, cx| {
23999        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
24000    });
24001    executor.run_until_parked();
24002    cx.assert_index_text(None);
24003}
24004
24005async fn setup_indent_guides_editor(
24006    text: &str,
24007    cx: &mut TestAppContext,
24008) -> (BufferId, EditorTestContext) {
24009    init_test(cx, |_| {});
24010
24011    let mut cx = EditorTestContext::new(cx).await;
24012
24013    let buffer_id = cx.update_editor(|editor, window, cx| {
24014        editor.set_text(text, window, cx);
24015        editor
24016            .buffer()
24017            .read(cx)
24018            .as_singleton()
24019            .unwrap()
24020            .read(cx)
24021            .remote_id()
24022    });
24023
24024    (buffer_id, cx)
24025}
24026
24027fn assert_indent_guides(
24028    range: Range<u32>,
24029    expected: Vec<IndentGuide>,
24030    active_indices: Option<Vec<usize>>,
24031    cx: &mut EditorTestContext,
24032) {
24033    let indent_guides = cx.update_editor(|editor, window, cx| {
24034        let snapshot = editor.snapshot(window, cx).display_snapshot;
24035        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
24036            editor,
24037            MultiBufferRow(range.start)..MultiBufferRow(range.end),
24038            true,
24039            &snapshot,
24040            cx,
24041        );
24042
24043        indent_guides.sort_by(|a, b| {
24044            a.depth.cmp(&b.depth).then(
24045                a.start_row
24046                    .cmp(&b.start_row)
24047                    .then(a.end_row.cmp(&b.end_row)),
24048            )
24049        });
24050        indent_guides
24051    });
24052
24053    if let Some(expected) = active_indices {
24054        let active_indices = cx.update_editor(|editor, window, cx| {
24055            let snapshot = editor.snapshot(window, cx).display_snapshot;
24056            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
24057        });
24058
24059        assert_eq!(
24060            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
24061            expected,
24062            "Active indent guide indices do not match"
24063        );
24064    }
24065
24066    assert_eq!(indent_guides, expected, "Indent guides do not match");
24067}
24068
24069fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
24070    IndentGuide {
24071        buffer_id,
24072        start_row: MultiBufferRow(start_row),
24073        end_row: MultiBufferRow(end_row),
24074        depth,
24075        tab_size: 4,
24076        settings: IndentGuideSettings {
24077            enabled: true,
24078            line_width: 1,
24079            active_line_width: 1,
24080            coloring: IndentGuideColoring::default(),
24081            background_coloring: IndentGuideBackgroundColoring::default(),
24082        },
24083    }
24084}
24085
24086#[gpui::test]
24087async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
24088    let (buffer_id, mut cx) = setup_indent_guides_editor(
24089        &"
24090        fn main() {
24091            let a = 1;
24092        }"
24093        .unindent(),
24094        cx,
24095    )
24096    .await;
24097
24098    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
24099}
24100
24101#[gpui::test]
24102async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
24103    let (buffer_id, mut cx) = setup_indent_guides_editor(
24104        &"
24105        fn main() {
24106            let a = 1;
24107            let b = 2;
24108        }"
24109        .unindent(),
24110        cx,
24111    )
24112    .await;
24113
24114    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
24115}
24116
24117#[gpui::test]
24118async fn test_indent_guide_nested(cx: &mut TestAppContext) {
24119    let (buffer_id, mut cx) = setup_indent_guides_editor(
24120        &"
24121        fn main() {
24122            let a = 1;
24123            if a == 3 {
24124                let b = 2;
24125            } else {
24126                let c = 3;
24127            }
24128        }"
24129        .unindent(),
24130        cx,
24131    )
24132    .await;
24133
24134    assert_indent_guides(
24135        0..8,
24136        vec![
24137            indent_guide(buffer_id, 1, 6, 0),
24138            indent_guide(buffer_id, 3, 3, 1),
24139            indent_guide(buffer_id, 5, 5, 1),
24140        ],
24141        None,
24142        &mut cx,
24143    );
24144}
24145
24146#[gpui::test]
24147async fn test_indent_guide_tab(cx: &mut TestAppContext) {
24148    let (buffer_id, mut cx) = setup_indent_guides_editor(
24149        &"
24150        fn main() {
24151            let a = 1;
24152                let b = 2;
24153            let c = 3;
24154        }"
24155        .unindent(),
24156        cx,
24157    )
24158    .await;
24159
24160    assert_indent_guides(
24161        0..5,
24162        vec![
24163            indent_guide(buffer_id, 1, 3, 0),
24164            indent_guide(buffer_id, 2, 2, 1),
24165        ],
24166        None,
24167        &mut cx,
24168    );
24169}
24170
24171#[gpui::test]
24172async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
24173    let (buffer_id, mut cx) = setup_indent_guides_editor(
24174        &"
24175        fn main() {
24176            let a = 1;
24177
24178            let c = 3;
24179        }"
24180        .unindent(),
24181        cx,
24182    )
24183    .await;
24184
24185    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
24186}
24187
24188#[gpui::test]
24189async fn test_indent_guide_complex(cx: &mut TestAppContext) {
24190    let (buffer_id, mut cx) = setup_indent_guides_editor(
24191        &"
24192        fn main() {
24193            let a = 1;
24194
24195            let c = 3;
24196
24197            if a == 3 {
24198                let b = 2;
24199            } else {
24200                let c = 3;
24201            }
24202        }"
24203        .unindent(),
24204        cx,
24205    )
24206    .await;
24207
24208    assert_indent_guides(
24209        0..11,
24210        vec![
24211            indent_guide(buffer_id, 1, 9, 0),
24212            indent_guide(buffer_id, 6, 6, 1),
24213            indent_guide(buffer_id, 8, 8, 1),
24214        ],
24215        None,
24216        &mut cx,
24217    );
24218}
24219
24220#[gpui::test]
24221async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
24222    let (buffer_id, mut cx) = setup_indent_guides_editor(
24223        &"
24224        fn main() {
24225            let a = 1;
24226
24227            let c = 3;
24228
24229            if a == 3 {
24230                let b = 2;
24231            } else {
24232                let c = 3;
24233            }
24234        }"
24235        .unindent(),
24236        cx,
24237    )
24238    .await;
24239
24240    assert_indent_guides(
24241        1..11,
24242        vec![
24243            indent_guide(buffer_id, 1, 9, 0),
24244            indent_guide(buffer_id, 6, 6, 1),
24245            indent_guide(buffer_id, 8, 8, 1),
24246        ],
24247        None,
24248        &mut cx,
24249    );
24250}
24251
24252#[gpui::test]
24253async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
24254    let (buffer_id, mut cx) = setup_indent_guides_editor(
24255        &"
24256        fn main() {
24257            let a = 1;
24258
24259            let c = 3;
24260
24261            if a == 3 {
24262                let b = 2;
24263            } else {
24264                let c = 3;
24265            }
24266        }"
24267        .unindent(),
24268        cx,
24269    )
24270    .await;
24271
24272    assert_indent_guides(
24273        1..10,
24274        vec![
24275            indent_guide(buffer_id, 1, 9, 0),
24276            indent_guide(buffer_id, 6, 6, 1),
24277            indent_guide(buffer_id, 8, 8, 1),
24278        ],
24279        None,
24280        &mut cx,
24281    );
24282}
24283
24284#[gpui::test]
24285async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
24286    let (buffer_id, mut cx) = setup_indent_guides_editor(
24287        &"
24288        fn main() {
24289            if a {
24290                b(
24291                    c,
24292                    d,
24293                )
24294            } else {
24295                e(
24296                    f
24297                )
24298            }
24299        }"
24300        .unindent(),
24301        cx,
24302    )
24303    .await;
24304
24305    assert_indent_guides(
24306        0..11,
24307        vec![
24308            indent_guide(buffer_id, 1, 10, 0),
24309            indent_guide(buffer_id, 2, 5, 1),
24310            indent_guide(buffer_id, 7, 9, 1),
24311            indent_guide(buffer_id, 3, 4, 2),
24312            indent_guide(buffer_id, 8, 8, 2),
24313        ],
24314        None,
24315        &mut cx,
24316    );
24317
24318    cx.update_editor(|editor, window, cx| {
24319        editor.fold_at(MultiBufferRow(2), window, cx);
24320        assert_eq!(
24321            editor.display_text(cx),
24322            "
24323            fn main() {
24324                if a {
24325                    b(⋯)
24326                } else {
24327                    e(
24328                        f
24329                    )
24330                }
24331            }"
24332            .unindent()
24333        );
24334    });
24335
24336    assert_indent_guides(
24337        0..11,
24338        vec![
24339            indent_guide(buffer_id, 1, 10, 0),
24340            indent_guide(buffer_id, 2, 5, 1),
24341            indent_guide(buffer_id, 7, 9, 1),
24342            indent_guide(buffer_id, 8, 8, 2),
24343        ],
24344        None,
24345        &mut cx,
24346    );
24347}
24348
24349#[gpui::test]
24350async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
24351    let (buffer_id, mut cx) = setup_indent_guides_editor(
24352        &"
24353        block1
24354            block2
24355                block3
24356                    block4
24357            block2
24358        block1
24359        block1"
24360            .unindent(),
24361        cx,
24362    )
24363    .await;
24364
24365    assert_indent_guides(
24366        1..10,
24367        vec![
24368            indent_guide(buffer_id, 1, 4, 0),
24369            indent_guide(buffer_id, 2, 3, 1),
24370            indent_guide(buffer_id, 3, 3, 2),
24371        ],
24372        None,
24373        &mut cx,
24374    );
24375}
24376
24377#[gpui::test]
24378async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
24379    let (buffer_id, mut cx) = setup_indent_guides_editor(
24380        &"
24381        block1
24382            block2
24383                block3
24384
24385        block1
24386        block1"
24387            .unindent(),
24388        cx,
24389    )
24390    .await;
24391
24392    assert_indent_guides(
24393        0..6,
24394        vec![
24395            indent_guide(buffer_id, 1, 2, 0),
24396            indent_guide(buffer_id, 2, 2, 1),
24397        ],
24398        None,
24399        &mut cx,
24400    );
24401}
24402
24403#[gpui::test]
24404async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
24405    let (buffer_id, mut cx) = setup_indent_guides_editor(
24406        &"
24407        function component() {
24408        \treturn (
24409        \t\t\t
24410        \t\t<div>
24411        \t\t\t<abc></abc>
24412        \t\t</div>
24413        \t)
24414        }"
24415        .unindent(),
24416        cx,
24417    )
24418    .await;
24419
24420    assert_indent_guides(
24421        0..8,
24422        vec![
24423            indent_guide(buffer_id, 1, 6, 0),
24424            indent_guide(buffer_id, 2, 5, 1),
24425            indent_guide(buffer_id, 4, 4, 2),
24426        ],
24427        None,
24428        &mut cx,
24429    );
24430}
24431
24432#[gpui::test]
24433async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
24434    let (buffer_id, mut cx) = setup_indent_guides_editor(
24435        &"
24436        function component() {
24437        \treturn (
24438        \t
24439        \t\t<div>
24440        \t\t\t<abc></abc>
24441        \t\t</div>
24442        \t)
24443        }"
24444        .unindent(),
24445        cx,
24446    )
24447    .await;
24448
24449    assert_indent_guides(
24450        0..8,
24451        vec![
24452            indent_guide(buffer_id, 1, 6, 0),
24453            indent_guide(buffer_id, 2, 5, 1),
24454            indent_guide(buffer_id, 4, 4, 2),
24455        ],
24456        None,
24457        &mut cx,
24458    );
24459}
24460
24461#[gpui::test]
24462async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
24463    let (buffer_id, mut cx) = setup_indent_guides_editor(
24464        &"
24465        block1
24466
24467
24468
24469            block2
24470        "
24471        .unindent(),
24472        cx,
24473    )
24474    .await;
24475
24476    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
24477}
24478
24479#[gpui::test]
24480async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
24481    let (buffer_id, mut cx) = setup_indent_guides_editor(
24482        &"
24483        def a:
24484        \tb = 3
24485        \tif True:
24486        \t\tc = 4
24487        \t\td = 5
24488        \tprint(b)
24489        "
24490        .unindent(),
24491        cx,
24492    )
24493    .await;
24494
24495    assert_indent_guides(
24496        0..6,
24497        vec![
24498            indent_guide(buffer_id, 1, 5, 0),
24499            indent_guide(buffer_id, 3, 4, 1),
24500        ],
24501        None,
24502        &mut cx,
24503    );
24504}
24505
24506#[gpui::test]
24507async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
24508    let (buffer_id, mut cx) = setup_indent_guides_editor(
24509        &"
24510    fn main() {
24511        let a = 1;
24512    }"
24513        .unindent(),
24514        cx,
24515    )
24516    .await;
24517
24518    cx.update_editor(|editor, window, cx| {
24519        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24520            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
24521        });
24522    });
24523
24524    assert_indent_guides(
24525        0..3,
24526        vec![indent_guide(buffer_id, 1, 1, 0)],
24527        Some(vec![0]),
24528        &mut cx,
24529    );
24530}
24531
24532#[gpui::test]
24533async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
24534    let (buffer_id, mut cx) = setup_indent_guides_editor(
24535        &"
24536    fn main() {
24537        if 1 == 2 {
24538            let a = 1;
24539        }
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    cx.run_until_parked();
24552
24553    assert_indent_guides(
24554        0..4,
24555        vec![
24556            indent_guide(buffer_id, 1, 3, 0),
24557            indent_guide(buffer_id, 2, 2, 1),
24558        ],
24559        Some(vec![1]),
24560        &mut cx,
24561    );
24562
24563    cx.update_editor(|editor, window, cx| {
24564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24565            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
24566        });
24567    });
24568    cx.run_until_parked();
24569
24570    assert_indent_guides(
24571        0..4,
24572        vec![
24573            indent_guide(buffer_id, 1, 3, 0),
24574            indent_guide(buffer_id, 2, 2, 1),
24575        ],
24576        Some(vec![1]),
24577        &mut cx,
24578    );
24579
24580    cx.update_editor(|editor, window, cx| {
24581        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24582            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
24583        });
24584    });
24585    cx.run_until_parked();
24586
24587    assert_indent_guides(
24588        0..4,
24589        vec![
24590            indent_guide(buffer_id, 1, 3, 0),
24591            indent_guide(buffer_id, 2, 2, 1),
24592        ],
24593        Some(vec![0]),
24594        &mut cx,
24595    );
24596}
24597
24598#[gpui::test]
24599async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
24600    let (buffer_id, mut cx) = setup_indent_guides_editor(
24601        &"
24602    fn main() {
24603        let a = 1;
24604
24605        let b = 2;
24606    }"
24607        .unindent(),
24608        cx,
24609    )
24610    .await;
24611
24612    cx.update_editor(|editor, window, cx| {
24613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24614            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
24615        });
24616    });
24617
24618    assert_indent_guides(
24619        0..5,
24620        vec![indent_guide(buffer_id, 1, 3, 0)],
24621        Some(vec![0]),
24622        &mut cx,
24623    );
24624}
24625
24626#[gpui::test]
24627async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
24628    let (buffer_id, mut cx) = setup_indent_guides_editor(
24629        &"
24630    def m:
24631        a = 1
24632        pass"
24633            .unindent(),
24634        cx,
24635    )
24636    .await;
24637
24638    cx.update_editor(|editor, window, cx| {
24639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24640            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
24641        });
24642    });
24643
24644    assert_indent_guides(
24645        0..3,
24646        vec![indent_guide(buffer_id, 1, 2, 0)],
24647        Some(vec![0]),
24648        &mut cx,
24649    );
24650}
24651
24652#[gpui::test]
24653async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
24654    init_test(cx, |_| {});
24655    let mut cx = EditorTestContext::new(cx).await;
24656    let text = indoc! {
24657        "
24658        impl A {
24659            fn b() {
24660                0;
24661                3;
24662                5;
24663                6;
24664                7;
24665            }
24666        }
24667        "
24668    };
24669    let base_text = indoc! {
24670        "
24671        impl A {
24672            fn b() {
24673                0;
24674                1;
24675                2;
24676                3;
24677                4;
24678            }
24679            fn c() {
24680                5;
24681                6;
24682                7;
24683            }
24684        }
24685        "
24686    };
24687
24688    cx.update_editor(|editor, window, cx| {
24689        editor.set_text(text, window, cx);
24690
24691        editor.buffer().update(cx, |multibuffer, cx| {
24692            let buffer = multibuffer.as_singleton().unwrap();
24693            let diff = cx.new(|cx| {
24694                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
24695            });
24696
24697            multibuffer.set_all_diff_hunks_expanded(cx);
24698            multibuffer.add_diff(diff, cx);
24699
24700            buffer.read(cx).remote_id()
24701        })
24702    });
24703    cx.run_until_parked();
24704
24705    cx.assert_state_with_diff(
24706        indoc! { "
24707          impl A {
24708              fn b() {
24709                  0;
24710        -         1;
24711        -         2;
24712                  3;
24713        -         4;
24714        -     }
24715        -     fn c() {
24716                  5;
24717                  6;
24718                  7;
24719              }
24720          }
24721          ˇ"
24722        }
24723        .to_string(),
24724    );
24725
24726    let mut actual_guides = cx.update_editor(|editor, window, cx| {
24727        editor
24728            .snapshot(window, cx)
24729            .buffer_snapshot()
24730            .indent_guides_in_range(Anchor::Min..Anchor::Max, false, cx)
24731            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
24732            .collect::<Vec<_>>()
24733    });
24734    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
24735    assert_eq!(
24736        actual_guides,
24737        vec![
24738            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
24739            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
24740            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
24741        ]
24742    );
24743}
24744
24745#[gpui::test]
24746async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
24747    init_test(cx, |_| {});
24748    let mut cx = EditorTestContext::new(cx).await;
24749
24750    let diff_base = r#"
24751        a
24752        b
24753        c
24754        "#
24755    .unindent();
24756
24757    cx.set_state(
24758        &r#"
24759        ˇA
24760        b
24761        C
24762        "#
24763        .unindent(),
24764    );
24765    cx.set_head_text(&diff_base);
24766    cx.update_editor(|editor, window, cx| {
24767        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24768    });
24769    executor.run_until_parked();
24770
24771    let both_hunks_expanded = r#"
24772        - a
24773        + ˇA
24774          b
24775        - c
24776        + C
24777        "#
24778    .unindent();
24779
24780    cx.assert_state_with_diff(both_hunks_expanded.clone());
24781
24782    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24783        let snapshot = editor.snapshot(window, cx);
24784        let hunks = editor
24785            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24786            .collect::<Vec<_>>();
24787        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
24788        hunks
24789            .into_iter()
24790            .map(|hunk| {
24791                multibuffer_snapshot
24792                    .anchor_in_excerpt(hunk.buffer_range.start)
24793                    .unwrap()
24794                    ..multibuffer_snapshot
24795                        .anchor_in_excerpt(hunk.buffer_range.end)
24796                        .unwrap()
24797            })
24798            .collect::<Vec<_>>()
24799    });
24800    assert_eq!(hunk_ranges.len(), 2);
24801
24802    cx.update_editor(|editor, _, cx| {
24803        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24804    });
24805    executor.run_until_parked();
24806
24807    let second_hunk_expanded = r#"
24808          ˇA
24809          b
24810        - c
24811        + C
24812        "#
24813    .unindent();
24814
24815    cx.assert_state_with_diff(second_hunk_expanded);
24816
24817    cx.update_editor(|editor, _, cx| {
24818        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24819    });
24820    executor.run_until_parked();
24821
24822    cx.assert_state_with_diff(both_hunks_expanded.clone());
24823
24824    cx.update_editor(|editor, _, cx| {
24825        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24826    });
24827    executor.run_until_parked();
24828
24829    let first_hunk_expanded = r#"
24830        - a
24831        + ˇA
24832          b
24833          C
24834        "#
24835    .unindent();
24836
24837    cx.assert_state_with_diff(first_hunk_expanded);
24838
24839    cx.update_editor(|editor, _, cx| {
24840        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24841    });
24842    executor.run_until_parked();
24843
24844    cx.assert_state_with_diff(both_hunks_expanded);
24845
24846    cx.set_state(
24847        &r#"
24848        ˇA
24849        b
24850        "#
24851        .unindent(),
24852    );
24853    cx.run_until_parked();
24854
24855    // TODO this cursor position seems bad
24856    cx.assert_state_with_diff(
24857        r#"
24858        - ˇa
24859        + A
24860          b
24861        "#
24862        .unindent(),
24863    );
24864
24865    cx.update_editor(|editor, window, cx| {
24866        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24867    });
24868
24869    cx.assert_state_with_diff(
24870        r#"
24871            - ˇa
24872            + A
24873              b
24874            - c
24875            "#
24876        .unindent(),
24877    );
24878
24879    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24880        let snapshot = editor.snapshot(window, cx);
24881        let hunks = editor
24882            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24883            .collect::<Vec<_>>();
24884        let multibuffer_snapshot = snapshot.buffer_snapshot();
24885        hunks
24886            .into_iter()
24887            .map(|hunk| {
24888                multibuffer_snapshot
24889                    .anchor_in_excerpt(hunk.buffer_range.start)
24890                    .unwrap()
24891                    ..multibuffer_snapshot
24892                        .anchor_in_excerpt(hunk.buffer_range.end)
24893                        .unwrap()
24894            })
24895            .collect::<Vec<_>>()
24896    });
24897    assert_eq!(hunk_ranges.len(), 2);
24898
24899    cx.update_editor(|editor, _, cx| {
24900        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
24901    });
24902    executor.run_until_parked();
24903
24904    cx.assert_state_with_diff(
24905        r#"
24906        - ˇa
24907        + A
24908          b
24909        "#
24910        .unindent(),
24911    );
24912}
24913
24914#[gpui::test]
24915async fn test_toggle_deletion_hunk_at_start_of_file(
24916    executor: BackgroundExecutor,
24917    cx: &mut TestAppContext,
24918) {
24919    init_test(cx, |_| {});
24920    let mut cx = EditorTestContext::new(cx).await;
24921
24922    let diff_base = r#"
24923        a
24924        b
24925        c
24926        "#
24927    .unindent();
24928
24929    cx.set_state(
24930        &r#"
24931        ˇb
24932        c
24933        "#
24934        .unindent(),
24935    );
24936    cx.set_head_text(&diff_base);
24937    cx.update_editor(|editor, window, cx| {
24938        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24939    });
24940    executor.run_until_parked();
24941
24942    let hunk_expanded = r#"
24943        - a
24944          ˇb
24945          c
24946        "#
24947    .unindent();
24948
24949    cx.assert_state_with_diff(hunk_expanded.clone());
24950
24951    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24952        let snapshot = editor.snapshot(window, cx);
24953        let hunks = editor
24954            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
24955            .collect::<Vec<_>>();
24956        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
24957        hunks
24958            .into_iter()
24959            .map(|hunk| {
24960                multibuffer_snapshot
24961                    .anchor_in_excerpt(hunk.buffer_range.start)
24962                    .unwrap()
24963                    ..multibuffer_snapshot
24964                        .anchor_in_excerpt(hunk.buffer_range.end)
24965                        .unwrap()
24966            })
24967            .collect::<Vec<_>>()
24968    });
24969    assert_eq!(hunk_ranges.len(), 1);
24970
24971    cx.update_editor(|editor, _, cx| {
24972        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24973    });
24974    executor.run_until_parked();
24975
24976    let hunk_collapsed = r#"
24977          ˇb
24978          c
24979        "#
24980    .unindent();
24981
24982    cx.assert_state_with_diff(hunk_collapsed);
24983
24984    cx.update_editor(|editor, _, cx| {
24985        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24986    });
24987    executor.run_until_parked();
24988
24989    cx.assert_state_with_diff(hunk_expanded);
24990}
24991
24992#[gpui::test]
24993async fn test_select_smaller_syntax_node_after_diff_hunk_collapse(
24994    executor: BackgroundExecutor,
24995    cx: &mut TestAppContext,
24996) {
24997    init_test(cx, |_| {});
24998
24999    let mut cx = EditorTestContext::new(cx).await;
25000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
25001
25002    cx.set_state(
25003        &r#"
25004        fn main() {
25005            let x = ˇ1;
25006        }
25007        "#
25008        .unindent(),
25009    );
25010
25011    let diff_base = r#"
25012        fn removed_one() {
25013            println!("this function was deleted");
25014        }
25015
25016        fn removed_two() {
25017            println!("this function was also deleted");
25018        }
25019
25020        fn main() {
25021            let x = 1;
25022        }
25023        "#
25024    .unindent();
25025    cx.set_head_text(&diff_base);
25026    executor.run_until_parked();
25027
25028    cx.update_editor(|editor, window, cx| {
25029        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
25030    });
25031    executor.run_until_parked();
25032
25033    cx.update_editor(|editor, window, cx| {
25034        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
25035    });
25036
25037    cx.update_editor(|editor, window, cx| {
25038        editor.collapse_all_diff_hunks(&CollapseAllDiffHunks, window, cx);
25039    });
25040    executor.run_until_parked();
25041
25042    cx.update_editor(|editor, window, cx| {
25043        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
25044    });
25045}
25046
25047#[gpui::test]
25048async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
25049    executor: BackgroundExecutor,
25050    cx: &mut TestAppContext,
25051) {
25052    init_test(cx, |_| {});
25053    let mut cx = EditorTestContext::new(cx).await;
25054
25055    cx.set_state("ˇnew\nsecond\nthird\n");
25056    cx.set_head_text("old\nsecond\nthird\n");
25057    cx.update_editor(|editor, window, cx| {
25058        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
25059    });
25060    executor.run_until_parked();
25061    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
25062
25063    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
25064    cx.update_editor(|editor, window, cx| {
25065        let snapshot = editor.snapshot(window, cx);
25066        let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
25067        let hunks = editor
25068            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
25069            .collect::<Vec<_>>();
25070        assert_eq!(hunks.len(), 1);
25071        let hunk_range = multibuffer_snapshot
25072            .anchor_in_excerpt(hunks[0].buffer_range.start)
25073            .unwrap()
25074            ..multibuffer_snapshot
25075                .anchor_in_excerpt(hunks[0].buffer_range.end)
25076                .unwrap();
25077        editor.toggle_single_diff_hunk(hunk_range, cx)
25078    });
25079    executor.run_until_parked();
25080    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
25081
25082    // Keep the editor scrolled to the top so the full hunk remains visible.
25083    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
25084}
25085
25086#[gpui::test]
25087async fn test_display_diff_hunks(cx: &mut TestAppContext) {
25088    init_test(cx, |_| {});
25089
25090    let fs = FakeFs::new(cx.executor());
25091    fs.insert_tree(
25092        path!("/test"),
25093        json!({
25094            ".git": {},
25095            "file-1": "ONE\n",
25096            "file-2": "TWO\n",
25097            "file-3": "THREE\n",
25098        }),
25099    )
25100    .await;
25101
25102    fs.set_head_for_repo(
25103        path!("/test/.git").as_ref(),
25104        &[
25105            ("file-1", "one\n".into()),
25106            ("file-2", "two\n".into()),
25107            ("file-3", "three\n".into()),
25108        ],
25109        "deadbeef",
25110    );
25111
25112    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
25113    let mut buffers = vec![];
25114    for i in 1..=3 {
25115        let buffer = project
25116            .update(cx, |project, cx| {
25117                let path = format!(path!("/test/file-{}"), i);
25118                project.open_local_buffer(path, cx)
25119            })
25120            .await
25121            .unwrap();
25122        buffers.push(buffer);
25123    }
25124
25125    let multibuffer = cx.new(|cx| {
25126        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
25127        multibuffer.set_all_diff_hunks_expanded(cx);
25128        for buffer in &buffers {
25129            let snapshot = buffer.read(cx).snapshot();
25130            multibuffer.set_excerpts_for_path(
25131                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
25132                buffer.clone(),
25133                vec![Point::zero()..snapshot.max_point()],
25134                2,
25135                cx,
25136            );
25137        }
25138        multibuffer
25139    });
25140
25141    let editor = cx.add_window(|window, cx| {
25142        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
25143    });
25144    cx.run_until_parked();
25145
25146    let snapshot = editor
25147        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25148        .unwrap();
25149    let hunks = snapshot
25150        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
25151        .map(|hunk| match hunk {
25152            DisplayDiffHunk::Unfolded {
25153                display_row_range, ..
25154            } => display_row_range,
25155            DisplayDiffHunk::Folded { .. } => unreachable!(),
25156        })
25157        .collect::<Vec<_>>();
25158    assert_eq!(
25159        hunks,
25160        [
25161            DisplayRow(2)..DisplayRow(4),
25162            DisplayRow(7)..DisplayRow(9),
25163            DisplayRow(12)..DisplayRow(14),
25164        ]
25165    );
25166}
25167
25168#[gpui::test]
25169async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
25170    init_test(cx, |_| {});
25171
25172    let mut cx = EditorTestContext::new(cx).await;
25173    cx.set_head_text(indoc! { "
25174        one
25175        two
25176        three
25177        four
25178        five
25179        "
25180    });
25181    cx.set_index_text(indoc! { "
25182        one
25183        two
25184        three
25185        four
25186        five
25187        "
25188    });
25189    cx.set_state(indoc! {"
25190        one
25191        TWO
25192        ˇTHREE
25193        FOUR
25194        five
25195    "});
25196    cx.run_until_parked();
25197    cx.update_editor(|editor, window, cx| {
25198        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
25199    });
25200    cx.run_until_parked();
25201    cx.assert_index_text(Some(indoc! {"
25202        one
25203        TWO
25204        THREE
25205        FOUR
25206        five
25207    "}));
25208    cx.set_state(indoc! { "
25209        one
25210        TWO
25211        ˇTHREE-HUNDRED
25212        FOUR
25213        five
25214    "});
25215    cx.run_until_parked();
25216    cx.update_editor(|editor, window, cx| {
25217        let snapshot = editor.snapshot(window, cx);
25218        let hunks = editor
25219            .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
25220            .collect::<Vec<_>>();
25221        assert_eq!(hunks.len(), 1);
25222        assert_eq!(
25223            hunks[0].status(),
25224            DiffHunkStatus {
25225                kind: DiffHunkStatusKind::Modified,
25226                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
25227            }
25228        );
25229
25230        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
25231    });
25232    cx.run_until_parked();
25233    cx.assert_index_text(Some(indoc! {"
25234        one
25235        TWO
25236        THREE-HUNDRED
25237        FOUR
25238        five
25239    "}));
25240}
25241
25242#[gpui::test]
25243fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
25244    init_test(cx, |_| {});
25245
25246    let editor = cx.add_window(|window, cx| {
25247        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
25248        build_editor(buffer, window, cx)
25249    });
25250
25251    let render_args = Arc::new(Mutex::new(None));
25252    let snapshot = editor
25253        .update(cx, |editor, window, cx| {
25254            let snapshot = editor.buffer().read(cx).snapshot(cx);
25255            let range =
25256                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
25257
25258            struct RenderArgs {
25259                row: MultiBufferRow,
25260                folded: bool,
25261                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
25262            }
25263
25264            let crease = Crease::inline(
25265                range,
25266                FoldPlaceholder::test(),
25267                {
25268                    let toggle_callback = render_args.clone();
25269                    move |row, folded, callback, _window, _cx| {
25270                        *toggle_callback.lock() = Some(RenderArgs {
25271                            row,
25272                            folded,
25273                            callback,
25274                        });
25275                        div()
25276                    }
25277                },
25278                |_row, _folded, _window, _cx| div(),
25279            );
25280
25281            editor.insert_creases(Some(crease), cx);
25282            let snapshot = editor.snapshot(window, cx);
25283            let _div =
25284                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
25285            snapshot
25286        })
25287        .unwrap();
25288
25289    let render_args = render_args.lock().take().unwrap();
25290    assert_eq!(render_args.row, MultiBufferRow(1));
25291    assert!(!render_args.folded);
25292    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
25293
25294    cx.update_window(*editor, |_, window, cx| {
25295        (render_args.callback)(true, window, cx)
25296    })
25297    .unwrap();
25298    let snapshot = editor
25299        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25300        .unwrap();
25301    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
25302
25303    cx.update_window(*editor, |_, window, cx| {
25304        (render_args.callback)(false, window, cx)
25305    })
25306    .unwrap();
25307    let snapshot = editor
25308        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
25309        .unwrap();
25310    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
25311}
25312
25313#[gpui::test]
25314async fn test_input_text(cx: &mut TestAppContext) {
25315    init_test(cx, |_| {});
25316    let mut cx = EditorTestContext::new(cx).await;
25317
25318    cx.set_state(
25319        &r#"ˇone
25320        two
25321
25322        three
25323        fourˇ
25324        five
25325
25326        siˇx"#
25327            .unindent(),
25328    );
25329
25330    cx.dispatch_action(HandleInput(String::new()));
25331    cx.assert_editor_state(
25332        &r#"ˇone
25333        two
25334
25335        three
25336        fourˇ
25337        five
25338
25339        siˇx"#
25340            .unindent(),
25341    );
25342
25343    cx.dispatch_action(HandleInput("AAAA".to_string()));
25344    cx.assert_editor_state(
25345        &r#"AAAAˇone
25346        two
25347
25348        three
25349        fourAAAAˇ
25350        five
25351
25352        siAAAAˇx"#
25353            .unindent(),
25354    );
25355}
25356
25357#[gpui::test]
25358async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
25359    init_test(cx, |_| {});
25360
25361    let mut cx = EditorTestContext::new(cx).await;
25362    cx.set_state(
25363        r#"let foo = 1;
25364let foo = 2;
25365let foo = 3;
25366let fooˇ = 4;
25367let foo = 5;
25368let foo = 6;
25369let foo = 7;
25370let foo = 8;
25371let foo = 9;
25372let foo = 10;
25373let foo = 11;
25374let foo = 12;
25375let foo = 13;
25376let foo = 14;
25377let foo = 15;"#,
25378    );
25379
25380    cx.update_editor(|e, window, cx| {
25381        assert_eq!(
25382            e.next_scroll_position,
25383            NextScrollCursorCenterTopBottom::Center,
25384            "Default next scroll direction is center",
25385        );
25386
25387        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25388        assert_eq!(
25389            e.next_scroll_position,
25390            NextScrollCursorCenterTopBottom::Top,
25391            "After center, next scroll direction should be top",
25392        );
25393
25394        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25395        assert_eq!(
25396            e.next_scroll_position,
25397            NextScrollCursorCenterTopBottom::Bottom,
25398            "After top, next scroll direction should be bottom",
25399        );
25400
25401        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25402        assert_eq!(
25403            e.next_scroll_position,
25404            NextScrollCursorCenterTopBottom::Center,
25405            "After bottom, scrolling should start over",
25406        );
25407
25408        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
25409        assert_eq!(
25410            e.next_scroll_position,
25411            NextScrollCursorCenterTopBottom::Top,
25412            "Scrolling continues if retriggered fast enough"
25413        );
25414    });
25415
25416    cx.executor()
25417        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
25418    cx.executor().run_until_parked();
25419    cx.update_editor(|e, _, _| {
25420        assert_eq!(
25421            e.next_scroll_position,
25422            NextScrollCursorCenterTopBottom::Center,
25423            "If scrolling is not triggered fast enough, it should reset"
25424        );
25425    });
25426}
25427
25428#[gpui::test]
25429async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
25430    init_test(cx, |_| {});
25431    let mut cx = EditorLspTestContext::new_rust(
25432        lsp::ServerCapabilities {
25433            definition_provider: Some(lsp::OneOf::Left(true)),
25434            references_provider: Some(lsp::OneOf::Left(true)),
25435            ..lsp::ServerCapabilities::default()
25436        },
25437        cx,
25438    )
25439    .await;
25440
25441    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
25442        let go_to_definition = cx
25443            .lsp
25444            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
25445                move |params, _| async move {
25446                    if empty_go_to_definition {
25447                        Ok(None)
25448                    } else {
25449                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
25450                            uri: params.text_document_position_params.text_document.uri,
25451                            range: lsp::Range::new(
25452                                lsp::Position::new(4, 3),
25453                                lsp::Position::new(4, 6),
25454                            ),
25455                        })))
25456                    }
25457                },
25458            );
25459        let references = cx
25460            .lsp
25461            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25462                Ok(Some(vec![lsp::Location {
25463                    uri: params.text_document_position.text_document.uri,
25464                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
25465                }]))
25466            });
25467        (go_to_definition, references)
25468    };
25469
25470    cx.set_state(
25471        &r#"fn one() {
25472            let mut a = ˇtwo();
25473        }
25474
25475        fn two() {}"#
25476            .unindent(),
25477    );
25478    set_up_lsp_handlers(false, &mut cx);
25479    let navigated = cx
25480        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25481        .await
25482        .expect("Failed to navigate to definition");
25483    assert_eq!(
25484        navigated,
25485        Navigated::Yes,
25486        "Should have navigated to definition from the GetDefinition response"
25487    );
25488    cx.assert_editor_state(
25489        &r#"fn one() {
25490            let mut a = two();
25491        }
25492
25493        fn «twoˇ»() {}"#
25494            .unindent(),
25495    );
25496
25497    let editors = cx.update_workspace(|workspace, _, cx| {
25498        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25499    });
25500    cx.update_editor(|_, _, test_editor_cx| {
25501        assert_eq!(
25502            editors.len(),
25503            1,
25504            "Initially, only one, test, editor should be open in the workspace"
25505        );
25506        assert_eq!(
25507            test_editor_cx.entity(),
25508            editors.last().expect("Asserted len is 1").clone()
25509        );
25510    });
25511
25512    set_up_lsp_handlers(true, &mut cx);
25513    let navigated = cx
25514        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25515        .await
25516        .expect("Failed to navigate to lookup references");
25517    assert_eq!(
25518        navigated,
25519        Navigated::Yes,
25520        "Should have navigated to references as a fallback after empty GoToDefinition response"
25521    );
25522    // We should not change the selections in the existing file,
25523    // if opening another milti buffer with the references
25524    cx.assert_editor_state(
25525        &r#"fn one() {
25526            let mut a = two();
25527        }
25528
25529        fn «twoˇ»() {}"#
25530            .unindent(),
25531    );
25532    let editors = cx.update_workspace(|workspace, _, cx| {
25533        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25534    });
25535    cx.update_editor(|_, _, test_editor_cx| {
25536        assert_eq!(
25537            editors.len(),
25538            2,
25539            "After falling back to references search, we open a new editor with the results"
25540        );
25541        let references_fallback_text = editors
25542            .into_iter()
25543            .find(|new_editor| *new_editor != test_editor_cx.entity())
25544            .expect("Should have one non-test editor now")
25545            .read(test_editor_cx)
25546            .text(test_editor_cx);
25547        assert_eq!(
25548            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
25549            "Should use the range from the references response and not the GoToDefinition one"
25550        );
25551    });
25552}
25553
25554#[gpui::test]
25555async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
25556    init_test(cx, |_| {});
25557    cx.update(|cx| {
25558        let mut editor_settings = EditorSettings::get_global(cx).clone();
25559        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
25560        EditorSettings::override_global(editor_settings, cx);
25561    });
25562    let mut cx = EditorLspTestContext::new_rust(
25563        lsp::ServerCapabilities {
25564            definition_provider: Some(lsp::OneOf::Left(true)),
25565            references_provider: Some(lsp::OneOf::Left(true)),
25566            ..lsp::ServerCapabilities::default()
25567        },
25568        cx,
25569    )
25570    .await;
25571    let original_state = r#"fn one() {
25572        let mut a = ˇtwo();
25573    }
25574
25575    fn two() {}"#
25576        .unindent();
25577    cx.set_state(&original_state);
25578
25579    let mut go_to_definition = cx
25580        .lsp
25581        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
25582            move |_, _| async move { Ok(None) },
25583        );
25584    let _references = cx
25585        .lsp
25586        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
25587            panic!("Should not call for references with no go to definition fallback")
25588        });
25589
25590    let navigated = cx
25591        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25592        .await
25593        .expect("Failed to navigate to lookup references");
25594    go_to_definition
25595        .next()
25596        .await
25597        .expect("Should have called the go_to_definition handler");
25598
25599    assert_eq!(
25600        navigated,
25601        Navigated::No,
25602        "Should have navigated to references as a fallback after empty GoToDefinition response"
25603    );
25604    cx.assert_editor_state(&original_state);
25605    let editors = cx.update_workspace(|workspace, _, cx| {
25606        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25607    });
25608    cx.update_editor(|_, _, _| {
25609        assert_eq!(
25610            editors.len(),
25611            1,
25612            "After unsuccessful fallback, no other editor should have been opened"
25613        );
25614    });
25615}
25616
25617#[gpui::test]
25618async fn test_goto_definition_close_ranges_open_singleton(cx: &mut TestAppContext) {
25619    init_test(cx, |_| {});
25620    let mut cx = EditorLspTestContext::new_rust(
25621        lsp::ServerCapabilities {
25622            definition_provider: Some(lsp::OneOf::Left(true)),
25623            ..lsp::ServerCapabilities::default()
25624        },
25625        cx,
25626    )
25627    .await;
25628
25629    // File content: 10 lines with functions defined on lines 3, 5, and 7 (0-indexed).
25630    // With the default excerpt_context_lines of 2, ranges that are within
25631    // 2 * 2 = 4 rows of each other should be grouped into one excerpt.
25632    cx.set_state(
25633        &r#"fn caller() {
25634            let _ = ˇtarget();
25635        }
25636        fn target_a() {}
25637
25638        fn target_b() {}
25639
25640        fn target_c() {}
25641        "#
25642        .unindent(),
25643    );
25644
25645    // Return two definitions that are close together (lines 3 and 5, gap of 2 rows)
25646    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25647        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25648            lsp::Location {
25649                uri: url.clone(),
25650                range: lsp::Range::new(lsp::Position::new(3, 3), lsp::Position::new(3, 11)),
25651            },
25652            lsp::Location {
25653                uri: url,
25654                range: lsp::Range::new(lsp::Position::new(5, 3), lsp::Position::new(5, 11)),
25655            },
25656        ])))
25657    });
25658
25659    let navigated = cx
25660        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25661        .await
25662        .expect("Failed to navigate to definitions");
25663    assert_eq!(navigated, Navigated::Yes);
25664
25665    let editors = cx.update_workspace(|workspace, _, cx| {
25666        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25667    });
25668    cx.update_editor(|_, _, _| {
25669        assert_eq!(
25670            editors.len(),
25671            1,
25672            "Close ranges should navigate in-place without opening a new editor"
25673        );
25674    });
25675
25676    // Both target ranges should be selected
25677    cx.assert_editor_state(
25678        &r#"fn caller() {
25679            let _ = target();
25680        }
25681        fn «target_aˇ»() {}
25682
25683        fn «target_bˇ»() {}
25684
25685        fn target_c() {}
25686        "#
25687        .unindent(),
25688    );
25689}
25690
25691#[gpui::test]
25692async fn test_goto_definition_far_ranges_open_multibuffer(cx: &mut TestAppContext) {
25693    init_test(cx, |_| {});
25694    let mut cx = EditorLspTestContext::new_rust(
25695        lsp::ServerCapabilities {
25696            definition_provider: Some(lsp::OneOf::Left(true)),
25697            ..lsp::ServerCapabilities::default()
25698        },
25699        cx,
25700    )
25701    .await;
25702
25703    // Create a file with definitions far apart (more than 2 * excerpt_context_lines rows).
25704    cx.set_state(
25705        &r#"fn caller() {
25706            let _ = ˇtarget();
25707        }
25708        fn target_a() {}
25709
25710
25711
25712
25713
25714
25715
25716
25717
25718
25719
25720
25721
25722
25723
25724        fn target_b() {}
25725        "#
25726        .unindent(),
25727    );
25728
25729    // Return two definitions that are far apart (lines 3 and 19, gap of 16 rows)
25730    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25731        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25732            lsp::Location {
25733                uri: url.clone(),
25734                range: lsp::Range::new(lsp::Position::new(3, 3), lsp::Position::new(3, 11)),
25735            },
25736            lsp::Location {
25737                uri: url,
25738                range: lsp::Range::new(lsp::Position::new(19, 3), lsp::Position::new(19, 11)),
25739            },
25740        ])))
25741    });
25742
25743    let navigated = cx
25744        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25745        .await
25746        .expect("Failed to navigate to definitions");
25747    assert_eq!(navigated, Navigated::Yes);
25748
25749    let editors = cx.update_workspace(|workspace, _, cx| {
25750        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25751    });
25752    cx.update_editor(|_, _, test_editor_cx| {
25753        assert_eq!(
25754            editors.len(),
25755            2,
25756            "Far apart ranges should open a new multibuffer editor"
25757        );
25758        let multibuffer_editor = editors
25759            .into_iter()
25760            .find(|editor| *editor != test_editor_cx.entity())
25761            .expect("Should have a multibuffer editor");
25762        let multibuffer_text = multibuffer_editor.read(test_editor_cx).text(test_editor_cx);
25763        assert!(
25764            multibuffer_text.contains("target_a"),
25765            "Multibuffer should contain the first definition"
25766        );
25767        assert!(
25768            multibuffer_text.contains("target_b"),
25769            "Multibuffer should contain the second definition"
25770        );
25771    });
25772}
25773
25774#[gpui::test]
25775async fn test_goto_definition_contained_ranges(cx: &mut TestAppContext) {
25776    init_test(cx, |_| {});
25777    let mut cx = EditorLspTestContext::new_rust(
25778        lsp::ServerCapabilities {
25779            definition_provider: Some(lsp::OneOf::Left(true)),
25780            ..lsp::ServerCapabilities::default()
25781        },
25782        cx,
25783    )
25784    .await;
25785
25786    // The LSP returns two single-line definitions on the same row where one
25787    // range contains the other. Both are on the same line so the
25788    // `fits_in_one_excerpt` check won't underflow, and the code reaches
25789    // `change_selections`.
25790    cx.set_state(
25791        &r#"fn caller() {
25792            let _ = ˇtarget();
25793        }
25794        fn target_outer() { fn target_inner() {} }
25795        "#
25796        .unindent(),
25797    );
25798
25799    // Return two definitions on the same line: an outer range covering the
25800    // whole line and an inner range for just the inner function name.
25801    cx.set_request_handler::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
25802        Ok(Some(lsp::GotoDefinitionResponse::Array(vec![
25803            // Inner range: just "target_inner" (cols 23..35)
25804            lsp::Location {
25805                uri: url.clone(),
25806                range: lsp::Range::new(lsp::Position::new(3, 23), lsp::Position::new(3, 35)),
25807            },
25808            // Outer range: the whole line (cols 0..48)
25809            lsp::Location {
25810                uri: url,
25811                range: lsp::Range::new(lsp::Position::new(3, 0), lsp::Position::new(3, 48)),
25812            },
25813        ])))
25814    });
25815
25816    let navigated = cx
25817        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
25818        .await
25819        .expect("Failed to navigate to definitions");
25820    assert_eq!(navigated, Navigated::Yes);
25821}
25822
25823#[gpui::test]
25824async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
25825    init_test(cx, |_| {});
25826    let mut cx = EditorLspTestContext::new_rust(
25827        lsp::ServerCapabilities {
25828            references_provider: Some(lsp::OneOf::Left(true)),
25829            ..lsp::ServerCapabilities::default()
25830        },
25831        cx,
25832    )
25833    .await;
25834
25835    cx.set_state(
25836        &r#"
25837        fn one() {
25838            let mut a = two();
25839        }
25840
25841        fn ˇtwo() {}"#
25842            .unindent(),
25843    );
25844    cx.lsp
25845        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25846            Ok(Some(vec![
25847                lsp::Location {
25848                    uri: params.text_document_position.text_document.uri.clone(),
25849                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
25850                },
25851                lsp::Location {
25852                    uri: params.text_document_position.text_document.uri,
25853                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
25854                },
25855            ]))
25856        });
25857    let navigated = cx
25858        .update_editor(|editor, window, cx| {
25859            editor.find_all_references(&FindAllReferences::default(), window, cx)
25860        })
25861        .unwrap()
25862        .await
25863        .expect("Failed to navigate to references");
25864    assert_eq!(
25865        navigated,
25866        Navigated::Yes,
25867        "Should have navigated to references from the FindAllReferences response"
25868    );
25869    cx.assert_editor_state(
25870        &r#"fn one() {
25871            let mut a = two();
25872        }
25873
25874        fn ˇtwo() {}"#
25875            .unindent(),
25876    );
25877
25878    let editors = cx.update_workspace(|workspace, _, cx| {
25879        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25880    });
25881    cx.update_editor(|_, _, _| {
25882        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
25883    });
25884
25885    cx.set_state(
25886        &r#"fn one() {
25887            let mut a = ˇtwo();
25888        }
25889
25890        fn two() {}"#
25891            .unindent(),
25892    );
25893    let navigated = cx
25894        .update_editor(|editor, window, cx| {
25895            editor.find_all_references(&FindAllReferences::default(), window, cx)
25896        })
25897        .unwrap()
25898        .await
25899        .expect("Failed to navigate to references");
25900    assert_eq!(
25901        navigated,
25902        Navigated::Yes,
25903        "Should have navigated to references from the FindAllReferences response"
25904    );
25905    cx.assert_editor_state(
25906        &r#"fn one() {
25907            let mut a = ˇtwo();
25908        }
25909
25910        fn two() {}"#
25911            .unindent(),
25912    );
25913    let editors = cx.update_workspace(|workspace, _, cx| {
25914        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25915    });
25916    cx.update_editor(|_, _, _| {
25917        assert_eq!(
25918            editors.len(),
25919            2,
25920            "should have re-used the previous multibuffer"
25921        );
25922    });
25923
25924    cx.set_state(
25925        &r#"fn one() {
25926            let mut a = ˇtwo();
25927        }
25928        fn three() {}
25929        fn two() {}"#
25930            .unindent(),
25931    );
25932    cx.lsp
25933        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
25934            Ok(Some(vec![
25935                lsp::Location {
25936                    uri: params.text_document_position.text_document.uri.clone(),
25937                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
25938                },
25939                lsp::Location {
25940                    uri: params.text_document_position.text_document.uri,
25941                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
25942                },
25943            ]))
25944        });
25945    let navigated = cx
25946        .update_editor(|editor, window, cx| {
25947            editor.find_all_references(&FindAllReferences::default(), window, cx)
25948        })
25949        .unwrap()
25950        .await
25951        .expect("Failed to navigate to references");
25952    assert_eq!(
25953        navigated,
25954        Navigated::Yes,
25955        "Should have navigated to references from the FindAllReferences response"
25956    );
25957    cx.assert_editor_state(
25958        &r#"fn one() {
25959                let mut a = ˇtwo();
25960            }
25961            fn three() {}
25962            fn two() {}"#
25963            .unindent(),
25964    );
25965    let editors = cx.update_workspace(|workspace, _, cx| {
25966        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
25967    });
25968    cx.update_editor(|_, _, _| {
25969        assert_eq!(
25970            editors.len(),
25971            3,
25972            "should have used a new multibuffer as offsets changed"
25973        );
25974    });
25975}
25976#[gpui::test]
25977async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
25978    init_test(cx, |_| {});
25979
25980    let language = Arc::new(Language::new(
25981        LanguageConfig::default(),
25982        Some(tree_sitter_rust::LANGUAGE.into()),
25983    ));
25984
25985    let text = r#"
25986        #[cfg(test)]
25987        mod tests() {
25988            #[test]
25989            fn runnable_1() {
25990                let a = 1;
25991            }
25992
25993            #[test]
25994            fn runnable_2() {
25995                let a = 1;
25996                let b = 2;
25997            }
25998        }
25999    "#
26000    .unindent();
26001
26002    let fs = FakeFs::new(cx.executor());
26003    fs.insert_file("/file.rs", Default::default()).await;
26004
26005    let project = Project::test(fs, ["/a".as_ref()], cx).await;
26006    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26007    let cx = &mut VisualTestContext::from_window(*window, cx);
26008    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
26009    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
26010
26011    let editor = cx.new_window_entity(|window, cx| {
26012        Editor::new(
26013            EditorMode::full(),
26014            multi_buffer,
26015            Some(project.clone()),
26016            window,
26017            cx,
26018        )
26019    });
26020
26021    editor.update_in(cx, |editor, window, cx| {
26022        let snapshot = editor.buffer().read(cx).snapshot(cx);
26023        editor.runnables.insert(
26024            buffer.read(cx).remote_id(),
26025            3,
26026            buffer.read(cx).version(),
26027            RunnableTasks {
26028                templates: Vec::new(),
26029                offset: snapshot.anchor_before(MultiBufferOffset(43)),
26030                column: 0,
26031                extra_variables: HashMap::default(),
26032                context_range: BufferOffset(43)..BufferOffset(85),
26033            },
26034        );
26035        editor.runnables.insert(
26036            buffer.read(cx).remote_id(),
26037            8,
26038            buffer.read(cx).version(),
26039            RunnableTasks {
26040                templates: Vec::new(),
26041                offset: snapshot.anchor_before(MultiBufferOffset(86)),
26042                column: 0,
26043                extra_variables: HashMap::default(),
26044                context_range: BufferOffset(86)..BufferOffset(191),
26045            },
26046        );
26047
26048        // Test finding task when cursor is inside function body
26049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26050            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
26051        });
26052        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
26053        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
26054
26055        // Test finding task when cursor is on function name
26056        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26057            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
26058        });
26059        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
26060        assert_eq!(row, 8, "Should find task when cursor is on function name");
26061    });
26062}
26063
26064#[gpui::test]
26065async fn test_folding_buffers(cx: &mut TestAppContext) {
26066    init_test(cx, |_| {});
26067
26068    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
26069    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
26070    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
26071
26072    let fs = FakeFs::new(cx.executor());
26073    fs.insert_tree(
26074        path!("/a"),
26075        json!({
26076            "first.rs": sample_text_1,
26077            "second.rs": sample_text_2,
26078            "third.rs": sample_text_3,
26079        }),
26080    )
26081    .await;
26082    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26083    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26084    let cx = &mut VisualTestContext::from_window(*window, cx);
26085    let worktree = project.update(cx, |project, cx| {
26086        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26087        assert_eq!(worktrees.len(), 1);
26088        worktrees.pop().unwrap()
26089    });
26090    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26091
26092    let buffer_1 = project
26093        .update(cx, |project, cx| {
26094            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
26095        })
26096        .await
26097        .unwrap();
26098    let buffer_2 = project
26099        .update(cx, |project, cx| {
26100            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
26101        })
26102        .await
26103        .unwrap();
26104    let buffer_3 = project
26105        .update(cx, |project, cx| {
26106            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
26107        })
26108        .await
26109        .unwrap();
26110
26111    let multi_buffer = cx.new(|cx| {
26112        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26113        multi_buffer.set_excerpts_for_path(
26114            PathKey::sorted(0),
26115            buffer_1.clone(),
26116            [
26117                Point::new(0, 0)..Point::new(2, 0),
26118                Point::new(5, 0)..Point::new(6, 0),
26119                Point::new(9, 0)..Point::new(10, 4),
26120            ],
26121            0,
26122            cx,
26123        );
26124        multi_buffer.set_excerpts_for_path(
26125            PathKey::sorted(1),
26126            buffer_2.clone(),
26127            [
26128                Point::new(0, 0)..Point::new(2, 0),
26129                Point::new(5, 0)..Point::new(6, 0),
26130                Point::new(9, 0)..Point::new(10, 4),
26131            ],
26132            0,
26133            cx,
26134        );
26135        multi_buffer.set_excerpts_for_path(
26136            PathKey::sorted(2),
26137            buffer_3.clone(),
26138            [
26139                Point::new(0, 0)..Point::new(2, 0),
26140                Point::new(5, 0)..Point::new(6, 0),
26141                Point::new(9, 0)..Point::new(10, 4),
26142            ],
26143            0,
26144            cx,
26145        );
26146        multi_buffer
26147    });
26148    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26149        Editor::new(
26150            EditorMode::full(),
26151            multi_buffer.clone(),
26152            Some(project.clone()),
26153            window,
26154            cx,
26155        )
26156    });
26157
26158    assert_eq!(
26159        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26160        "\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",
26161    );
26162
26163    multi_buffer_editor.update(cx, |editor, cx| {
26164        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
26165    });
26166    assert_eq!(
26167        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26168        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26169        "After folding the first buffer, its text should not be displayed"
26170    );
26171
26172    multi_buffer_editor.update(cx, |editor, cx| {
26173        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
26174    });
26175    assert_eq!(
26176        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26177        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
26178        "After folding the second buffer, its text should not be displayed"
26179    );
26180
26181    multi_buffer_editor.update(cx, |editor, cx| {
26182        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
26183    });
26184    assert_eq!(
26185        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26186        "\n\n\n\n\n",
26187        "After folding the third buffer, its text should not be displayed"
26188    );
26189
26190    // Emulate selection inside the fold logic, that should work
26191    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26192        editor
26193            .snapshot(window, cx)
26194            .next_line_boundary(Point::new(0, 4));
26195    });
26196
26197    multi_buffer_editor.update(cx, |editor, cx| {
26198        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
26199    });
26200    assert_eq!(
26201        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26202        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
26203        "After unfolding the second buffer, its text should be displayed"
26204    );
26205
26206    // Typing inside of buffer 1 causes that buffer to be unfolded.
26207    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26208        assert_eq!(
26209            multi_buffer
26210                .read(cx)
26211                .snapshot(cx)
26212                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
26213                .collect::<String>(),
26214            "bbbb"
26215        );
26216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26217            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
26218        });
26219        editor.handle_input("B", window, cx);
26220    });
26221
26222    assert_eq!(
26223        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26224        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
26225        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
26226    );
26227
26228    multi_buffer_editor.update(cx, |editor, cx| {
26229        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
26230    });
26231    assert_eq!(
26232        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26233        "\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",
26234        "After unfolding the all buffers, all original text should be displayed"
26235    );
26236}
26237
26238#[gpui::test]
26239async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext) {
26240    init_test(cx, |_| {});
26241
26242    let fs = FakeFs::new(cx.executor());
26243    fs.insert_tree(
26244        path!("/root"),
26245        json!({
26246            "file_a.txt": "File A\nFile A\nFile A",
26247            "file_b.txt": "File B\nFile B\nFile B",
26248        }),
26249    )
26250    .await;
26251
26252    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
26253    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26254    let cx = &mut VisualTestContext::from_window(*window, cx);
26255    let worktree = project.update(cx, |project, cx| {
26256        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26257        assert_eq!(worktrees.len(), 1);
26258        worktrees.pop().unwrap()
26259    });
26260    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26261
26262    let buffer_a = project
26263        .update(cx, |project, cx| {
26264            project.open_buffer((worktree_id, rel_path("file_a.txt")), cx)
26265        })
26266        .await
26267        .unwrap();
26268    let buffer_b = project
26269        .update(cx, |project, cx| {
26270            project.open_buffer((worktree_id, rel_path("file_b.txt")), cx)
26271        })
26272        .await
26273        .unwrap();
26274
26275    let multi_buffer = cx.new(|cx| {
26276        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26277        let range_a = Point::new(0, 0)..Point::new(2, 4);
26278        let range_b = Point::new(0, 0)..Point::new(2, 4);
26279
26280        multi_buffer.set_excerpts_for_path(PathKey::sorted(0), buffer_a.clone(), [range_a], 0, cx);
26281        multi_buffer.set_excerpts_for_path(PathKey::sorted(1), buffer_b.clone(), [range_b], 0, cx);
26282        multi_buffer
26283    });
26284
26285    let editor = cx.new_window_entity(|window, cx| {
26286        Editor::new(
26287            EditorMode::full(),
26288            multi_buffer.clone(),
26289            Some(project.clone()),
26290            window,
26291            cx,
26292        )
26293    });
26294
26295    editor.update(cx, |editor, cx| {
26296        editor.fold_buffer(buffer_a.read(cx).remote_id(), cx);
26297    });
26298    assert!(editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
26299
26300    // When the excerpts for `buffer_a` are removed, a
26301    // `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
26302    // picked up by the editor and update the display map accordingly.
26303    multi_buffer.update(cx, |multi_buffer, cx| {
26304        multi_buffer.remove_excerpts(PathKey::sorted(0), cx)
26305    });
26306    assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
26307}
26308
26309#[gpui::test]
26310async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
26311    init_test(cx, |_| {});
26312
26313    let sample_text_1 = "1111\n2222\n3333".to_string();
26314    let sample_text_2 = "4444\n5555\n6666".to_string();
26315    let sample_text_3 = "7777\n8888\n9999".to_string();
26316
26317    let fs = FakeFs::new(cx.executor());
26318    fs.insert_tree(
26319        path!("/a"),
26320        json!({
26321            "first.rs": sample_text_1,
26322            "second.rs": sample_text_2,
26323            "third.rs": sample_text_3,
26324        }),
26325    )
26326    .await;
26327    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26328    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26329    let cx = &mut VisualTestContext::from_window(*window, cx);
26330    let worktree = project.update(cx, |project, cx| {
26331        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26332        assert_eq!(worktrees.len(), 1);
26333        worktrees.pop().unwrap()
26334    });
26335    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26336
26337    let buffer_1 = project
26338        .update(cx, |project, cx| {
26339            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
26340        })
26341        .await
26342        .unwrap();
26343    let buffer_2 = project
26344        .update(cx, |project, cx| {
26345            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
26346        })
26347        .await
26348        .unwrap();
26349    let buffer_3 = project
26350        .update(cx, |project, cx| {
26351            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
26352        })
26353        .await
26354        .unwrap();
26355
26356    let multi_buffer = cx.new(|cx| {
26357        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26358        multi_buffer.set_excerpts_for_path(
26359            PathKey::sorted(0),
26360            buffer_1.clone(),
26361            [Point::new(0, 0)..Point::new(3, 0)],
26362            0,
26363            cx,
26364        );
26365        multi_buffer.set_excerpts_for_path(
26366            PathKey::sorted(1),
26367            buffer_2.clone(),
26368            [Point::new(0, 0)..Point::new(3, 0)],
26369            0,
26370            cx,
26371        );
26372        multi_buffer.set_excerpts_for_path(
26373            PathKey::sorted(2),
26374            buffer_3.clone(),
26375            [Point::new(0, 0)..Point::new(3, 0)],
26376            0,
26377            cx,
26378        );
26379        multi_buffer
26380    });
26381
26382    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26383        Editor::new(
26384            EditorMode::full(),
26385            multi_buffer,
26386            Some(project.clone()),
26387            window,
26388            cx,
26389        )
26390    });
26391
26392    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
26393    assert_eq!(
26394        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26395        full_text,
26396    );
26397
26398    multi_buffer_editor.update(cx, |editor, cx| {
26399        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
26400    });
26401    assert_eq!(
26402        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26403        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
26404        "After folding the first buffer, its text should not be displayed"
26405    );
26406
26407    multi_buffer_editor.update(cx, |editor, cx| {
26408        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
26409    });
26410
26411    assert_eq!(
26412        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26413        "\n\n\n\n\n\n7777\n8888\n9999",
26414        "After folding the second buffer, its text should not be displayed"
26415    );
26416
26417    multi_buffer_editor.update(cx, |editor, cx| {
26418        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
26419    });
26420    assert_eq!(
26421        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26422        "\n\n\n\n\n",
26423        "After folding the third buffer, its text should not be displayed"
26424    );
26425
26426    multi_buffer_editor.update(cx, |editor, cx| {
26427        editor.unfold_buffer(buffer_2.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",
26432        "After unfolding the second buffer, its text should be displayed"
26433    );
26434
26435    multi_buffer_editor.update(cx, |editor, cx| {
26436        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
26437    });
26438    assert_eq!(
26439        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26440        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
26441        "After unfolding the first buffer, its text should be displayed"
26442    );
26443
26444    multi_buffer_editor.update(cx, |editor, cx| {
26445        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
26446    });
26447    assert_eq!(
26448        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26449        full_text,
26450        "After unfolding all buffers, all original text should be displayed"
26451    );
26452}
26453
26454#[gpui::test]
26455async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
26456    init_test(cx, |_| {});
26457
26458    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
26459
26460    let fs = FakeFs::new(cx.executor());
26461    fs.insert_tree(
26462        path!("/a"),
26463        json!({
26464            "main.rs": sample_text,
26465        }),
26466    )
26467    .await;
26468    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26469    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26470    let cx = &mut VisualTestContext::from_window(*window, cx);
26471    let worktree = project.update(cx, |project, cx| {
26472        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
26473        assert_eq!(worktrees.len(), 1);
26474        worktrees.pop().unwrap()
26475    });
26476    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
26477
26478    let buffer_1 = project
26479        .update(cx, |project, cx| {
26480            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
26481        })
26482        .await
26483        .unwrap();
26484
26485    let multi_buffer = cx.new(|cx| {
26486        let mut multi_buffer = MultiBuffer::new(ReadWrite);
26487        multi_buffer.set_excerpts_for_path(
26488            PathKey::sorted(0),
26489            buffer_1.clone(),
26490            [Point::new(0, 0)
26491                ..Point::new(
26492                    sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
26493                    0,
26494                )],
26495            0,
26496            cx,
26497        );
26498        multi_buffer
26499    });
26500    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
26501        Editor::new(
26502            EditorMode::full(),
26503            multi_buffer,
26504            Some(project.clone()),
26505            window,
26506            cx,
26507        )
26508    });
26509
26510    let selection_range = Point::new(1, 0)..Point::new(2, 0);
26511    multi_buffer_editor.update_in(cx, |editor, window, cx| {
26512        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
26513        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
26514        editor.highlight_text(
26515            HighlightKey::Editor,
26516            vec![highlight_range.clone()],
26517            HighlightStyle::color(Hsla::green()),
26518            cx,
26519        );
26520        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26521            s.select_ranges(Some(highlight_range))
26522        });
26523    });
26524
26525    let full_text = format!("\n\n{sample_text}");
26526    assert_eq!(
26527        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
26528        full_text,
26529    );
26530}
26531
26532#[gpui::test]
26533async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
26534    init_test(cx, |_| {});
26535    cx.update(|cx| {
26536        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
26537            "keymaps/default-linux.json",
26538            cx,
26539        )
26540        .unwrap();
26541        cx.bind_keys(default_key_bindings);
26542    });
26543
26544    let (editor, cx) = cx.add_window_view(|window, cx| {
26545        let multi_buffer = MultiBuffer::build_multi(
26546            [
26547                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
26548                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
26549                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
26550                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
26551            ],
26552            cx,
26553        );
26554        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
26555
26556        let buffer_ids = multi_buffer
26557            .read(cx)
26558            .snapshot(cx)
26559            .excerpts()
26560            .map(|excerpt| excerpt.context.start.buffer_id)
26561            .collect::<Vec<_>>();
26562        // fold all but the second buffer, so that we test navigating between two
26563        // adjacent folded buffers, as well as folded buffers at the start and
26564        // end the multibuffer
26565        editor.fold_buffer(buffer_ids[0], cx);
26566        editor.fold_buffer(buffer_ids[2], cx);
26567        editor.fold_buffer(buffer_ids[3], cx);
26568
26569        editor
26570    });
26571    cx.simulate_resize(size(px(1000.), px(1000.)));
26572
26573    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
26574    cx.assert_excerpts_with_selections(indoc! {"
26575        [EXCERPT]
26576        ˇ[FOLDED]
26577        [EXCERPT]
26578        a1
26579        b1
26580        [EXCERPT]
26581        [FOLDED]
26582        [EXCERPT]
26583        [FOLDED]
26584        "
26585    });
26586    cx.simulate_keystroke("down");
26587    cx.assert_excerpts_with_selections(indoc! {"
26588        [EXCERPT]
26589        [FOLDED]
26590        [EXCERPT]
26591        ˇa1
26592        b1
26593        [EXCERPT]
26594        [FOLDED]
26595        [EXCERPT]
26596        [FOLDED]
26597        "
26598    });
26599    cx.simulate_keystroke("down");
26600    cx.assert_excerpts_with_selections(indoc! {"
26601        [EXCERPT]
26602        [FOLDED]
26603        [EXCERPT]
26604        a1
26605        ˇb1
26606        [EXCERPT]
26607        [FOLDED]
26608        [EXCERPT]
26609        [FOLDED]
26610        "
26611    });
26612    cx.simulate_keystroke("down");
26613    cx.assert_excerpts_with_selections(indoc! {"
26614        [EXCERPT]
26615        [FOLDED]
26616        [EXCERPT]
26617        a1
26618        b1
26619        ˇ[EXCERPT]
26620        [FOLDED]
26621        [EXCERPT]
26622        [FOLDED]
26623        "
26624    });
26625    cx.simulate_keystroke("down");
26626    cx.assert_excerpts_with_selections(indoc! {"
26627        [EXCERPT]
26628        [FOLDED]
26629        [EXCERPT]
26630        a1
26631        b1
26632        [EXCERPT]
26633        ˇ[FOLDED]
26634        [EXCERPT]
26635        [FOLDED]
26636        "
26637    });
26638    for _ in 0..5 {
26639        cx.simulate_keystroke("down");
26640        cx.assert_excerpts_with_selections(indoc! {"
26641            [EXCERPT]
26642            [FOLDED]
26643            [EXCERPT]
26644            a1
26645            b1
26646            [EXCERPT]
26647            [FOLDED]
26648            [EXCERPT]
26649            ˇ[FOLDED]
26650            "
26651        });
26652    }
26653
26654    cx.simulate_keystroke("up");
26655    cx.assert_excerpts_with_selections(indoc! {"
26656        [EXCERPT]
26657        [FOLDED]
26658        [EXCERPT]
26659        a1
26660        b1
26661        [EXCERPT]
26662        ˇ[FOLDED]
26663        [EXCERPT]
26664        [FOLDED]
26665        "
26666    });
26667    cx.simulate_keystroke("up");
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    cx.simulate_keystroke("up");
26681    cx.assert_excerpts_with_selections(indoc! {"
26682        [EXCERPT]
26683        [FOLDED]
26684        [EXCERPT]
26685        a1
26686        ˇb1
26687        [EXCERPT]
26688        [FOLDED]
26689        [EXCERPT]
26690        [FOLDED]
26691        "
26692    });
26693    cx.simulate_keystroke("up");
26694    cx.assert_excerpts_with_selections(indoc! {"
26695        [EXCERPT]
26696        [FOLDED]
26697        [EXCERPT]
26698        ˇa1
26699        b1
26700        [EXCERPT]
26701        [FOLDED]
26702        [EXCERPT]
26703        [FOLDED]
26704        "
26705    });
26706    for _ in 0..5 {
26707        cx.simulate_keystroke("up");
26708        cx.assert_excerpts_with_selections(indoc! {"
26709            [EXCERPT]
26710            ˇ[FOLDED]
26711            [EXCERPT]
26712            a1
26713            b1
26714            [EXCERPT]
26715            [FOLDED]
26716            [EXCERPT]
26717            [FOLDED]
26718            "
26719        });
26720    }
26721}
26722
26723#[gpui::test]
26724async fn test_edit_prediction_text(cx: &mut TestAppContext) {
26725    init_test(cx, |_| {});
26726
26727    // Simple insertion
26728    assert_highlighted_edits(
26729        "Hello, world!",
26730        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
26731        true,
26732        cx,
26733        &|highlighted_edits, cx| {
26734            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
26735            assert_eq!(highlighted_edits.highlights.len(), 1);
26736            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
26737            assert_eq!(
26738                highlighted_edits.highlights[0].1.background_color,
26739                Some(cx.theme().status().created_background)
26740            );
26741        },
26742    )
26743    .await;
26744
26745    // Replacement
26746    assert_highlighted_edits(
26747        "This is a test.",
26748        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
26749        false,
26750        cx,
26751        &|highlighted_edits, cx| {
26752            assert_eq!(highlighted_edits.text, "That is a test.");
26753            assert_eq!(highlighted_edits.highlights.len(), 1);
26754            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
26755            assert_eq!(
26756                highlighted_edits.highlights[0].1.background_color,
26757                Some(cx.theme().status().created_background)
26758            );
26759        },
26760    )
26761    .await;
26762
26763    // Multiple edits
26764    assert_highlighted_edits(
26765        "Hello, world!",
26766        vec![
26767            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
26768            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
26769        ],
26770        false,
26771        cx,
26772        &|highlighted_edits, cx| {
26773            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
26774            assert_eq!(highlighted_edits.highlights.len(), 2);
26775            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
26776            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
26777            assert_eq!(
26778                highlighted_edits.highlights[0].1.background_color,
26779                Some(cx.theme().status().created_background)
26780            );
26781            assert_eq!(
26782                highlighted_edits.highlights[1].1.background_color,
26783                Some(cx.theme().status().created_background)
26784            );
26785        },
26786    )
26787    .await;
26788
26789    // Multiple lines with edits
26790    assert_highlighted_edits(
26791        "First line\nSecond line\nThird line\nFourth line",
26792        vec![
26793            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
26794            (
26795                Point::new(2, 0)..Point::new(2, 10),
26796                "New third line".to_string(),
26797            ),
26798            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
26799        ],
26800        false,
26801        cx,
26802        &|highlighted_edits, cx| {
26803            assert_eq!(
26804                highlighted_edits.text,
26805                "Second modified\nNew third line\nFourth updated line"
26806            );
26807            assert_eq!(highlighted_edits.highlights.len(), 3);
26808            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
26809            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
26810            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
26811            for highlight in &highlighted_edits.highlights {
26812                assert_eq!(
26813                    highlight.1.background_color,
26814                    Some(cx.theme().status().created_background)
26815                );
26816            }
26817        },
26818    )
26819    .await;
26820}
26821
26822#[gpui::test]
26823async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
26824    init_test(cx, |_| {});
26825
26826    // Deletion
26827    assert_highlighted_edits(
26828        "Hello, world!",
26829        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
26830        true,
26831        cx,
26832        &|highlighted_edits, cx| {
26833            assert_eq!(highlighted_edits.text, "Hello, world!");
26834            assert_eq!(highlighted_edits.highlights.len(), 1);
26835            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
26836            assert_eq!(
26837                highlighted_edits.highlights[0].1.background_color,
26838                Some(cx.theme().status().deleted_background)
26839            );
26840        },
26841    )
26842    .await;
26843
26844    // Insertion
26845    assert_highlighted_edits(
26846        "Hello, world!",
26847        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
26848        true,
26849        cx,
26850        &|highlighted_edits, cx| {
26851            assert_eq!(highlighted_edits.highlights.len(), 1);
26852            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
26853            assert_eq!(
26854                highlighted_edits.highlights[0].1.background_color,
26855                Some(cx.theme().status().created_background)
26856            );
26857        },
26858    )
26859    .await;
26860}
26861
26862async fn assert_highlighted_edits(
26863    text: &str,
26864    edits: Vec<(Range<Point>, String)>,
26865    include_deletions: bool,
26866    cx: &mut TestAppContext,
26867    assertion_fn: &dyn Fn(HighlightedText, &App),
26868) {
26869    let window = cx.add_window(|window, cx| {
26870        let buffer = MultiBuffer::build_simple(text, cx);
26871        Editor::new(EditorMode::full(), buffer, None, window, cx)
26872    });
26873    let cx = &mut VisualTestContext::from_window(*window, cx);
26874
26875    let (buffer, snapshot) = window
26876        .update(cx, |editor, _window, cx| {
26877            (
26878                editor.buffer().clone(),
26879                editor.buffer().read(cx).snapshot(cx),
26880            )
26881        })
26882        .unwrap();
26883
26884    let edits = edits
26885        .into_iter()
26886        .map(|(range, edit)| {
26887            (
26888                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
26889                edit,
26890            )
26891        })
26892        .collect::<Vec<_>>();
26893
26894    let text_anchor_edits = edits
26895        .clone()
26896        .into_iter()
26897        .map(|(range, edit)| {
26898            (
26899                range.start.expect_text_anchor()..range.end.expect_text_anchor(),
26900                edit.into(),
26901            )
26902        })
26903        .collect::<Vec<_>>();
26904
26905    let edit_preview = window
26906        .update(cx, |_, _window, cx| {
26907            buffer
26908                .read(cx)
26909                .as_singleton()
26910                .unwrap()
26911                .read(cx)
26912                .preview_edits(text_anchor_edits.into(), cx)
26913        })
26914        .unwrap()
26915        .await;
26916
26917    cx.update(|_window, cx| {
26918        let highlighted_edits = edit_prediction_edit_text(
26919            snapshot.as_singleton().unwrap(),
26920            &edits,
26921            &edit_preview,
26922            include_deletions,
26923            &snapshot,
26924            cx,
26925        );
26926        assertion_fn(highlighted_edits, cx)
26927    });
26928}
26929
26930#[track_caller]
26931fn assert_breakpoint(
26932    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
26933    path: &Arc<Path>,
26934    expected: Vec<(u32, Breakpoint)>,
26935) {
26936    if expected.is_empty() {
26937        assert!(!breakpoints.contains_key(path), "{}", path.display());
26938    } else {
26939        let mut breakpoint = breakpoints
26940            .get(path)
26941            .unwrap()
26942            .iter()
26943            .map(|breakpoint| {
26944                (
26945                    breakpoint.row,
26946                    Breakpoint {
26947                        message: breakpoint.message.clone(),
26948                        state: breakpoint.state,
26949                        condition: breakpoint.condition.clone(),
26950                        hit_condition: breakpoint.hit_condition.clone(),
26951                    },
26952                )
26953            })
26954            .collect::<Vec<_>>();
26955
26956        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
26957
26958        assert_eq!(expected, breakpoint);
26959    }
26960}
26961
26962fn add_log_breakpoint_at_cursor(
26963    editor: &mut Editor,
26964    log_message: &str,
26965    window: &mut Window,
26966    cx: &mut Context<Editor>,
26967) {
26968    let (anchor, bp) = editor
26969        .breakpoints_at_cursors(window, cx)
26970        .first()
26971        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
26972        .unwrap_or_else(|| {
26973            let snapshot = editor.snapshot(window, cx);
26974            let cursor_position: Point =
26975                editor.selections.newest(&snapshot.display_snapshot).head();
26976
26977            let breakpoint_position = snapshot
26978                .buffer_snapshot()
26979                .anchor_before(Point::new(cursor_position.row, 0));
26980
26981            (breakpoint_position, Breakpoint::new_log(log_message))
26982        });
26983
26984    editor.edit_breakpoint_at_anchor(
26985        anchor,
26986        bp,
26987        BreakpointEditAction::EditLogMessage(log_message.into()),
26988        cx,
26989    );
26990}
26991
26992#[gpui::test]
26993async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
26994    init_test(cx, |_| {});
26995
26996    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
26997    let fs = FakeFs::new(cx.executor());
26998    fs.insert_tree(
26999        path!("/a"),
27000        json!({
27001            "main.rs": sample_text,
27002        }),
27003    )
27004    .await;
27005    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27006    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27007    let cx = &mut VisualTestContext::from_window(*window, cx);
27008
27009    let fs = FakeFs::new(cx.executor());
27010    fs.insert_tree(
27011        path!("/a"),
27012        json!({
27013            "main.rs": sample_text,
27014        }),
27015    )
27016    .await;
27017    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27018    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27019    let workspace = window
27020        .read_with(cx, |mw, _| mw.workspace().clone())
27021        .unwrap();
27022    let cx = &mut VisualTestContext::from_window(*window, cx);
27023    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27024        workspace.project().update(cx, |project, cx| {
27025            project.worktrees(cx).next().unwrap().read(cx).id()
27026        })
27027    });
27028
27029    let buffer = project
27030        .update(cx, |project, cx| {
27031            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27032        })
27033        .await
27034        .unwrap();
27035
27036    let (editor, cx) = cx.add_window_view(|window, cx| {
27037        Editor::new(
27038            EditorMode::full(),
27039            MultiBuffer::build_from_buffer(buffer, cx),
27040            Some(project.clone()),
27041            window,
27042            cx,
27043        )
27044    });
27045
27046    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27047    let abs_path = project.read_with(cx, |project, cx| {
27048        project
27049            .absolute_path(&project_path, cx)
27050            .map(Arc::from)
27051            .unwrap()
27052    });
27053
27054    // assert we can add breakpoint on the first line
27055    editor.update_in(cx, |editor, window, cx| {
27056        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27057        editor.move_to_end(&MoveToEnd, window, cx);
27058        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27059    });
27060
27061    let breakpoints = editor.update(cx, |editor, cx| {
27062        editor
27063            .breakpoint_store()
27064            .as_ref()
27065            .unwrap()
27066            .read(cx)
27067            .all_source_breakpoints(cx)
27068    });
27069
27070    assert_eq!(1, breakpoints.len());
27071    assert_breakpoint(
27072        &breakpoints,
27073        &abs_path,
27074        vec![
27075            (0, Breakpoint::new_standard()),
27076            (3, Breakpoint::new_standard()),
27077        ],
27078    );
27079
27080    editor.update_in(cx, |editor, window, cx| {
27081        editor.move_to_beginning(&MoveToBeginning, window, cx);
27082        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27083    });
27084
27085    let breakpoints = editor.update(cx, |editor, cx| {
27086        editor
27087            .breakpoint_store()
27088            .as_ref()
27089            .unwrap()
27090            .read(cx)
27091            .all_source_breakpoints(cx)
27092    });
27093
27094    assert_eq!(1, breakpoints.len());
27095    assert_breakpoint(
27096        &breakpoints,
27097        &abs_path,
27098        vec![(3, Breakpoint::new_standard())],
27099    );
27100
27101    editor.update_in(cx, |editor, window, cx| {
27102        editor.move_to_end(&MoveToEnd, window, cx);
27103        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27104    });
27105
27106    let breakpoints = editor.update(cx, |editor, cx| {
27107        editor
27108            .breakpoint_store()
27109            .as_ref()
27110            .unwrap()
27111            .read(cx)
27112            .all_source_breakpoints(cx)
27113    });
27114
27115    assert_eq!(0, breakpoints.len());
27116    assert_breakpoint(&breakpoints, &abs_path, vec![]);
27117}
27118
27119#[gpui::test]
27120async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
27121    init_test(cx, |_| {});
27122
27123    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27124
27125    let fs = FakeFs::new(cx.executor());
27126    fs.insert_tree(
27127        path!("/a"),
27128        json!({
27129            "main.rs": sample_text,
27130        }),
27131    )
27132    .await;
27133    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27134    let (multi_workspace, cx) =
27135        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27136    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27137
27138    let worktree_id = workspace.update(cx, |workspace, cx| {
27139        workspace.project().update(cx, |project, cx| {
27140            project.worktrees(cx).next().unwrap().read(cx).id()
27141        })
27142    });
27143
27144    let buffer = project
27145        .update(cx, |project, cx| {
27146            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27147        })
27148        .await
27149        .unwrap();
27150
27151    let (editor, cx) = cx.add_window_view(|window, cx| {
27152        Editor::new(
27153            EditorMode::full(),
27154            MultiBuffer::build_from_buffer(buffer, cx),
27155            Some(project.clone()),
27156            window,
27157            cx,
27158        )
27159    });
27160
27161    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27162    let abs_path = project.read_with(cx, |project, cx| {
27163        project
27164            .absolute_path(&project_path, cx)
27165            .map(Arc::from)
27166            .unwrap()
27167    });
27168
27169    editor.update_in(cx, |editor, window, cx| {
27170        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
27171    });
27172
27173    let breakpoints = editor.update(cx, |editor, cx| {
27174        editor
27175            .breakpoint_store()
27176            .as_ref()
27177            .unwrap()
27178            .read(cx)
27179            .all_source_breakpoints(cx)
27180    });
27181
27182    assert_breakpoint(
27183        &breakpoints,
27184        &abs_path,
27185        vec![(0, Breakpoint::new_log("hello world"))],
27186    );
27187
27188    // Removing a log message from a log breakpoint should remove it
27189    editor.update_in(cx, |editor, window, cx| {
27190        add_log_breakpoint_at_cursor(editor, "", window, cx);
27191    });
27192
27193    let breakpoints = editor.update(cx, |editor, cx| {
27194        editor
27195            .breakpoint_store()
27196            .as_ref()
27197            .unwrap()
27198            .read(cx)
27199            .all_source_breakpoints(cx)
27200    });
27201
27202    assert_breakpoint(&breakpoints, &abs_path, vec![]);
27203
27204    editor.update_in(cx, |editor, window, cx| {
27205        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27206        editor.move_to_end(&MoveToEnd, window, cx);
27207        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27208        // Not adding a log message to a standard breakpoint shouldn't remove it
27209        add_log_breakpoint_at_cursor(editor, "", window, cx);
27210    });
27211
27212    let breakpoints = editor.update(cx, |editor, cx| {
27213        editor
27214            .breakpoint_store()
27215            .as_ref()
27216            .unwrap()
27217            .read(cx)
27218            .all_source_breakpoints(cx)
27219    });
27220
27221    assert_breakpoint(
27222        &breakpoints,
27223        &abs_path,
27224        vec![
27225            (0, Breakpoint::new_standard()),
27226            (3, Breakpoint::new_standard()),
27227        ],
27228    );
27229
27230    editor.update_in(cx, |editor, window, cx| {
27231        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
27232    });
27233
27234    let breakpoints = editor.update(cx, |editor, cx| {
27235        editor
27236            .breakpoint_store()
27237            .as_ref()
27238            .unwrap()
27239            .read(cx)
27240            .all_source_breakpoints(cx)
27241    });
27242
27243    assert_breakpoint(
27244        &breakpoints,
27245        &abs_path,
27246        vec![
27247            (0, Breakpoint::new_standard()),
27248            (3, Breakpoint::new_log("hello world")),
27249        ],
27250    );
27251
27252    editor.update_in(cx, |editor, window, cx| {
27253        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
27254    });
27255
27256    let breakpoints = editor.update(cx, |editor, cx| {
27257        editor
27258            .breakpoint_store()
27259            .as_ref()
27260            .unwrap()
27261            .read(cx)
27262            .all_source_breakpoints(cx)
27263    });
27264
27265    assert_breakpoint(
27266        &breakpoints,
27267        &abs_path,
27268        vec![
27269            (0, Breakpoint::new_standard()),
27270            (3, Breakpoint::new_log("hello Earth!!")),
27271        ],
27272    );
27273}
27274
27275/// This also tests that Editor::breakpoint_at_cursor_head is working properly
27276/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
27277/// or when breakpoints were placed out of order. This tests for a regression too
27278#[gpui::test]
27279async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
27280    init_test(cx, |_| {});
27281
27282    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27283    let fs = FakeFs::new(cx.executor());
27284    fs.insert_tree(
27285        path!("/a"),
27286        json!({
27287            "main.rs": sample_text,
27288        }),
27289    )
27290    .await;
27291    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27292    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27293    let cx = &mut VisualTestContext::from_window(*window, cx);
27294
27295    let fs = FakeFs::new(cx.executor());
27296    fs.insert_tree(
27297        path!("/a"),
27298        json!({
27299            "main.rs": sample_text,
27300        }),
27301    )
27302    .await;
27303    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27304    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27305    let workspace = window
27306        .read_with(cx, |mw, _| mw.workspace().clone())
27307        .unwrap();
27308    let cx = &mut VisualTestContext::from_window(*window, cx);
27309    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27310        workspace.project().update(cx, |project, cx| {
27311            project.worktrees(cx).next().unwrap().read(cx).id()
27312        })
27313    });
27314
27315    let buffer = project
27316        .update(cx, |project, cx| {
27317            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27318        })
27319        .await
27320        .unwrap();
27321
27322    let (editor, cx) = cx.add_window_view(|window, cx| {
27323        Editor::new(
27324            EditorMode::full(),
27325            MultiBuffer::build_from_buffer(buffer, cx),
27326            Some(project.clone()),
27327            window,
27328            cx,
27329        )
27330    });
27331
27332    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
27333    let abs_path = project.read_with(cx, |project, cx| {
27334        project
27335            .absolute_path(&project_path, cx)
27336            .map(Arc::from)
27337            .unwrap()
27338    });
27339
27340    // assert we can add breakpoint on the first line
27341    editor.update_in(cx, |editor, window, cx| {
27342        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27343        editor.move_to_end(&MoveToEnd, window, cx);
27344        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27345        editor.move_up(&MoveUp, window, cx);
27346        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27347    });
27348
27349    let breakpoints = editor.update(cx, |editor, cx| {
27350        editor
27351            .breakpoint_store()
27352            .as_ref()
27353            .unwrap()
27354            .read(cx)
27355            .all_source_breakpoints(cx)
27356    });
27357
27358    assert_eq!(1, breakpoints.len());
27359    assert_breakpoint(
27360        &breakpoints,
27361        &abs_path,
27362        vec![
27363            (0, Breakpoint::new_standard()),
27364            (2, Breakpoint::new_standard()),
27365            (3, Breakpoint::new_standard()),
27366        ],
27367    );
27368
27369    editor.update_in(cx, |editor, window, cx| {
27370        editor.move_to_beginning(&MoveToBeginning, window, cx);
27371        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27372        editor.move_to_end(&MoveToEnd, window, cx);
27373        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27374        // Disabling a breakpoint that doesn't exist should do nothing
27375        editor.move_up(&MoveUp, window, cx);
27376        editor.move_up(&MoveUp, window, cx);
27377        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27378    });
27379
27380    let breakpoints = editor.update(cx, |editor, cx| {
27381        editor
27382            .breakpoint_store()
27383            .as_ref()
27384            .unwrap()
27385            .read(cx)
27386            .all_source_breakpoints(cx)
27387    });
27388
27389    let disable_breakpoint = {
27390        let mut bp = Breakpoint::new_standard();
27391        bp.state = BreakpointState::Disabled;
27392        bp
27393    };
27394
27395    assert_eq!(1, breakpoints.len());
27396    assert_breakpoint(
27397        &breakpoints,
27398        &abs_path,
27399        vec![
27400            (0, disable_breakpoint.clone()),
27401            (2, Breakpoint::new_standard()),
27402            (3, disable_breakpoint.clone()),
27403        ],
27404    );
27405
27406    editor.update_in(cx, |editor, window, cx| {
27407        editor.move_to_beginning(&MoveToBeginning, window, cx);
27408        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
27409        editor.move_to_end(&MoveToEnd, window, cx);
27410        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
27411        editor.move_up(&MoveUp, window, cx);
27412        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
27413    });
27414
27415    let breakpoints = editor.update(cx, |editor, cx| {
27416        editor
27417            .breakpoint_store()
27418            .as_ref()
27419            .unwrap()
27420            .read(cx)
27421            .all_source_breakpoints(cx)
27422    });
27423
27424    assert_eq!(1, breakpoints.len());
27425    assert_breakpoint(
27426        &breakpoints,
27427        &abs_path,
27428        vec![
27429            (0, Breakpoint::new_standard()),
27430            (2, disable_breakpoint),
27431            (3, Breakpoint::new_standard()),
27432        ],
27433    );
27434}
27435
27436#[gpui::test]
27437async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
27438    init_test(cx, |_| {});
27439
27440    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
27441    let fs = FakeFs::new(cx.executor());
27442    fs.insert_tree(
27443        path!("/a"),
27444        json!({
27445            "main.rs": sample_text,
27446        }),
27447    )
27448    .await;
27449    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27450    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27451    let workspace = window
27452        .read_with(cx, |mw, _| mw.workspace().clone())
27453        .unwrap();
27454    let cx = &mut VisualTestContext::from_window(*window, cx);
27455    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27456        workspace.project().update(cx, |project, cx| {
27457            project.worktrees(cx).next().unwrap().read(cx).id()
27458        })
27459    });
27460
27461    let buffer = project
27462        .update(cx, |project, cx| {
27463            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
27464        })
27465        .await
27466        .unwrap();
27467
27468    let (editor, cx) = cx.add_window_view(|window, cx| {
27469        Editor::new(
27470            EditorMode::full(),
27471            MultiBuffer::build_from_buffer(buffer, cx),
27472            Some(project.clone()),
27473            window,
27474            cx,
27475        )
27476    });
27477
27478    // Simulate hovering over row 0 with no existing breakpoint.
27479    editor.update(cx, |editor, _cx| {
27480        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
27481            display_row: DisplayRow(0),
27482            is_active: true,
27483            collides_with_existing_breakpoint: false,
27484        });
27485    });
27486
27487    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
27488    editor.update_in(cx, |editor, window, cx| {
27489        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27490    });
27491    editor.update(cx, |editor, _cx| {
27492        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27493        assert!(
27494            indicator.collides_with_existing_breakpoint,
27495            "Adding a breakpoint on the hovered row should set collision to true"
27496        );
27497    });
27498
27499    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
27500    editor.update_in(cx, |editor, window, cx| {
27501        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27502    });
27503    editor.update(cx, |editor, _cx| {
27504        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27505        assert!(
27506            !indicator.collides_with_existing_breakpoint,
27507            "Removing a breakpoint on the hovered row should set collision to false"
27508        );
27509    });
27510
27511    // Now move cursor to row 2 while phantom indicator stays on row 0.
27512    editor.update_in(cx, |editor, window, cx| {
27513        editor.move_down(&MoveDown, window, cx);
27514        editor.move_down(&MoveDown, window, cx);
27515    });
27516
27517    // Ensure phantom indicator is still on row 0, not colliding.
27518    editor.update(cx, |editor, _cx| {
27519        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
27520            display_row: DisplayRow(0),
27521            is_active: true,
27522            collides_with_existing_breakpoint: false,
27523        });
27524    });
27525
27526    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
27527    editor.update_in(cx, |editor, window, cx| {
27528        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
27529    });
27530    editor.update(cx, |editor, _cx| {
27531        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
27532        assert!(
27533            !indicator.collides_with_existing_breakpoint,
27534            "Toggling a breakpoint on a different row should not affect the phantom indicator"
27535        );
27536    });
27537}
27538
27539#[gpui::test]
27540async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
27541    init_test(cx, |_| {});
27542    let capabilities = lsp::ServerCapabilities {
27543        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
27544            prepare_provider: Some(true),
27545            work_done_progress_options: Default::default(),
27546        })),
27547        ..Default::default()
27548    };
27549    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
27550
27551    cx.set_state(indoc! {"
27552        struct Fˇoo {}
27553    "});
27554
27555    cx.update_editor(|editor, _, cx| {
27556        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
27557        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
27558        editor.highlight_background(
27559            HighlightKey::DocumentHighlightRead,
27560            &[highlight_range],
27561            |_, theme| theme.colors().editor_document_highlight_read_background,
27562            cx,
27563        );
27564    });
27565
27566    let mut prepare_rename_handler = cx
27567        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
27568            move |_, _, _| async move {
27569                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
27570                    start: lsp::Position {
27571                        line: 0,
27572                        character: 7,
27573                    },
27574                    end: lsp::Position {
27575                        line: 0,
27576                        character: 10,
27577                    },
27578                })))
27579            },
27580        );
27581    let prepare_rename_task = cx
27582        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
27583        .expect("Prepare rename was not started");
27584    prepare_rename_handler.next().await.unwrap();
27585    prepare_rename_task.await.expect("Prepare rename failed");
27586
27587    let mut rename_handler =
27588        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
27589            let edit = lsp::TextEdit {
27590                range: lsp::Range {
27591                    start: lsp::Position {
27592                        line: 0,
27593                        character: 7,
27594                    },
27595                    end: lsp::Position {
27596                        line: 0,
27597                        character: 10,
27598                    },
27599                },
27600                new_text: "FooRenamed".to_string(),
27601            };
27602            Ok(Some(lsp::WorkspaceEdit::new(
27603                // Specify the same edit twice
27604                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
27605            )))
27606        });
27607    let rename_task = cx
27608        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
27609        .expect("Confirm rename was not started");
27610    rename_handler.next().await.unwrap();
27611    rename_task.await.expect("Confirm rename failed");
27612    cx.run_until_parked();
27613
27614    // Despite two edits, only one is actually applied as those are identical
27615    cx.assert_editor_state(indoc! {"
27616        struct FooRenamedˇ {}
27617    "});
27618}
27619
27620#[gpui::test]
27621async fn test_rename_without_prepare(cx: &mut TestAppContext) {
27622    init_test(cx, |_| {});
27623    // These capabilities indicate that the server does not support prepare rename.
27624    let capabilities = lsp::ServerCapabilities {
27625        rename_provider: Some(lsp::OneOf::Left(true)),
27626        ..Default::default()
27627    };
27628    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
27629
27630    cx.set_state(indoc! {"
27631        struct Fˇoo {}
27632    "});
27633
27634    cx.update_editor(|editor, _window, cx| {
27635        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
27636        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
27637        editor.highlight_background(
27638            HighlightKey::DocumentHighlightRead,
27639            &[highlight_range],
27640            |_, theme| theme.colors().editor_document_highlight_read_background,
27641            cx,
27642        );
27643    });
27644
27645    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
27646        .expect("Prepare rename was not started")
27647        .await
27648        .expect("Prepare rename failed");
27649
27650    let mut rename_handler =
27651        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
27652            let edit = lsp::TextEdit {
27653                range: lsp::Range {
27654                    start: lsp::Position {
27655                        line: 0,
27656                        character: 7,
27657                    },
27658                    end: lsp::Position {
27659                        line: 0,
27660                        character: 10,
27661                    },
27662                },
27663                new_text: "FooRenamed".to_string(),
27664            };
27665            Ok(Some(lsp::WorkspaceEdit::new(
27666                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
27667            )))
27668        });
27669    let rename_task = cx
27670        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
27671        .expect("Confirm rename was not started");
27672    rename_handler.next().await.unwrap();
27673    rename_task.await.expect("Confirm rename failed");
27674    cx.run_until_parked();
27675
27676    // Correct range is renamed, as `surrounding_word` is used to find it.
27677    cx.assert_editor_state(indoc! {"
27678        struct FooRenamedˇ {}
27679    "});
27680}
27681
27682#[gpui::test]
27683async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
27684    init_test(cx, |_| {});
27685    let mut cx = EditorTestContext::new(cx).await;
27686
27687    let language = Arc::new(
27688        Language::new(
27689            LanguageConfig::default(),
27690            Some(tree_sitter_html::LANGUAGE.into()),
27691        )
27692        .with_brackets_query(
27693            r#"
27694            ("<" @open "/>" @close)
27695            ("</" @open ">" @close)
27696            ("<" @open ">" @close)
27697            ("\"" @open "\"" @close)
27698            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
27699        "#,
27700        )
27701        .unwrap(),
27702    );
27703    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27704
27705    cx.set_state(indoc! {"
27706        <span>ˇ</span>
27707    "});
27708    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27709    cx.assert_editor_state(indoc! {"
27710        <span>
27711        ˇ
27712        </span>
27713    "});
27714
27715    cx.set_state(indoc! {"
27716        <span><span></span>ˇ</span>
27717    "});
27718    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27719    cx.assert_editor_state(indoc! {"
27720        <span><span></span>
27721        ˇ</span>
27722    "});
27723
27724    cx.set_state(indoc! {"
27725        <span>ˇ
27726        </span>
27727    "});
27728    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
27729    cx.assert_editor_state(indoc! {"
27730        <span>
27731        ˇ
27732        </span>
27733    "});
27734}
27735
27736#[gpui::test(iterations = 10)]
27737async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
27738    init_test(cx, |_| {});
27739
27740    let fs = FakeFs::new(cx.executor());
27741    fs.insert_tree(
27742        path!("/dir"),
27743        json!({
27744            "a.ts": "a",
27745        }),
27746    )
27747    .await;
27748
27749    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
27750    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27751    let workspace = window
27752        .read_with(cx, |mw, _| mw.workspace().clone())
27753        .unwrap();
27754    let cx = &mut VisualTestContext::from_window(*window, cx);
27755
27756    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27757    language_registry.add(Arc::new(Language::new(
27758        LanguageConfig {
27759            name: "TypeScript".into(),
27760            matcher: LanguageMatcher {
27761                path_suffixes: vec!["ts".to_string()],
27762                ..Default::default()
27763            },
27764            ..Default::default()
27765        },
27766        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
27767    )));
27768    let mut fake_language_servers = language_registry.register_fake_lsp(
27769        "TypeScript",
27770        FakeLspAdapter {
27771            capabilities: lsp::ServerCapabilities {
27772                code_lens_provider: Some(lsp::CodeLensOptions {
27773                    resolve_provider: Some(true),
27774                }),
27775                execute_command_provider: Some(lsp::ExecuteCommandOptions {
27776                    commands: vec!["_the/command".to_string()],
27777                    ..lsp::ExecuteCommandOptions::default()
27778                }),
27779                ..lsp::ServerCapabilities::default()
27780            },
27781            ..FakeLspAdapter::default()
27782        },
27783    );
27784
27785    let editor = workspace
27786        .update_in(cx, |workspace, window, cx| {
27787            workspace.open_abs_path(
27788                PathBuf::from(path!("/dir/a.ts")),
27789                OpenOptions::default(),
27790                window,
27791                cx,
27792            )
27793        })
27794        .await
27795        .unwrap()
27796        .downcast::<Editor>()
27797        .unwrap();
27798    cx.executor().run_until_parked();
27799
27800    let fake_server = fake_language_servers.next().await.unwrap();
27801
27802    let buffer = editor.update(cx, |editor, cx| {
27803        editor
27804            .buffer()
27805            .read(cx)
27806            .as_singleton()
27807            .expect("have opened a single file by path")
27808    });
27809
27810    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
27811    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
27812    drop(buffer_snapshot);
27813    let actions = cx
27814        .update_window(*window, |_, window, cx| {
27815            project.code_actions(&buffer, anchor..anchor, window, cx)
27816        })
27817        .unwrap();
27818
27819    fake_server
27820        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
27821            Ok(Some(vec![
27822                lsp::CodeLens {
27823                    range: lsp::Range::default(),
27824                    command: Some(lsp::Command {
27825                        title: "Code lens command".to_owned(),
27826                        command: "_the/command".to_owned(),
27827                        arguments: None,
27828                    }),
27829                    data: None,
27830                },
27831                lsp::CodeLens {
27832                    range: lsp::Range::default(),
27833                    command: Some(lsp::Command {
27834                        title: "Command not in capabilities".to_owned(),
27835                        command: "not in capabilities".to_owned(),
27836                        arguments: None,
27837                    }),
27838                    data: None,
27839                },
27840                lsp::CodeLens {
27841                    range: lsp::Range {
27842                        start: lsp::Position {
27843                            line: 1,
27844                            character: 1,
27845                        },
27846                        end: lsp::Position {
27847                            line: 1,
27848                            character: 1,
27849                        },
27850                    },
27851                    command: Some(lsp::Command {
27852                        title: "Command not in range".to_owned(),
27853                        command: "_the/command".to_owned(),
27854                        arguments: None,
27855                    }),
27856                    data: None,
27857                },
27858            ]))
27859        })
27860        .next()
27861        .await;
27862
27863    let actions = actions.await.unwrap();
27864    assert_eq!(
27865        actions.len(),
27866        1,
27867        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
27868    );
27869    let action = actions[0].clone();
27870    let apply = project.update(cx, |project, cx| {
27871        project.apply_code_action(buffer.clone(), action, true, cx)
27872    });
27873
27874    // Resolving the code action does not populate its edits. In absence of
27875    // edits, we must execute the given command.
27876    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
27877        |mut lens, _| async move {
27878            let lens_command = lens.command.as_mut().expect("should have a command");
27879            assert_eq!(lens_command.title, "Code lens command");
27880            lens_command.arguments = Some(vec![json!("the-argument")]);
27881            Ok(lens)
27882        },
27883    );
27884
27885    // While executing the command, the language server sends the editor
27886    // a `workspaceEdit` request.
27887    fake_server
27888        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
27889            let fake = fake_server.clone();
27890            move |params, _| {
27891                assert_eq!(params.command, "_the/command");
27892                let fake = fake.clone();
27893                async move {
27894                    fake.server
27895                        .request::<lsp::request::ApplyWorkspaceEdit>(
27896                            lsp::ApplyWorkspaceEditParams {
27897                                label: None,
27898                                edit: lsp::WorkspaceEdit {
27899                                    changes: Some(
27900                                        [(
27901                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
27902                                            vec![lsp::TextEdit {
27903                                                range: lsp::Range::new(
27904                                                    lsp::Position::new(0, 0),
27905                                                    lsp::Position::new(0, 0),
27906                                                ),
27907                                                new_text: "X".into(),
27908                                            }],
27909                                        )]
27910                                        .into_iter()
27911                                        .collect(),
27912                                    ),
27913                                    ..lsp::WorkspaceEdit::default()
27914                                },
27915                            },
27916                            DEFAULT_LSP_REQUEST_TIMEOUT,
27917                        )
27918                        .await
27919                        .into_response()
27920                        .unwrap();
27921                    Ok(Some(json!(null)))
27922                }
27923            }
27924        })
27925        .next()
27926        .await;
27927
27928    // Applying the code lens command returns a project transaction containing the edits
27929    // sent by the language server in its `workspaceEdit` request.
27930    let transaction = apply.await.unwrap();
27931    assert!(transaction.0.contains_key(&buffer));
27932    buffer.update(cx, |buffer, cx| {
27933        assert_eq!(buffer.text(), "Xa");
27934        buffer.undo(cx);
27935        assert_eq!(buffer.text(), "a");
27936    });
27937
27938    let actions_after_edits = cx
27939        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
27940        .unwrap()
27941        .await;
27942    assert_eq!(
27943        actions, actions_after_edits,
27944        "For the same selection, same code lens actions should be returned"
27945    );
27946
27947    let _responses =
27948        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
27949            panic!("No more code lens requests are expected");
27950        });
27951    editor.update_in(cx, |editor, window, cx| {
27952        editor.select_all(&SelectAll, window, cx);
27953    });
27954    cx.executor().run_until_parked();
27955    let new_actions = cx
27956        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
27957        .unwrap()
27958        .await;
27959    assert_eq!(
27960        actions, new_actions,
27961        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
27962    );
27963}
27964
27965#[gpui::test]
27966async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
27967    init_test(cx, |_| {});
27968
27969    let fs = FakeFs::new(cx.executor());
27970    let main_text = r#"fn main() {
27971println!("1");
27972println!("2");
27973println!("3");
27974println!("4");
27975println!("5");
27976}"#;
27977    let lib_text = "mod foo {}";
27978    fs.insert_tree(
27979        path!("/a"),
27980        json!({
27981            "lib.rs": lib_text,
27982            "main.rs": main_text,
27983        }),
27984    )
27985    .await;
27986
27987    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27988    let (multi_workspace, cx) =
27989        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27990    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27991    let worktree_id = workspace.update(cx, |workspace, cx| {
27992        workspace.project().update(cx, |project, cx| {
27993            project.worktrees(cx).next().unwrap().read(cx).id()
27994        })
27995    });
27996
27997    let expected_ranges = vec![
27998        Point::new(0, 0)..Point::new(0, 0),
27999        Point::new(1, 0)..Point::new(1, 1),
28000        Point::new(2, 0)..Point::new(2, 2),
28001        Point::new(3, 0)..Point::new(3, 3),
28002    ];
28003
28004    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28005    let editor_1 = workspace
28006        .update_in(cx, |workspace, window, cx| {
28007            workspace.open_path(
28008                (worktree_id, rel_path("main.rs")),
28009                Some(pane_1.downgrade()),
28010                true,
28011                window,
28012                cx,
28013            )
28014        })
28015        .unwrap()
28016        .await
28017        .downcast::<Editor>()
28018        .unwrap();
28019    pane_1.update(cx, |pane, cx| {
28020        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28021        open_editor.update(cx, |editor, cx| {
28022            assert_eq!(
28023                editor.display_text(cx),
28024                main_text,
28025                "Original main.rs text on initial open",
28026            );
28027            assert_eq!(
28028                editor
28029                    .selections
28030                    .all::<Point>(&editor.display_snapshot(cx))
28031                    .into_iter()
28032                    .map(|s| s.range())
28033                    .collect::<Vec<_>>(),
28034                vec![Point::zero()..Point::zero()],
28035                "Default selections on initial open",
28036            );
28037        })
28038    });
28039    editor_1.update_in(cx, |editor, window, cx| {
28040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28041            s.select_ranges(expected_ranges.clone());
28042        });
28043    });
28044
28045    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
28046        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
28047    });
28048    let editor_2 = workspace
28049        .update_in(cx, |workspace, window, cx| {
28050            workspace.open_path(
28051                (worktree_id, rel_path("main.rs")),
28052                Some(pane_2.downgrade()),
28053                true,
28054                window,
28055                cx,
28056            )
28057        })
28058        .unwrap()
28059        .await
28060        .downcast::<Editor>()
28061        .unwrap();
28062    pane_2.update(cx, |pane, cx| {
28063        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28064        open_editor.update(cx, |editor, cx| {
28065            assert_eq!(
28066                editor.display_text(cx),
28067                main_text,
28068                "Original main.rs text on initial open in another panel",
28069            );
28070            assert_eq!(
28071                editor
28072                    .selections
28073                    .all::<Point>(&editor.display_snapshot(cx))
28074                    .into_iter()
28075                    .map(|s| s.range())
28076                    .collect::<Vec<_>>(),
28077                vec![Point::zero()..Point::zero()],
28078                "Default selections on initial open in another panel",
28079            );
28080        })
28081    });
28082
28083    editor_2.update_in(cx, |editor, window, cx| {
28084        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
28085    });
28086
28087    let _other_editor_1 = workspace
28088        .update_in(cx, |workspace, window, cx| {
28089            workspace.open_path(
28090                (worktree_id, rel_path("lib.rs")),
28091                Some(pane_1.downgrade()),
28092                true,
28093                window,
28094                cx,
28095            )
28096        })
28097        .unwrap()
28098        .await
28099        .downcast::<Editor>()
28100        .unwrap();
28101    pane_1
28102        .update_in(cx, |pane, window, cx| {
28103            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
28104        })
28105        .await
28106        .unwrap();
28107    drop(editor_1);
28108    pane_1.update(cx, |pane, cx| {
28109        pane.active_item()
28110            .unwrap()
28111            .downcast::<Editor>()
28112            .unwrap()
28113            .update(cx, |editor, cx| {
28114                assert_eq!(
28115                    editor.display_text(cx),
28116                    lib_text,
28117                    "Other file should be open and active",
28118                );
28119            });
28120        assert_eq!(pane.items().count(), 1, "No other editors should be open");
28121    });
28122
28123    let _other_editor_2 = workspace
28124        .update_in(cx, |workspace, window, cx| {
28125            workspace.open_path(
28126                (worktree_id, rel_path("lib.rs")),
28127                Some(pane_2.downgrade()),
28128                true,
28129                window,
28130                cx,
28131            )
28132        })
28133        .unwrap()
28134        .await
28135        .downcast::<Editor>()
28136        .unwrap();
28137    pane_2
28138        .update_in(cx, |pane, window, cx| {
28139            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
28140        })
28141        .await
28142        .unwrap();
28143    drop(editor_2);
28144    pane_2.update(cx, |pane, cx| {
28145        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28146        open_editor.update(cx, |editor, cx| {
28147            assert_eq!(
28148                editor.display_text(cx),
28149                lib_text,
28150                "Other file should be open and active in another panel too",
28151            );
28152        });
28153        assert_eq!(
28154            pane.items().count(),
28155            1,
28156            "No other editors should be open in another pane",
28157        );
28158    });
28159
28160    let _editor_1_reopened = workspace
28161        .update_in(cx, |workspace, window, cx| {
28162            workspace.open_path(
28163                (worktree_id, rel_path("main.rs")),
28164                Some(pane_1.downgrade()),
28165                true,
28166                window,
28167                cx,
28168            )
28169        })
28170        .unwrap()
28171        .await
28172        .downcast::<Editor>()
28173        .unwrap();
28174    let _editor_2_reopened = workspace
28175        .update_in(cx, |workspace, window, cx| {
28176            workspace.open_path(
28177                (worktree_id, rel_path("main.rs")),
28178                Some(pane_2.downgrade()),
28179                true,
28180                window,
28181                cx,
28182            )
28183        })
28184        .unwrap()
28185        .await
28186        .downcast::<Editor>()
28187        .unwrap();
28188    pane_1.update(cx, |pane, cx| {
28189        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28190        open_editor.update(cx, |editor, cx| {
28191            assert_eq!(
28192                editor.display_text(cx),
28193                main_text,
28194                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
28195            );
28196            assert_eq!(
28197                editor
28198                    .selections
28199                    .all::<Point>(&editor.display_snapshot(cx))
28200                    .into_iter()
28201                    .map(|s| s.range())
28202                    .collect::<Vec<_>>(),
28203                expected_ranges,
28204                "Previous editor in the 1st panel had selections and should get them restored on reopen",
28205            );
28206        })
28207    });
28208    pane_2.update(cx, |pane, cx| {
28209        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28210        open_editor.update(cx, |editor, cx| {
28211            assert_eq!(
28212                editor.display_text(cx),
28213                r#"fn main() {
28214⋯rintln!("1");
28215⋯intln!("2");
28216⋯ntln!("3");
28217println!("4");
28218println!("5");
28219}"#,
28220                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
28221            );
28222            assert_eq!(
28223                editor
28224                    .selections
28225                    .all::<Point>(&editor.display_snapshot(cx))
28226                    .into_iter()
28227                    .map(|s| s.range())
28228                    .collect::<Vec<_>>(),
28229                vec![Point::zero()..Point::zero()],
28230                "Previous editor in the 2nd pane had no selections changed hence should restore none",
28231            );
28232        })
28233    });
28234}
28235
28236#[gpui::test]
28237async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
28238    init_test(cx, |_| {});
28239
28240    let fs = FakeFs::new(cx.executor());
28241    let main_text = r#"fn main() {
28242println!("1");
28243println!("2");
28244println!("3");
28245println!("4");
28246println!("5");
28247}"#;
28248    let lib_text = "mod foo {}";
28249    fs.insert_tree(
28250        path!("/a"),
28251        json!({
28252            "lib.rs": lib_text,
28253            "main.rs": main_text,
28254        }),
28255    )
28256    .await;
28257
28258    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28259    let (multi_workspace, cx) =
28260        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28261    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28262    let worktree_id = workspace.update(cx, |workspace, cx| {
28263        workspace.project().update(cx, |project, cx| {
28264            project.worktrees(cx).next().unwrap().read(cx).id()
28265        })
28266    });
28267
28268    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28269    let editor = workspace
28270        .update_in(cx, |workspace, window, cx| {
28271            workspace.open_path(
28272                (worktree_id, rel_path("main.rs")),
28273                Some(pane.downgrade()),
28274                true,
28275                window,
28276                cx,
28277            )
28278        })
28279        .unwrap()
28280        .await
28281        .downcast::<Editor>()
28282        .unwrap();
28283    pane.update(cx, |pane, cx| {
28284        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28285        open_editor.update(cx, |editor, cx| {
28286            assert_eq!(
28287                editor.display_text(cx),
28288                main_text,
28289                "Original main.rs text on initial open",
28290            );
28291        })
28292    });
28293    editor.update_in(cx, |editor, window, cx| {
28294        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
28295    });
28296
28297    cx.update_global(|store: &mut SettingsStore, cx| {
28298        store.update_user_settings(cx, |s| {
28299            s.workspace.restore_on_file_reopen = Some(false);
28300        });
28301    });
28302    editor.update_in(cx, |editor, window, cx| {
28303        editor.fold_ranges(
28304            vec![
28305                Point::new(1, 0)..Point::new(1, 1),
28306                Point::new(2, 0)..Point::new(2, 2),
28307                Point::new(3, 0)..Point::new(3, 3),
28308            ],
28309            false,
28310            window,
28311            cx,
28312        );
28313    });
28314    pane.update_in(cx, |pane, window, cx| {
28315        pane.close_all_items(&CloseAllItems::default(), window, cx)
28316    })
28317    .await
28318    .unwrap();
28319    pane.update(cx, |pane, _| {
28320        assert!(pane.active_item().is_none());
28321    });
28322    cx.update_global(|store: &mut SettingsStore, cx| {
28323        store.update_user_settings(cx, |s| {
28324            s.workspace.restore_on_file_reopen = Some(true);
28325        });
28326    });
28327
28328    let _editor_reopened = workspace
28329        .update_in(cx, |workspace, window, cx| {
28330            workspace.open_path(
28331                (worktree_id, rel_path("main.rs")),
28332                Some(pane.downgrade()),
28333                true,
28334                window,
28335                cx,
28336            )
28337        })
28338        .unwrap()
28339        .await
28340        .downcast::<Editor>()
28341        .unwrap();
28342    pane.update(cx, |pane, cx| {
28343        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28344        open_editor.update(cx, |editor, cx| {
28345            assert_eq!(
28346                editor.display_text(cx),
28347                main_text,
28348                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
28349            );
28350        })
28351    });
28352}
28353
28354#[gpui::test]
28355async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
28356    struct EmptyModalView {
28357        focus_handle: gpui::FocusHandle,
28358    }
28359    impl EventEmitter<DismissEvent> for EmptyModalView {}
28360    impl Render for EmptyModalView {
28361        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
28362            div()
28363        }
28364    }
28365    impl Focusable for EmptyModalView {
28366        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
28367            self.focus_handle.clone()
28368        }
28369    }
28370    impl workspace::ModalView for EmptyModalView {}
28371    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
28372        EmptyModalView {
28373            focus_handle: cx.focus_handle(),
28374        }
28375    }
28376
28377    init_test(cx, |_| {});
28378
28379    let fs = FakeFs::new(cx.executor());
28380    let project = Project::test(fs, [], cx).await;
28381    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28382    let workspace = window
28383        .read_with(cx, |mw, _| mw.workspace().clone())
28384        .unwrap();
28385    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
28386    let cx = &mut VisualTestContext::from_window(*window, cx);
28387    let editor = cx.new_window_entity(|window, cx| {
28388        Editor::new(
28389            EditorMode::full(),
28390            buffer,
28391            Some(project.clone()),
28392            window,
28393            cx,
28394        )
28395    });
28396    workspace.update_in(cx, |workspace, window, cx| {
28397        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
28398    });
28399
28400    editor.update_in(cx, |editor, window, cx| {
28401        editor.open_context_menu(&OpenContextMenu, window, cx);
28402        assert!(editor.mouse_context_menu.is_some());
28403    });
28404    workspace.update_in(cx, |workspace, window, cx| {
28405        workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
28406    });
28407
28408    cx.read(|cx| {
28409        assert!(editor.read(cx).mouse_context_menu.is_none());
28410    });
28411}
28412
28413fn set_linked_edit_ranges(
28414    opening: (Point, Point),
28415    closing: (Point, Point),
28416    editor: &mut Editor,
28417    cx: &mut Context<Editor>,
28418) {
28419    let Some((buffer, _)) = editor
28420        .buffer
28421        .read(cx)
28422        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
28423    else {
28424        panic!("Failed to get buffer for selection position");
28425    };
28426    let buffer = buffer.read(cx);
28427    let buffer_id = buffer.remote_id();
28428    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
28429    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
28430    let mut linked_ranges = HashMap::default();
28431    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
28432    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
28433}
28434
28435#[gpui::test]
28436async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
28437    init_test(cx, |_| {});
28438
28439    let fs = FakeFs::new(cx.executor());
28440    fs.insert_file(path!("/file.html"), Default::default())
28441        .await;
28442
28443    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
28444
28445    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28446    let html_language = Arc::new(Language::new(
28447        LanguageConfig {
28448            name: "HTML".into(),
28449            matcher: LanguageMatcher {
28450                path_suffixes: vec!["html".to_string()],
28451                ..LanguageMatcher::default()
28452            },
28453            brackets: BracketPairConfig {
28454                pairs: vec![BracketPair {
28455                    start: "<".into(),
28456                    end: ">".into(),
28457                    close: true,
28458                    ..Default::default()
28459                }],
28460                ..Default::default()
28461            },
28462            ..Default::default()
28463        },
28464        Some(tree_sitter_html::LANGUAGE.into()),
28465    ));
28466    language_registry.add(html_language);
28467    let mut fake_servers = language_registry.register_fake_lsp(
28468        "HTML",
28469        FakeLspAdapter {
28470            capabilities: lsp::ServerCapabilities {
28471                completion_provider: Some(lsp::CompletionOptions {
28472                    resolve_provider: Some(true),
28473                    ..Default::default()
28474                }),
28475                ..Default::default()
28476            },
28477            ..Default::default()
28478        },
28479    );
28480
28481    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28482    let workspace = window
28483        .read_with(cx, |mw, _| mw.workspace().clone())
28484        .unwrap();
28485    let cx = &mut VisualTestContext::from_window(*window, cx);
28486
28487    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
28488        workspace.project().update(cx, |project, cx| {
28489            project.worktrees(cx).next().unwrap().read(cx).id()
28490        })
28491    });
28492
28493    project
28494        .update(cx, |project, cx| {
28495            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
28496        })
28497        .await
28498        .unwrap();
28499    let editor = workspace
28500        .update_in(cx, |workspace, window, cx| {
28501            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
28502        })
28503        .await
28504        .unwrap()
28505        .downcast::<Editor>()
28506        .unwrap();
28507
28508    let fake_server = fake_servers.next().await.unwrap();
28509    cx.run_until_parked();
28510    editor.update_in(cx, |editor, window, cx| {
28511        editor.set_text("<ad></ad>", window, cx);
28512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28513            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
28514        });
28515        set_linked_edit_ranges(
28516            (Point::new(0, 1), Point::new(0, 3)),
28517            (Point::new(0, 6), Point::new(0, 8)),
28518            editor,
28519            cx,
28520        );
28521    });
28522    let mut completion_handle =
28523        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
28524            Ok(Some(lsp::CompletionResponse::Array(vec![
28525                lsp::CompletionItem {
28526                    label: "head".to_string(),
28527                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
28528                        lsp::InsertReplaceEdit {
28529                            new_text: "head".to_string(),
28530                            insert: lsp::Range::new(
28531                                lsp::Position::new(0, 1),
28532                                lsp::Position::new(0, 3),
28533                            ),
28534                            replace: lsp::Range::new(
28535                                lsp::Position::new(0, 1),
28536                                lsp::Position::new(0, 3),
28537                            ),
28538                        },
28539                    )),
28540                    ..Default::default()
28541                },
28542            ])))
28543        });
28544    editor.update_in(cx, |editor, window, cx| {
28545        editor.show_completions(&ShowCompletions, window, cx);
28546    });
28547    cx.run_until_parked();
28548    completion_handle.next().await.unwrap();
28549    editor.update(cx, |editor, _| {
28550        assert!(
28551            editor.context_menu_visible(),
28552            "Completion menu should be visible"
28553        );
28554    });
28555    editor.update_in(cx, |editor, window, cx| {
28556        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
28557    });
28558    cx.executor().run_until_parked();
28559    editor.update(cx, |editor, cx| {
28560        assert_eq!(editor.text(cx), "<head></head>");
28561    });
28562}
28563
28564#[gpui::test]
28565async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
28566    init_test(cx, |_| {});
28567
28568    let mut cx = EditorTestContext::new(cx).await;
28569    let language = Arc::new(Language::new(
28570        LanguageConfig {
28571            name: "TSX".into(),
28572            matcher: LanguageMatcher {
28573                path_suffixes: vec!["tsx".to_string()],
28574                ..LanguageMatcher::default()
28575            },
28576            brackets: BracketPairConfig {
28577                pairs: vec![BracketPair {
28578                    start: "<".into(),
28579                    end: ">".into(),
28580                    close: true,
28581                    ..Default::default()
28582                }],
28583                ..Default::default()
28584            },
28585            linked_edit_characters: HashSet::from_iter(['.']),
28586            ..Default::default()
28587        },
28588        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
28589    ));
28590    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28591
28592    // Test typing > does not extend linked pair
28593    cx.set_state("<divˇ<div></div>");
28594    cx.update_editor(|editor, _, cx| {
28595        set_linked_edit_ranges(
28596            (Point::new(0, 1), Point::new(0, 4)),
28597            (Point::new(0, 11), Point::new(0, 14)),
28598            editor,
28599            cx,
28600        );
28601    });
28602    cx.update_editor(|editor, window, cx| {
28603        editor.handle_input(">", window, cx);
28604    });
28605    cx.assert_editor_state("<div>ˇ<div></div>");
28606
28607    // Test typing . do extend linked pair
28608    cx.set_state("<Animatedˇ></Animated>");
28609    cx.update_editor(|editor, _, cx| {
28610        set_linked_edit_ranges(
28611            (Point::new(0, 1), Point::new(0, 9)),
28612            (Point::new(0, 12), Point::new(0, 20)),
28613            editor,
28614            cx,
28615        );
28616    });
28617    cx.update_editor(|editor, window, cx| {
28618        editor.handle_input(".", window, cx);
28619    });
28620    cx.assert_editor_state("<Animated.ˇ></Animated.>");
28621    cx.update_editor(|editor, _, cx| {
28622        set_linked_edit_ranges(
28623            (Point::new(0, 1), Point::new(0, 10)),
28624            (Point::new(0, 13), Point::new(0, 21)),
28625            editor,
28626            cx,
28627        );
28628    });
28629    cx.update_editor(|editor, window, cx| {
28630        editor.handle_input("V", window, cx);
28631    });
28632    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
28633}
28634
28635#[gpui::test]
28636async fn test_linked_edits_on_typing_dot_without_language_override(cx: &mut TestAppContext) {
28637    init_test(cx, |_| {});
28638
28639    let mut cx = EditorTestContext::new(cx).await;
28640    let language = Arc::new(Language::new(
28641        LanguageConfig {
28642            name: "HTML".into(),
28643            matcher: LanguageMatcher {
28644                path_suffixes: vec!["html".to_string()],
28645                ..LanguageMatcher::default()
28646            },
28647            brackets: BracketPairConfig {
28648                pairs: vec![BracketPair {
28649                    start: "<".into(),
28650                    end: ">".into(),
28651                    close: true,
28652                    ..Default::default()
28653                }],
28654                ..Default::default()
28655            },
28656            ..Default::default()
28657        },
28658        Some(tree_sitter_html::LANGUAGE.into()),
28659    ));
28660    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28661
28662    cx.set_state("<Tableˇ></Table>");
28663    cx.update_editor(|editor, _, cx| {
28664        set_linked_edit_ranges(
28665            (Point::new(0, 1), Point::new(0, 6)),
28666            (Point::new(0, 9), Point::new(0, 14)),
28667            editor,
28668            cx,
28669        );
28670    });
28671    cx.update_editor(|editor, window, cx| {
28672        editor.handle_input(".", window, cx);
28673    });
28674    cx.assert_editor_state("<Table.ˇ></Table.>");
28675}
28676
28677#[gpui::test]
28678async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
28679    init_test(cx, |_| {});
28680
28681    let fs = FakeFs::new(cx.executor());
28682    fs.insert_tree(
28683        path!("/root"),
28684        json!({
28685            "a": {
28686                "main.rs": "fn main() {}",
28687            },
28688            "foo": {
28689                "bar": {
28690                    "external_file.rs": "pub mod external {}",
28691                }
28692            }
28693        }),
28694    )
28695    .await;
28696
28697    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
28698    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28699    language_registry.add(rust_lang());
28700    let _fake_servers = language_registry.register_fake_lsp(
28701        "Rust",
28702        FakeLspAdapter {
28703            ..FakeLspAdapter::default()
28704        },
28705    );
28706    let (multi_workspace, cx) =
28707        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28708    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28709    let worktree_id = workspace.update(cx, |workspace, cx| {
28710        workspace.project().update(cx, |project, cx| {
28711            project.worktrees(cx).next().unwrap().read(cx).id()
28712        })
28713    });
28714
28715    let assert_language_servers_count =
28716        |expected: usize, context: &str, cx: &mut VisualTestContext| {
28717            project.update(cx, |project, cx| {
28718                let current = project
28719                    .lsp_store()
28720                    .read(cx)
28721                    .as_local()
28722                    .unwrap()
28723                    .language_servers
28724                    .len();
28725                assert_eq!(expected, current, "{context}");
28726            });
28727        };
28728
28729    assert_language_servers_count(
28730        0,
28731        "No servers should be running before any file is open",
28732        cx,
28733    );
28734    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
28735    let main_editor = workspace
28736        .update_in(cx, |workspace, window, cx| {
28737            workspace.open_path(
28738                (worktree_id, rel_path("main.rs")),
28739                Some(pane.downgrade()),
28740                true,
28741                window,
28742                cx,
28743            )
28744        })
28745        .unwrap()
28746        .await
28747        .downcast::<Editor>()
28748        .unwrap();
28749    pane.update(cx, |pane, cx| {
28750        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28751        open_editor.update(cx, |editor, cx| {
28752            assert_eq!(
28753                editor.display_text(cx),
28754                "fn main() {}",
28755                "Original main.rs text on initial open",
28756            );
28757        });
28758        assert_eq!(open_editor, main_editor);
28759    });
28760    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
28761
28762    let external_editor = workspace
28763        .update_in(cx, |workspace, window, cx| {
28764            workspace.open_abs_path(
28765                PathBuf::from("/root/foo/bar/external_file.rs"),
28766                OpenOptions::default(),
28767                window,
28768                cx,
28769            )
28770        })
28771        .await
28772        .expect("opening external file")
28773        .downcast::<Editor>()
28774        .expect("downcasted external file's open element to editor");
28775    pane.update(cx, |pane, cx| {
28776        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28777        open_editor.update(cx, |editor, cx| {
28778            assert_eq!(
28779                editor.display_text(cx),
28780                "pub mod external {}",
28781                "External file is open now",
28782            );
28783        });
28784        assert_eq!(open_editor, external_editor);
28785    });
28786    assert_language_servers_count(
28787        1,
28788        "Second, external, *.rs file should join the existing server",
28789        cx,
28790    );
28791
28792    pane.update_in(cx, |pane, window, cx| {
28793        pane.close_active_item(&CloseActiveItem::default(), window, cx)
28794    })
28795    .await
28796    .unwrap();
28797    pane.update_in(cx, |pane, window, cx| {
28798        pane.navigate_backward(&Default::default(), window, cx);
28799    });
28800    cx.run_until_parked();
28801    pane.update(cx, |pane, cx| {
28802        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
28803        open_editor.update(cx, |editor, cx| {
28804            assert_eq!(
28805                editor.display_text(cx),
28806                "pub mod external {}",
28807                "External file is open now",
28808            );
28809        });
28810    });
28811    assert_language_servers_count(
28812        1,
28813        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
28814        cx,
28815    );
28816
28817    cx.update(|_, cx| {
28818        workspace::reload(cx);
28819    });
28820    assert_language_servers_count(
28821        1,
28822        "After reloading the worktree with local and external files opened, only one project should be started",
28823        cx,
28824    );
28825}
28826
28827#[gpui::test]
28828async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
28829    init_test(cx, |_| {});
28830
28831    let mut cx = EditorTestContext::new(cx).await;
28832    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
28833    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28834
28835    // test cursor move to start of each line on tab
28836    // for `if`, `elif`, `else`, `while`, `with` and `for`
28837    cx.set_state(indoc! {"
28838        def main():
28839        ˇ    for item in items:
28840        ˇ        while item.active:
28841        ˇ            if item.value > 10:
28842        ˇ                continue
28843        ˇ            elif item.value < 0:
28844        ˇ                break
28845        ˇ            else:
28846        ˇ                with item.context() as ctx:
28847        ˇ                    yield count
28848        ˇ        else:
28849        ˇ            log('while else')
28850        ˇ    else:
28851        ˇ        log('for else')
28852    "});
28853    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28854    cx.wait_for_autoindent_applied().await;
28855    cx.assert_editor_state(indoc! {"
28856        def main():
28857            ˇfor item in items:
28858                ˇwhile item.active:
28859                    ˇif item.value > 10:
28860                        ˇcontinue
28861                    ˇelif item.value < 0:
28862                        ˇbreak
28863                    ˇelse:
28864                        ˇwith item.context() as ctx:
28865                            ˇyield count
28866                ˇelse:
28867                    ˇlog('while else')
28868            ˇelse:
28869                ˇlog('for else')
28870    "});
28871    // test relative indent is preserved when tab
28872    // for `if`, `elif`, `else`, `while`, `with` and `for`
28873    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28874    cx.wait_for_autoindent_applied().await;
28875    cx.assert_editor_state(indoc! {"
28876        def main():
28877                ˇfor item in items:
28878                    ˇwhile item.active:
28879                        ˇif item.value > 10:
28880                            ˇcontinue
28881                        ˇelif item.value < 0:
28882                            ˇbreak
28883                        ˇelse:
28884                            ˇwith item.context() as ctx:
28885                                ˇyield count
28886                    ˇelse:
28887                        ˇlog('while else')
28888                ˇelse:
28889                    ˇlog('for else')
28890    "});
28891
28892    // test cursor move to start of each line on tab
28893    // for `try`, `except`, `else`, `finally`, `match` and `def`
28894    cx.set_state(indoc! {"
28895        def main():
28896        ˇ    try:
28897        ˇ        fetch()
28898        ˇ    except ValueError:
28899        ˇ        handle_error()
28900        ˇ    else:
28901        ˇ        match value:
28902        ˇ            case _:
28903        ˇ    finally:
28904        ˇ        def status():
28905        ˇ            return 0
28906    "});
28907    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28908    cx.wait_for_autoindent_applied().await;
28909    cx.assert_editor_state(indoc! {"
28910        def main():
28911            ˇtry:
28912                ˇfetch()
28913            ˇexcept ValueError:
28914                ˇhandle_error()
28915            ˇelse:
28916                ˇmatch value:
28917                    ˇcase _:
28918            ˇfinally:
28919                ˇdef status():
28920                    ˇreturn 0
28921    "});
28922    // test relative indent is preserved when tab
28923    // for `try`, `except`, `else`, `finally`, `match` and `def`
28924    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28925    cx.wait_for_autoindent_applied().await;
28926    cx.assert_editor_state(indoc! {"
28927        def main():
28928                ˇtry:
28929                    ˇfetch()
28930                ˇexcept ValueError:
28931                    ˇhandle_error()
28932                ˇelse:
28933                    ˇmatch value:
28934                        ˇcase _:
28935                ˇfinally:
28936                    ˇdef status():
28937                        ˇreturn 0
28938    "});
28939}
28940
28941#[gpui::test]
28942async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
28943    init_test(cx, |_| {});
28944
28945    let mut cx = EditorTestContext::new(cx).await;
28946    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
28947    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28948
28949    // test `else` auto outdents when typed inside `if` block
28950    cx.set_state(indoc! {"
28951        def main():
28952            if i == 2:
28953                return
28954                ˇ
28955    "});
28956    cx.update_editor(|editor, window, cx| {
28957        editor.handle_input("else:", window, cx);
28958    });
28959    cx.wait_for_autoindent_applied().await;
28960    cx.assert_editor_state(indoc! {"
28961        def main():
28962            if i == 2:
28963                return
28964            else:ˇ
28965    "});
28966
28967    // test `except` auto outdents when typed inside `try` block
28968    cx.set_state(indoc! {"
28969        def main():
28970            try:
28971                i = 2
28972                ˇ
28973    "});
28974    cx.update_editor(|editor, window, cx| {
28975        editor.handle_input("except:", window, cx);
28976    });
28977    cx.wait_for_autoindent_applied().await;
28978    cx.assert_editor_state(indoc! {"
28979        def main():
28980            try:
28981                i = 2
28982            except:ˇ
28983    "});
28984
28985    // test `else` auto outdents when typed inside `except` block
28986    cx.set_state(indoc! {"
28987        def main():
28988            try:
28989                i = 2
28990            except:
28991                j = 2
28992                ˇ
28993    "});
28994    cx.update_editor(|editor, window, cx| {
28995        editor.handle_input("else:", window, cx);
28996    });
28997    cx.wait_for_autoindent_applied().await;
28998    cx.assert_editor_state(indoc! {"
28999        def main():
29000            try:
29001                i = 2
29002            except:
29003                j = 2
29004            else:ˇ
29005    "});
29006
29007    // test `finally` auto outdents when typed inside `else` block
29008    cx.set_state(indoc! {"
29009        def main():
29010            try:
29011                i = 2
29012            except:
29013                j = 2
29014            else:
29015                k = 2
29016                ˇ
29017    "});
29018    cx.update_editor(|editor, window, cx| {
29019        editor.handle_input("finally:", window, cx);
29020    });
29021    cx.wait_for_autoindent_applied().await;
29022    cx.assert_editor_state(indoc! {"
29023        def main():
29024            try:
29025                i = 2
29026            except:
29027                j = 2
29028            else:
29029                k = 2
29030            finally:ˇ
29031    "});
29032
29033    // test `else` does not outdents when typed inside `except` block right after for block
29034    cx.set_state(indoc! {"
29035        def main():
29036            try:
29037                i = 2
29038            except:
29039                for i in range(n):
29040                    pass
29041                ˇ
29042    "});
29043    cx.update_editor(|editor, window, cx| {
29044        editor.handle_input("else:", window, cx);
29045    });
29046    cx.wait_for_autoindent_applied().await;
29047    cx.assert_editor_state(indoc! {"
29048        def main():
29049            try:
29050                i = 2
29051            except:
29052                for i in range(n):
29053                    pass
29054                else:ˇ
29055    "});
29056
29057    // test `finally` auto outdents when typed inside `else` block right after for block
29058    cx.set_state(indoc! {"
29059        def main():
29060            try:
29061                i = 2
29062            except:
29063                j = 2
29064            else:
29065                for i in range(n):
29066                    pass
29067                ˇ
29068    "});
29069    cx.update_editor(|editor, window, cx| {
29070        editor.handle_input("finally:", window, cx);
29071    });
29072    cx.wait_for_autoindent_applied().await;
29073    cx.assert_editor_state(indoc! {"
29074        def main():
29075            try:
29076                i = 2
29077            except:
29078                j = 2
29079            else:
29080                for i in range(n):
29081                    pass
29082            finally:ˇ
29083    "});
29084
29085    // test `except` outdents to inner "try" block
29086    cx.set_state(indoc! {"
29087        def main():
29088            try:
29089                i = 2
29090                if i == 2:
29091                    try:
29092                        i = 3
29093                        ˇ
29094    "});
29095    cx.update_editor(|editor, window, cx| {
29096        editor.handle_input("except:", window, cx);
29097    });
29098    cx.wait_for_autoindent_applied().await;
29099    cx.assert_editor_state(indoc! {"
29100        def main():
29101            try:
29102                i = 2
29103                if i == 2:
29104                    try:
29105                        i = 3
29106                    except:ˇ
29107    "});
29108
29109    // test `except` outdents to outer "try" block
29110    cx.set_state(indoc! {"
29111        def main():
29112            try:
29113                i = 2
29114                if i == 2:
29115                    try:
29116                        i = 3
29117                ˇ
29118    "});
29119    cx.update_editor(|editor, window, cx| {
29120        editor.handle_input("except:", window, cx);
29121    });
29122    cx.wait_for_autoindent_applied().await;
29123    cx.assert_editor_state(indoc! {"
29124        def main():
29125            try:
29126                i = 2
29127                if i == 2:
29128                    try:
29129                        i = 3
29130            except:ˇ
29131    "});
29132
29133    // test `else` stays at correct indent when typed after `for` block
29134    cx.set_state(indoc! {"
29135        def main():
29136            for i in range(10):
29137                if i == 3:
29138                    break
29139            ˇ
29140    "});
29141    cx.update_editor(|editor, window, cx| {
29142        editor.handle_input("else:", window, cx);
29143    });
29144    cx.wait_for_autoindent_applied().await;
29145    cx.assert_editor_state(indoc! {"
29146        def main():
29147            for i in range(10):
29148                if i == 3:
29149                    break
29150            else:ˇ
29151    "});
29152
29153    // test does not outdent on typing after line with square brackets
29154    cx.set_state(indoc! {"
29155        def f() -> list[str]:
29156            ˇ
29157    "});
29158    cx.update_editor(|editor, window, cx| {
29159        editor.handle_input("a", window, cx);
29160    });
29161    cx.wait_for_autoindent_applied().await;
29162    cx.assert_editor_state(indoc! {"
29163        def f() -> list[str]:
2916429165    "});
29166
29167    // test does not outdent on typing : after case keyword
29168    cx.set_state(indoc! {"
29169        match 1:
29170            caseˇ
29171    "});
29172    cx.update_editor(|editor, window, cx| {
29173        editor.handle_input(":", window, cx);
29174    });
29175    cx.wait_for_autoindent_applied().await;
29176    cx.assert_editor_state(indoc! {"
29177        match 1:
29178            case:ˇ
29179    "});
29180}
29181
29182#[gpui::test]
29183async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
29184    init_test(cx, |_| {});
29185    update_test_language_settings(cx, &|settings| {
29186        settings.defaults.extend_comment_on_newline = Some(false);
29187    });
29188    let mut cx = EditorTestContext::new(cx).await;
29189    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
29190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29191
29192    // test correct indent after newline on comment
29193    cx.set_state(indoc! {"
29194        # COMMENT:ˇ
29195    "});
29196    cx.update_editor(|editor, window, cx| {
29197        editor.newline(&Newline, window, cx);
29198    });
29199    cx.wait_for_autoindent_applied().await;
29200    cx.assert_editor_state(indoc! {"
29201        # COMMENT:
29202        ˇ
29203    "});
29204
29205    // test correct indent after newline in brackets
29206    cx.set_state(indoc! {"
29207        {ˇ}
29208    "});
29209    cx.update_editor(|editor, window, cx| {
29210        editor.newline(&Newline, window, cx);
29211    });
29212    cx.wait_for_autoindent_applied().await;
29213    cx.assert_editor_state(indoc! {"
29214        {
29215            ˇ
29216        }
29217    "});
29218
29219    cx.set_state(indoc! {"
29220        (ˇ)
29221    "});
29222    cx.update_editor(|editor, window, cx| {
29223        editor.newline(&Newline, window, cx);
29224    });
29225    cx.run_until_parked();
29226    cx.assert_editor_state(indoc! {"
29227        (
29228            ˇ
29229        )
29230    "});
29231
29232    // do not indent after empty lists or dictionaries
29233    cx.set_state(indoc! {"
29234        a = []ˇ
29235    "});
29236    cx.update_editor(|editor, window, cx| {
29237        editor.newline(&Newline, window, cx);
29238    });
29239    cx.run_until_parked();
29240    cx.assert_editor_state(indoc! {"
29241        a = []
29242        ˇ
29243    "});
29244}
29245
29246#[gpui::test]
29247async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
29248    init_test(cx, |_| {});
29249
29250    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
29251    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
29252    language_registry.add(markdown_lang());
29253    language_registry.add(python_lang);
29254
29255    let mut cx = EditorTestContext::new(cx).await;
29256    cx.update_buffer(|buffer, cx| {
29257        buffer.set_language_registry(language_registry);
29258        buffer.set_language(Some(markdown_lang()), cx);
29259    });
29260
29261    // Test that `else:` correctly outdents to match `if:` inside the Python code block
29262    cx.set_state(indoc! {"
29263        # Heading
29264
29265        ```python
29266        def main():
29267            if condition:
29268                pass
29269                ˇ
29270        ```
29271    "});
29272    cx.update_editor(|editor, window, cx| {
29273        editor.handle_input("else:", window, cx);
29274    });
29275    cx.run_until_parked();
29276    cx.assert_editor_state(indoc! {"
29277        # Heading
29278
29279        ```python
29280        def main():
29281            if condition:
29282                pass
29283            else:ˇ
29284        ```
29285    "});
29286}
29287
29288#[gpui::test]
29289async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
29290    init_test(cx, |_| {});
29291
29292    let mut cx = EditorTestContext::new(cx).await;
29293    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29294    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29295
29296    // test cursor move to start of each line on tab
29297    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
29298    cx.set_state(indoc! {"
29299        function main() {
29300        ˇ    for item in $items; do
29301        ˇ        while [ -n \"$item\" ]; do
29302        ˇ            if [ \"$value\" -gt 10 ]; then
29303        ˇ                continue
29304        ˇ            elif [ \"$value\" -lt 0 ]; then
29305        ˇ                break
29306        ˇ            else
29307        ˇ                echo \"$item\"
29308        ˇ            fi
29309        ˇ        done
29310        ˇ    done
29311        ˇ}
29312    "});
29313    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29314    cx.wait_for_autoindent_applied().await;
29315    cx.assert_editor_state(indoc! {"
29316        function main() {
29317            ˇfor item in $items; do
29318                ˇwhile [ -n \"$item\" ]; do
29319                    ˇif [ \"$value\" -gt 10 ]; then
29320                        ˇcontinue
29321                    ˇelif [ \"$value\" -lt 0 ]; then
29322                        ˇbreak
29323                    ˇelse
29324                        ˇecho \"$item\"
29325                    ˇfi
29326                ˇdone
29327            ˇdone
29328        ˇ}
29329    "});
29330    // test relative indent is preserved when tab
29331    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29332    cx.wait_for_autoindent_applied().await;
29333    cx.assert_editor_state(indoc! {"
29334        function main() {
29335                ˇfor item in $items; do
29336                    ˇwhile [ -n \"$item\" ]; do
29337                        ˇif [ \"$value\" -gt 10 ]; then
29338                            ˇcontinue
29339                        ˇelif [ \"$value\" -lt 0 ]; then
29340                            ˇbreak
29341                        ˇelse
29342                            ˇecho \"$item\"
29343                        ˇfi
29344                    ˇdone
29345                ˇdone
29346            ˇ}
29347    "});
29348
29349    // test cursor move to start of each line on tab
29350    // for `case` statement with patterns
29351    cx.set_state(indoc! {"
29352        function handle() {
29353        ˇ    case \"$1\" in
29354        ˇ        start)
29355        ˇ            echo \"a\"
29356        ˇ            ;;
29357        ˇ        stop)
29358        ˇ            echo \"b\"
29359        ˇ            ;;
29360        ˇ        *)
29361        ˇ            echo \"c\"
29362        ˇ            ;;
29363        ˇ    esac
29364        ˇ}
29365    "});
29366    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29367    cx.wait_for_autoindent_applied().await;
29368    cx.assert_editor_state(indoc! {"
29369        function handle() {
29370            ˇcase \"$1\" in
29371                ˇstart)
29372                    ˇecho \"a\"
29373                    ˇ;;
29374                ˇstop)
29375                    ˇecho \"b\"
29376                    ˇ;;
29377                ˇ*)
29378                    ˇecho \"c\"
29379                    ˇ;;
29380            ˇesac
29381        ˇ}
29382    "});
29383}
29384
29385#[gpui::test]
29386async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
29387    init_test(cx, |_| {});
29388
29389    let mut cx = EditorTestContext::new(cx).await;
29390    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29391    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29392
29393    // test indents on comment insert
29394    cx.set_state(indoc! {"
29395        function main() {
29396        ˇ    for item in $items; do
29397        ˇ        while [ -n \"$item\" ]; do
29398        ˇ            if [ \"$value\" -gt 10 ]; then
29399        ˇ                continue
29400        ˇ            elif [ \"$value\" -lt 0 ]; then
29401        ˇ                break
29402        ˇ            else
29403        ˇ                echo \"$item\"
29404        ˇ            fi
29405        ˇ        done
29406        ˇ    done
29407        ˇ}
29408    "});
29409    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
29410    cx.wait_for_autoindent_applied().await;
29411    cx.assert_editor_state(indoc! {"
29412        function main() {
29413        #ˇ    for item in $items; do
29414        #ˇ        while [ -n \"$item\" ]; do
29415        #ˇ            if [ \"$value\" -gt 10 ]; then
29416        #ˇ                continue
29417        #ˇ            elif [ \"$value\" -lt 0 ]; then
29418        #ˇ                break
29419        #ˇ            else
29420        #ˇ                echo \"$item\"
29421        #ˇ            fi
29422        #ˇ        done
29423        #ˇ    done
29424        #ˇ}
29425    "});
29426}
29427
29428#[gpui::test]
29429async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
29430    init_test(cx, |_| {});
29431
29432    let mut cx = EditorTestContext::new(cx).await;
29433    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29434    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29435
29436    // test `else` auto outdents when typed inside `if` block
29437    cx.set_state(indoc! {"
29438        if [ \"$1\" = \"test\" ]; then
29439            echo \"foo bar\"
29440            ˇ
29441    "});
29442    cx.update_editor(|editor, window, cx| {
29443        editor.handle_input("else", window, cx);
29444    });
29445    cx.wait_for_autoindent_applied().await;
29446    cx.assert_editor_state(indoc! {"
29447        if [ \"$1\" = \"test\" ]; then
29448            echo \"foo bar\"
29449        elseˇ
29450    "});
29451
29452    // test `elif` auto outdents when typed inside `if` block
29453    cx.set_state(indoc! {"
29454        if [ \"$1\" = \"test\" ]; then
29455            echo \"foo bar\"
29456            ˇ
29457    "});
29458    cx.update_editor(|editor, window, cx| {
29459        editor.handle_input("elif", window, cx);
29460    });
29461    cx.wait_for_autoindent_applied().await;
29462    cx.assert_editor_state(indoc! {"
29463        if [ \"$1\" = \"test\" ]; then
29464            echo \"foo bar\"
29465        elifˇ
29466    "});
29467
29468    // test `fi` auto outdents when typed inside `else` block
29469    cx.set_state(indoc! {"
29470        if [ \"$1\" = \"test\" ]; then
29471            echo \"foo bar\"
29472        else
29473            echo \"bar baz\"
29474            ˇ
29475    "});
29476    cx.update_editor(|editor, window, cx| {
29477        editor.handle_input("fi", window, cx);
29478    });
29479    cx.wait_for_autoindent_applied().await;
29480    cx.assert_editor_state(indoc! {"
29481        if [ \"$1\" = \"test\" ]; then
29482            echo \"foo bar\"
29483        else
29484            echo \"bar baz\"
29485        fiˇ
29486    "});
29487
29488    // test `done` auto outdents when typed inside `while` block
29489    cx.set_state(indoc! {"
29490        while read line; do
29491            echo \"$line\"
29492            ˇ
29493    "});
29494    cx.update_editor(|editor, window, cx| {
29495        editor.handle_input("done", window, cx);
29496    });
29497    cx.wait_for_autoindent_applied().await;
29498    cx.assert_editor_state(indoc! {"
29499        while read line; do
29500            echo \"$line\"
29501        doneˇ
29502    "});
29503
29504    // test `done` auto outdents when typed inside `for` block
29505    cx.set_state(indoc! {"
29506        for file in *.txt; do
29507            cat \"$file\"
29508            ˇ
29509    "});
29510    cx.update_editor(|editor, window, cx| {
29511        editor.handle_input("done", window, cx);
29512    });
29513    cx.wait_for_autoindent_applied().await;
29514    cx.assert_editor_state(indoc! {"
29515        for file in *.txt; do
29516            cat \"$file\"
29517        doneˇ
29518    "});
29519
29520    // test `esac` auto outdents when typed inside `case` block
29521    cx.set_state(indoc! {"
29522        case \"$1\" in
29523            start)
29524                echo \"foo bar\"
29525                ;;
29526            stop)
29527                echo \"bar baz\"
29528                ;;
29529            ˇ
29530    "});
29531    cx.update_editor(|editor, window, cx| {
29532        editor.handle_input("esac", window, cx);
29533    });
29534    cx.wait_for_autoindent_applied().await;
29535    cx.assert_editor_state(indoc! {"
29536        case \"$1\" in
29537            start)
29538                echo \"foo bar\"
29539                ;;
29540            stop)
29541                echo \"bar baz\"
29542                ;;
29543        esacˇ
29544    "});
29545
29546    // test `*)` auto outdents when typed inside `case` block
29547    cx.set_state(indoc! {"
29548        case \"$1\" in
29549            start)
29550                echo \"foo bar\"
29551                ;;
29552                ˇ
29553    "});
29554    cx.update_editor(|editor, window, cx| {
29555        editor.handle_input("*)", window, cx);
29556    });
29557    cx.wait_for_autoindent_applied().await;
29558    cx.assert_editor_state(indoc! {"
29559        case \"$1\" in
29560            start)
29561                echo \"foo bar\"
29562                ;;
29563            *)ˇ
29564    "});
29565
29566    // test `fi` outdents to correct level with nested if blocks
29567    cx.set_state(indoc! {"
29568        if [ \"$1\" = \"test\" ]; then
29569            echo \"outer if\"
29570            if [ \"$2\" = \"debug\" ]; then
29571                echo \"inner if\"
29572                ˇ
29573    "});
29574    cx.update_editor(|editor, window, cx| {
29575        editor.handle_input("fi", window, cx);
29576    });
29577    cx.wait_for_autoindent_applied().await;
29578    cx.assert_editor_state(indoc! {"
29579        if [ \"$1\" = \"test\" ]; then
29580            echo \"outer if\"
29581            if [ \"$2\" = \"debug\" ]; then
29582                echo \"inner if\"
29583            fiˇ
29584    "});
29585}
29586
29587#[gpui::test]
29588async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
29589    init_test(cx, |_| {});
29590    update_test_language_settings(cx, &|settings| {
29591        settings.defaults.extend_comment_on_newline = Some(false);
29592    });
29593    let mut cx = EditorTestContext::new(cx).await;
29594    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
29595    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
29596
29597    // test correct indent after newline on comment
29598    cx.set_state(indoc! {"
29599        # COMMENT:ˇ
29600    "});
29601    cx.update_editor(|editor, window, cx| {
29602        editor.newline(&Newline, window, cx);
29603    });
29604    cx.wait_for_autoindent_applied().await;
29605    cx.assert_editor_state(indoc! {"
29606        # COMMENT:
29607        ˇ
29608    "});
29609
29610    // test correct indent after newline after `then`
29611    cx.set_state(indoc! {"
29612
29613        if [ \"$1\" = \"test\" ]; thenˇ
29614    "});
29615    cx.update_editor(|editor, window, cx| {
29616        editor.newline(&Newline, window, cx);
29617    });
29618    cx.wait_for_autoindent_applied().await;
29619    cx.assert_editor_state(indoc! {"
29620
29621        if [ \"$1\" = \"test\" ]; then
29622            ˇ
29623    "});
29624
29625    // test correct indent after newline after `else`
29626    cx.set_state(indoc! {"
29627        if [ \"$1\" = \"test\" ]; then
29628        elseˇ
29629    "});
29630    cx.update_editor(|editor, window, cx| {
29631        editor.newline(&Newline, window, cx);
29632    });
29633    cx.wait_for_autoindent_applied().await;
29634    cx.assert_editor_state(indoc! {"
29635        if [ \"$1\" = \"test\" ]; then
29636        else
29637            ˇ
29638    "});
29639
29640    // test correct indent after newline after `elif`
29641    cx.set_state(indoc! {"
29642        if [ \"$1\" = \"test\" ]; then
29643        elifˇ
29644    "});
29645    cx.update_editor(|editor, window, cx| {
29646        editor.newline(&Newline, window, cx);
29647    });
29648    cx.wait_for_autoindent_applied().await;
29649    cx.assert_editor_state(indoc! {"
29650        if [ \"$1\" = \"test\" ]; then
29651        elif
29652            ˇ
29653    "});
29654
29655    // test correct indent after newline after `do`
29656    cx.set_state(indoc! {"
29657        for file in *.txt; doˇ
29658    "});
29659    cx.update_editor(|editor, window, cx| {
29660        editor.newline(&Newline, window, cx);
29661    });
29662    cx.wait_for_autoindent_applied().await;
29663    cx.assert_editor_state(indoc! {"
29664        for file in *.txt; do
29665            ˇ
29666    "});
29667
29668    // test correct indent after newline after case pattern
29669    cx.set_state(indoc! {"
29670        case \"$1\" in
29671            start)ˇ
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        case \"$1\" in
29679            start)
29680                ˇ
29681    "});
29682
29683    // test correct indent after newline after case pattern
29684    cx.set_state(indoc! {"
29685        case \"$1\" in
29686            start)
29687                ;;
29688            *)ˇ
29689    "});
29690    cx.update_editor(|editor, window, cx| {
29691        editor.newline(&Newline, window, cx);
29692    });
29693    cx.wait_for_autoindent_applied().await;
29694    cx.assert_editor_state(indoc! {"
29695        case \"$1\" in
29696            start)
29697                ;;
29698            *)
29699                ˇ
29700    "});
29701
29702    // test correct indent after newline after function opening brace
29703    cx.set_state(indoc! {"
29704        function test() {ˇ}
29705    "});
29706    cx.update_editor(|editor, window, cx| {
29707        editor.newline(&Newline, window, cx);
29708    });
29709    cx.wait_for_autoindent_applied().await;
29710    cx.assert_editor_state(indoc! {"
29711        function test() {
29712            ˇ
29713        }
29714    "});
29715
29716    // test no extra indent after semicolon on same line
29717    cx.set_state(indoc! {"
29718        echo \"test\"29719    "});
29720    cx.update_editor(|editor, window, cx| {
29721        editor.newline(&Newline, window, cx);
29722    });
29723    cx.wait_for_autoindent_applied().await;
29724    cx.assert_editor_state(indoc! {"
29725        echo \"test\";
29726        ˇ
29727    "});
29728}
29729
29730fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
29731    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
29732    point..point
29733}
29734
29735#[track_caller]
29736fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
29737    let (text, ranges) = marked_text_ranges(marked_text, true);
29738    assert_eq!(editor.text(cx), text);
29739    assert_eq!(
29740        editor.selections.ranges(&editor.display_snapshot(cx)),
29741        ranges
29742            .iter()
29743            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
29744            .collect::<Vec<_>>(),
29745        "Assert selections are {}",
29746        marked_text
29747    );
29748}
29749
29750pub fn handle_signature_help_request(
29751    cx: &mut EditorLspTestContext,
29752    mocked_response: lsp::SignatureHelp,
29753) -> impl Future<Output = ()> + use<> {
29754    let mut request =
29755        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
29756            let mocked_response = mocked_response.clone();
29757            async move { Ok(Some(mocked_response)) }
29758        });
29759
29760    async move {
29761        request.next().await;
29762    }
29763}
29764
29765#[track_caller]
29766pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
29767    cx.update_editor(|editor, _, _| {
29768        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
29769            let entries = menu.entries.borrow();
29770            let entries = entries
29771                .iter()
29772                .map(|entry| entry.string.as_str())
29773                .collect::<Vec<_>>();
29774            assert_eq!(entries, expected);
29775        } else {
29776            panic!("Expected completions menu");
29777        }
29778    });
29779}
29780
29781#[gpui::test]
29782async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
29783    init_test(cx, |_| {});
29784    let mut cx = EditorLspTestContext::new_rust(
29785        lsp::ServerCapabilities {
29786            completion_provider: Some(lsp::CompletionOptions {
29787                ..Default::default()
29788            }),
29789            ..Default::default()
29790        },
29791        cx,
29792    )
29793    .await;
29794    cx.lsp
29795        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
29796            Ok(Some(lsp::CompletionResponse::Array(vec![
29797                lsp::CompletionItem {
29798                    label: "unsafe".into(),
29799                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
29800                        range: lsp::Range {
29801                            start: lsp::Position {
29802                                line: 0,
29803                                character: 9,
29804                            },
29805                            end: lsp::Position {
29806                                line: 0,
29807                                character: 11,
29808                            },
29809                        },
29810                        new_text: "unsafe".to_string(),
29811                    })),
29812                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
29813                    ..Default::default()
29814                },
29815            ])))
29816        });
29817
29818    cx.update_editor(|editor, _, cx| {
29819        editor.project().unwrap().update(cx, |project, cx| {
29820            project.snippets().update(cx, |snippets, _cx| {
29821                snippets.add_snippet_for_test(
29822                    None,
29823                    PathBuf::from("test_snippets.json"),
29824                    vec![
29825                        Arc::new(project::snippet_provider::Snippet {
29826                            prefix: vec![
29827                                "unlimited word count".to_string(),
29828                                "unlimit word count".to_string(),
29829                                "unlimited unknown".to_string(),
29830                            ],
29831                            body: "this is many words".to_string(),
29832                            description: Some("description".to_string()),
29833                            name: "multi-word snippet test".to_string(),
29834                        }),
29835                        Arc::new(project::snippet_provider::Snippet {
29836                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
29837                            body: "fewer words".to_string(),
29838                            description: Some("alt description".to_string()),
29839                            name: "other name".to_string(),
29840                        }),
29841                        Arc::new(project::snippet_provider::Snippet {
29842                            prefix: vec!["ab aa".to_string()],
29843                            body: "abcd".to_string(),
29844                            description: None,
29845                            name: "alphabet".to_string(),
29846                        }),
29847                    ],
29848                );
29849            });
29850        })
29851    });
29852
29853    let get_completions = |cx: &mut EditorLspTestContext| {
29854        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
29855            Some(CodeContextMenu::Completions(context_menu)) => {
29856                let entries = context_menu.entries.borrow();
29857                entries
29858                    .iter()
29859                    .map(|entry| entry.string.clone())
29860                    .collect_vec()
29861            }
29862            _ => vec![],
29863        })
29864    };
29865
29866    // snippets:
29867    //  @foo
29868    //  foo bar
29869    //
29870    // when typing:
29871    //
29872    // when typing:
29873    //  - if I type a symbol "open the completions with snippets only"
29874    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
29875    //
29876    // stuff we need:
29877    //  - filtering logic change?
29878    //  - remember how far back the completion started.
29879
29880    let test_cases: &[(&str, &[&str])] = &[
29881        (
29882            "un",
29883            &[
29884                "unsafe",
29885                "unlimit word count",
29886                "unlimited unknown",
29887                "unlimited word count",
29888                "unsnip",
29889            ],
29890        ),
29891        (
29892            "u ",
29893            &[
29894                "unlimit word count",
29895                "unlimited unknown",
29896                "unlimited word count",
29897            ],
29898        ),
29899        ("u a", &["ab aa", "unsafe"]), // unsAfe
29900        (
29901            "u u",
29902            &[
29903                "unsafe",
29904                "unlimit word count",
29905                "unlimited unknown", // ranked highest among snippets
29906                "unlimited word count",
29907                "unsnip",
29908            ],
29909        ),
29910        ("uw c", &["unlimit word count", "unlimited word count"]),
29911        (
29912            "u w",
29913            &[
29914                "unlimit word count",
29915                "unlimited word count",
29916                "unlimited unknown",
29917            ],
29918        ),
29919        ("u w ", &["unlimit word count", "unlimited word count"]),
29920        (
29921            "u ",
29922            &[
29923                "unlimit word count",
29924                "unlimited unknown",
29925                "unlimited word count",
29926            ],
29927        ),
29928        ("wor", &[]),
29929        ("uf", &["unsafe"]),
29930        ("af", &["unsafe"]),
29931        ("afu", &[]),
29932        (
29933            "ue",
29934            &["unsafe", "unlimited unknown", "unlimited word count"],
29935        ),
29936        ("@", &["@few"]),
29937        ("@few", &["@few"]),
29938        ("@ ", &[]),
29939        ("a@", &["@few"]),
29940        ("a@f", &["@few", "unsafe"]),
29941        ("a@fw", &["@few"]),
29942        ("a", &["ab aa", "unsafe"]),
29943        ("aa", &["ab aa"]),
29944        ("aaa", &["ab aa"]),
29945        ("ab", &["ab aa"]),
29946        ("ab ", &["ab aa"]),
29947        ("ab a", &["ab aa", "unsafe"]),
29948        ("ab ab", &["ab aa"]),
29949        ("ab ab aa", &["ab aa"]),
29950    ];
29951
29952    for &(input_to_simulate, expected_completions) in test_cases {
29953        cx.set_state("fn a() { ˇ }\n");
29954        for c in input_to_simulate.split("") {
29955            cx.simulate_input(c);
29956            cx.run_until_parked();
29957        }
29958        let expected_completions = expected_completions
29959            .iter()
29960            .map(|s| s.to_string())
29961            .collect_vec();
29962        assert_eq!(
29963            get_completions(&mut cx),
29964            expected_completions,
29965            "< actual / expected >, input = {input_to_simulate:?}",
29966        );
29967    }
29968}
29969
29970/// Handle completion request passing a marked string specifying where the completion
29971/// should be triggered from using '|' character, what range should be replaced, and what completions
29972/// should be returned using '<' and '>' to delimit the range.
29973///
29974/// Also see `handle_completion_request_with_insert_and_replace`.
29975#[track_caller]
29976pub fn handle_completion_request(
29977    marked_string: &str,
29978    completions: Vec<&'static str>,
29979    is_incomplete: bool,
29980    counter: Arc<AtomicUsize>,
29981    cx: &mut EditorLspTestContext,
29982) -> impl Future<Output = ()> {
29983    let complete_from_marker: TextRangeMarker = '|'.into();
29984    let replace_range_marker: TextRangeMarker = ('<', '>').into();
29985    let (_, mut marked_ranges) = marked_text_ranges_by(
29986        marked_string,
29987        vec![complete_from_marker.clone(), replace_range_marker.clone()],
29988    );
29989
29990    let complete_from_position = cx.to_lsp(MultiBufferOffset(
29991        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
29992    ));
29993    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
29994    let replace_range =
29995        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
29996
29997    let mut request =
29998        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
29999            let completions = completions.clone();
30000            counter.fetch_add(1, atomic::Ordering::Release);
30001            async move {
30002                assert_eq!(params.text_document_position.text_document.uri, url.clone());
30003                assert_eq!(
30004                    params.text_document_position.position,
30005                    complete_from_position
30006                );
30007                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
30008                    is_incomplete,
30009                    item_defaults: None,
30010                    items: completions
30011                        .iter()
30012                        .map(|completion_text| lsp::CompletionItem {
30013                            label: completion_text.to_string(),
30014                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
30015                                range: replace_range,
30016                                new_text: completion_text.to_string(),
30017                            })),
30018                            ..Default::default()
30019                        })
30020                        .collect(),
30021                })))
30022            }
30023        });
30024
30025    async move {
30026        request.next().await;
30027    }
30028}
30029
30030/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
30031/// given instead, which also contains an `insert` range.
30032///
30033/// This function uses markers to define ranges:
30034/// - `|` marks the cursor position
30035/// - `<>` marks the replace range
30036/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
30037pub fn handle_completion_request_with_insert_and_replace(
30038    cx: &mut EditorLspTestContext,
30039    marked_string: &str,
30040    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
30041    counter: Arc<AtomicUsize>,
30042) -> impl Future<Output = ()> {
30043    let complete_from_marker: TextRangeMarker = '|'.into();
30044    let replace_range_marker: TextRangeMarker = ('<', '>').into();
30045    let insert_range_marker: TextRangeMarker = ('{', '}').into();
30046
30047    let (_, mut marked_ranges) = marked_text_ranges_by(
30048        marked_string,
30049        vec![
30050            complete_from_marker.clone(),
30051            replace_range_marker.clone(),
30052            insert_range_marker.clone(),
30053        ],
30054    );
30055
30056    let complete_from_position = cx.to_lsp(MultiBufferOffset(
30057        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
30058    ));
30059    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
30060    let replace_range =
30061        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
30062
30063    let insert_range = match marked_ranges.remove(&insert_range_marker) {
30064        Some(ranges) if !ranges.is_empty() => {
30065            let range1 = ranges[0].clone();
30066            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
30067        }
30068        _ => lsp::Range {
30069            start: replace_range.start,
30070            end: complete_from_position,
30071        },
30072    };
30073
30074    let mut request =
30075        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
30076            let completions = completions.clone();
30077            counter.fetch_add(1, atomic::Ordering::Release);
30078            async move {
30079                assert_eq!(params.text_document_position.text_document.uri, url.clone());
30080                assert_eq!(
30081                    params.text_document_position.position, complete_from_position,
30082                    "marker `|` position doesn't match",
30083                );
30084                Ok(Some(lsp::CompletionResponse::Array(
30085                    completions
30086                        .iter()
30087                        .map(|(label, new_text)| lsp::CompletionItem {
30088                            label: label.to_string(),
30089                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
30090                                lsp::InsertReplaceEdit {
30091                                    insert: insert_range,
30092                                    replace: replace_range,
30093                                    new_text: new_text.to_string(),
30094                                },
30095                            )),
30096                            ..Default::default()
30097                        })
30098                        .collect(),
30099                )))
30100            }
30101        });
30102
30103    async move {
30104        request.next().await;
30105    }
30106}
30107
30108fn handle_resolve_completion_request(
30109    cx: &mut EditorLspTestContext,
30110    edits: Option<Vec<(&'static str, &'static str)>>,
30111) -> impl Future<Output = ()> {
30112    let edits = edits.map(|edits| {
30113        edits
30114            .iter()
30115            .map(|(marked_string, new_text)| {
30116                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
30117                let replace_range = cx.to_lsp_range(
30118                    MultiBufferOffset(marked_ranges[0].start)
30119                        ..MultiBufferOffset(marked_ranges[0].end),
30120                );
30121                lsp::TextEdit::new(replace_range, new_text.to_string())
30122            })
30123            .collect::<Vec<_>>()
30124    });
30125
30126    let mut request =
30127        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
30128            let edits = edits.clone();
30129            async move {
30130                Ok(lsp::CompletionItem {
30131                    additional_text_edits: edits,
30132                    ..Default::default()
30133                })
30134            }
30135        });
30136
30137    async move {
30138        request.next().await;
30139    }
30140}
30141
30142pub(crate) fn update_test_language_settings(
30143    cx: &mut TestAppContext,
30144    f: &dyn Fn(&mut AllLanguageSettingsContent),
30145) {
30146    cx.update(|cx| {
30147        SettingsStore::update_global(cx, |store, cx| {
30148            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
30149                f(&mut settings.project.all_languages)
30150            });
30151        });
30152    });
30153}
30154
30155pub(crate) fn update_test_project_settings(
30156    cx: &mut TestAppContext,
30157    f: &dyn Fn(&mut ProjectSettingsContent),
30158) {
30159    cx.update(|cx| {
30160        SettingsStore::update_global(cx, |store, cx| {
30161            store.update_user_settings(cx, |settings| f(&mut settings.project));
30162        });
30163    });
30164}
30165
30166pub(crate) fn update_test_editor_settings(
30167    cx: &mut TestAppContext,
30168    f: &dyn Fn(&mut EditorSettingsContent),
30169) {
30170    cx.update(|cx| {
30171        SettingsStore::update_global(cx, |store, cx| {
30172            store.update_user_settings(cx, |settings| f(&mut settings.editor));
30173        })
30174    })
30175}
30176
30177pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
30178    cx.update(|cx| {
30179        assets::Assets.load_test_fonts(cx);
30180        let store = SettingsStore::test(cx);
30181        cx.set_global(store);
30182        theme_settings::init(theme::LoadThemes::JustBase, cx);
30183        release_channel::init(semver::Version::new(0, 0, 0), cx);
30184        crate::init(cx);
30185    });
30186    zlog::init_test();
30187    update_test_language_settings(cx, &f);
30188}
30189
30190#[track_caller]
30191fn assert_hunk_revert(
30192    not_reverted_text_with_selections: &str,
30193    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
30194    expected_reverted_text_with_selections: &str,
30195    base_text: &str,
30196    cx: &mut EditorLspTestContext,
30197) {
30198    cx.set_state(not_reverted_text_with_selections);
30199    cx.set_head_text(base_text);
30200    cx.executor().run_until_parked();
30201
30202    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
30203        let snapshot = editor.snapshot(window, cx);
30204        let reverted_hunk_statuses = snapshot
30205            .buffer_snapshot()
30206            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
30207            .map(|hunk| hunk.status().kind)
30208            .collect::<Vec<_>>();
30209
30210        editor.git_restore(&Default::default(), window, cx);
30211        reverted_hunk_statuses
30212    });
30213    cx.executor().run_until_parked();
30214    cx.assert_editor_state(expected_reverted_text_with_selections);
30215    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
30216}
30217
30218#[gpui::test(iterations = 10)]
30219async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
30220    init_test(cx, |_| {});
30221
30222    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
30223    let counter = diagnostic_requests.clone();
30224
30225    let fs = FakeFs::new(cx.executor());
30226    fs.insert_tree(
30227        path!("/a"),
30228        json!({
30229            "first.rs": "fn main() { let a = 5; }",
30230            "second.rs": "// Test file",
30231        }),
30232    )
30233    .await;
30234
30235    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
30236    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30237    let workspace = window
30238        .read_with(cx, |mw, _| mw.workspace().clone())
30239        .unwrap();
30240    let cx = &mut VisualTestContext::from_window(*window, cx);
30241
30242    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30243    language_registry.add(rust_lang());
30244    let mut fake_servers = language_registry.register_fake_lsp(
30245        "Rust",
30246        FakeLspAdapter {
30247            capabilities: lsp::ServerCapabilities {
30248                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
30249                    lsp::DiagnosticOptions {
30250                        identifier: None,
30251                        inter_file_dependencies: true,
30252                        workspace_diagnostics: true,
30253                        work_done_progress_options: Default::default(),
30254                    },
30255                )),
30256                ..Default::default()
30257            },
30258            ..Default::default()
30259        },
30260    );
30261
30262    let editor = workspace
30263        .update_in(cx, |workspace, window, cx| {
30264            workspace.open_abs_path(
30265                PathBuf::from(path!("/a/first.rs")),
30266                OpenOptions::default(),
30267                window,
30268                cx,
30269            )
30270        })
30271        .await
30272        .unwrap()
30273        .downcast::<Editor>()
30274        .unwrap();
30275    let fake_server = fake_servers.next().await.unwrap();
30276    let server_id = fake_server.server.server_id();
30277    let mut first_request = fake_server
30278        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
30279            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
30280            let result_id = Some(new_result_id.to_string());
30281            assert_eq!(
30282                params.text_document.uri,
30283                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
30284            );
30285            async move {
30286                Ok(lsp::DocumentDiagnosticReportResult::Report(
30287                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
30288                        related_documents: None,
30289                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
30290                            items: Vec::new(),
30291                            result_id,
30292                        },
30293                    }),
30294                ))
30295            }
30296        });
30297
30298    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
30299        project.update(cx, |project, cx| {
30300            let buffer_id = editor
30301                .read(cx)
30302                .buffer()
30303                .read(cx)
30304                .as_singleton()
30305                .expect("created a singleton buffer")
30306                .read(cx)
30307                .remote_id();
30308            let buffer_result_id = project
30309                .lsp_store()
30310                .read(cx)
30311                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
30312            assert_eq!(expected_result_id, buffer_result_id);
30313        });
30314    };
30315
30316    ensure_result_id(None, cx);
30317    cx.executor().advance_clock(Duration::from_millis(60));
30318    cx.executor().run_until_parked();
30319    assert_eq!(
30320        diagnostic_requests.load(atomic::Ordering::Acquire),
30321        1,
30322        "Opening file should trigger diagnostic request"
30323    );
30324    first_request
30325        .next()
30326        .await
30327        .expect("should have sent the first diagnostics pull request");
30328    ensure_result_id(Some(SharedString::new_static("1")), cx);
30329
30330    // Editing should trigger diagnostics
30331    editor.update_in(cx, |editor, window, cx| {
30332        editor.handle_input("2", window, cx)
30333    });
30334    cx.executor().advance_clock(Duration::from_millis(60));
30335    cx.executor().run_until_parked();
30336    assert_eq!(
30337        diagnostic_requests.load(atomic::Ordering::Acquire),
30338        2,
30339        "Editing should trigger diagnostic request"
30340    );
30341    ensure_result_id(Some(SharedString::new_static("2")), cx);
30342
30343    // Moving cursor should not trigger diagnostic request
30344    editor.update_in(cx, |editor, window, cx| {
30345        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30346            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
30347        });
30348    });
30349    cx.executor().advance_clock(Duration::from_millis(60));
30350    cx.executor().run_until_parked();
30351    assert_eq!(
30352        diagnostic_requests.load(atomic::Ordering::Acquire),
30353        2,
30354        "Cursor movement should not trigger diagnostic request"
30355    );
30356    ensure_result_id(Some(SharedString::new_static("2")), cx);
30357    // Multiple rapid edits should be debounced
30358    for _ in 0..5 {
30359        editor.update_in(cx, |editor, window, cx| {
30360            editor.handle_input("x", window, cx)
30361        });
30362    }
30363    cx.executor().advance_clock(Duration::from_millis(60));
30364    cx.executor().run_until_parked();
30365
30366    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
30367    assert!(
30368        final_requests <= 4,
30369        "Multiple rapid edits should be debounced (got {final_requests} requests)",
30370    );
30371    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
30372}
30373
30374#[gpui::test]
30375async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
30376    // Regression test for issue #11671
30377    // Previously, adding a cursor after moving multiple cursors would reset
30378    // the cursor count instead of adding to the existing cursors.
30379    init_test(cx, |_| {});
30380    let mut cx = EditorTestContext::new(cx).await;
30381
30382    // Create a simple buffer with cursor at start
30383    cx.set_state(indoc! {"
30384        ˇaaaa
30385        bbbb
30386        cccc
30387        dddd
30388        eeee
30389        ffff
30390        gggg
30391        hhhh"});
30392
30393    // Add 2 cursors below (so we have 3 total)
30394    cx.update_editor(|editor, window, cx| {
30395        editor.add_selection_below(&Default::default(), window, cx);
30396        editor.add_selection_below(&Default::default(), window, cx);
30397    });
30398
30399    // Verify we have 3 cursors
30400    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
30401    assert_eq!(
30402        initial_count, 3,
30403        "Should have 3 cursors after adding 2 below"
30404    );
30405
30406    // Move down one line
30407    cx.update_editor(|editor, window, cx| {
30408        editor.move_down(&MoveDown, window, cx);
30409    });
30410
30411    // Add another cursor below
30412    cx.update_editor(|editor, window, cx| {
30413        editor.add_selection_below(&Default::default(), window, cx);
30414    });
30415
30416    // Should now have 4 cursors (3 original + 1 new)
30417    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
30418    assert_eq!(
30419        final_count, 4,
30420        "Should have 4 cursors after moving and adding another"
30421    );
30422}
30423
30424#[gpui::test]
30425async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
30426    init_test(cx, |_| {});
30427
30428    let mut cx = EditorTestContext::new(cx).await;
30429
30430    cx.set_state(indoc!(
30431        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
30432           Second line here"#
30433    ));
30434
30435    cx.update_editor(|editor, window, cx| {
30436        // Enable soft wrapping with a narrow width to force soft wrapping and
30437        // confirm that more than 2 rows are being displayed.
30438        editor.set_wrap_width(Some(100.0.into()), cx);
30439        assert!(editor.display_text(cx).lines().count() > 2);
30440
30441        editor.add_selection_below(
30442            &AddSelectionBelow {
30443                skip_soft_wrap: true,
30444            },
30445            window,
30446            cx,
30447        );
30448
30449        assert_eq!(
30450            display_ranges(editor, cx),
30451            &[
30452                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
30453                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
30454            ]
30455        );
30456
30457        editor.add_selection_above(
30458            &AddSelectionAbove {
30459                skip_soft_wrap: true,
30460            },
30461            window,
30462            cx,
30463        );
30464
30465        assert_eq!(
30466            display_ranges(editor, cx),
30467            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
30468        );
30469
30470        editor.add_selection_below(
30471            &AddSelectionBelow {
30472                skip_soft_wrap: false,
30473            },
30474            window,
30475            cx,
30476        );
30477
30478        assert_eq!(
30479            display_ranges(editor, cx),
30480            &[
30481                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
30482                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
30483            ]
30484        );
30485
30486        editor.add_selection_above(
30487            &AddSelectionAbove {
30488                skip_soft_wrap: false,
30489            },
30490            window,
30491            cx,
30492        );
30493
30494        assert_eq!(
30495            display_ranges(editor, cx),
30496            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
30497        );
30498    });
30499
30500    // Set up text where selections are in the middle of a soft-wrapped line.
30501    // When adding selection below with `skip_soft_wrap` set to `true`, the new
30502    // selection should be at the same buffer column, not the same pixel
30503    // position.
30504    cx.set_state(indoc!(
30505        r#"1. Very long line to show «howˇ» a wrapped line would look
30506           2. Very long line to show how a wrapped line would look"#
30507    ));
30508
30509    cx.update_editor(|editor, window, cx| {
30510        // Enable soft wrapping with a narrow width to force soft wrapping and
30511        // confirm that more than 2 rows are being displayed.
30512        editor.set_wrap_width(Some(100.0.into()), cx);
30513        assert!(editor.display_text(cx).lines().count() > 2);
30514
30515        editor.add_selection_below(
30516            &AddSelectionBelow {
30517                skip_soft_wrap: true,
30518            },
30519            window,
30520            cx,
30521        );
30522
30523        // Assert that there's now 2 selections, both selecting the same column
30524        // range in the buffer row.
30525        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
30526        let selections = editor.selections.all::<Point>(&display_map);
30527        assert_eq!(selections.len(), 2);
30528        assert_eq!(selections[0].start.column, selections[1].start.column);
30529        assert_eq!(selections[0].end.column, selections[1].end.column);
30530    });
30531}
30532
30533#[gpui::test]
30534async fn test_insert_snippet(cx: &mut TestAppContext) {
30535    init_test(cx, |_| {});
30536    let mut cx = EditorTestContext::new(cx).await;
30537
30538    cx.update_editor(|editor, _, cx| {
30539        editor.project().unwrap().update(cx, |project, cx| {
30540            project.snippets().update(cx, |snippets, _cx| {
30541                let snippet = project::snippet_provider::Snippet {
30542                    prefix: vec![], // no prefix needed!
30543                    body: "an Unspecified".to_string(),
30544                    description: Some("shhhh it's a secret".to_string()),
30545                    name: "super secret snippet".to_string(),
30546                };
30547                snippets.add_snippet_for_test(
30548                    None,
30549                    PathBuf::from("test_snippets.json"),
30550                    vec![Arc::new(snippet)],
30551                );
30552
30553                let snippet = project::snippet_provider::Snippet {
30554                    prefix: vec![], // no prefix needed!
30555                    body: " Location".to_string(),
30556                    description: Some("the word 'location'".to_string()),
30557                    name: "location word".to_string(),
30558                };
30559                snippets.add_snippet_for_test(
30560                    Some("Markdown".to_string()),
30561                    PathBuf::from("test_snippets.json"),
30562                    vec![Arc::new(snippet)],
30563                );
30564            });
30565        })
30566    });
30567
30568    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
30569
30570    cx.update_editor(|editor, window, cx| {
30571        editor.insert_snippet_at_selections(
30572            &InsertSnippet {
30573                language: None,
30574                name: Some("super secret snippet".to_string()),
30575                snippet: None,
30576            },
30577            window,
30578            cx,
30579        );
30580
30581        // Language is specified in the action,
30582        // so the buffer language does not need to match
30583        editor.insert_snippet_at_selections(
30584            &InsertSnippet {
30585                language: Some("Markdown".to_string()),
30586                name: Some("location word".to_string()),
30587                snippet: None,
30588            },
30589            window,
30590            cx,
30591        );
30592
30593        editor.insert_snippet_at_selections(
30594            &InsertSnippet {
30595                language: None,
30596                name: None,
30597                snippet: Some("$0 after".to_string()),
30598            },
30599            window,
30600            cx,
30601        );
30602    });
30603
30604    cx.assert_editor_state(
30605        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
30606    );
30607}
30608
30609#[gpui::test]
30610async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
30611    use crate::inlays::inlay_hints::InlayHintRefreshReason;
30612    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
30613    use settings::InlayHintSettingsContent;
30614    use std::sync::atomic::AtomicU32;
30615    use std::time::Duration;
30616
30617    const BASE_TIMEOUT_SECS: u64 = 1;
30618
30619    let request_count = Arc::new(AtomicU32::new(0));
30620    let closure_request_count = request_count.clone();
30621
30622    init_test(cx, &|settings| {
30623        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
30624            enabled: Some(true),
30625            ..InlayHintSettingsContent::default()
30626        })
30627    });
30628    cx.update(|cx| {
30629        SettingsStore::update_global(cx, |store, cx| {
30630            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
30631                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
30632                    request_timeout: Some(BASE_TIMEOUT_SECS),
30633                    button: Some(true),
30634                    notifications: None,
30635                    semantic_token_rules: None,
30636                });
30637            });
30638        });
30639    });
30640
30641    let fs = FakeFs::new(cx.executor());
30642    fs.insert_tree(
30643        path!("/a"),
30644        json!({
30645            "main.rs": "fn main() { let a = 5; }",
30646        }),
30647    )
30648    .await;
30649
30650    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
30651    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30652    language_registry.add(rust_lang());
30653    let mut fake_servers = language_registry.register_fake_lsp(
30654        "Rust",
30655        FakeLspAdapter {
30656            capabilities: lsp::ServerCapabilities {
30657                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30658                ..lsp::ServerCapabilities::default()
30659            },
30660            initializer: Some(Box::new(move |fake_server| {
30661                let request_count = closure_request_count.clone();
30662                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30663                    move |params, cx| {
30664                        let request_count = request_count.clone();
30665                        async move {
30666                            cx.background_executor()
30667                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
30668                                .await;
30669                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
30670                            assert_eq!(
30671                                params.text_document.uri,
30672                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
30673                            );
30674                            Ok(Some(vec![lsp::InlayHint {
30675                                position: lsp::Position::new(0, 1),
30676                                label: lsp::InlayHintLabel::String(count.to_string()),
30677                                kind: None,
30678                                text_edits: None,
30679                                tooltip: None,
30680                                padding_left: None,
30681                                padding_right: None,
30682                                data: None,
30683                            }]))
30684                        }
30685                    },
30686                );
30687            })),
30688            ..FakeLspAdapter::default()
30689        },
30690    );
30691
30692    let buffer = project
30693        .update(cx, |project, cx| {
30694            project.open_local_buffer(path!("/a/main.rs"), cx)
30695        })
30696        .await
30697        .unwrap();
30698    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
30699
30700    cx.executor().run_until_parked();
30701    let fake_server = fake_servers.next().await.unwrap();
30702
30703    cx.executor()
30704        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
30705    cx.executor().run_until_parked();
30706    editor
30707        .update(cx, |editor, _window, cx| {
30708            assert!(
30709                cached_hint_labels(editor, cx).is_empty(),
30710                "First request should time out, no hints cached"
30711            );
30712        })
30713        .unwrap();
30714
30715    editor
30716        .update(cx, |editor, _window, cx| {
30717            editor.refresh_inlay_hints(
30718                InlayHintRefreshReason::RefreshRequested {
30719                    server_id: fake_server.server.server_id(),
30720                    request_id: Some(1),
30721                },
30722                cx,
30723            );
30724        })
30725        .unwrap();
30726    cx.executor()
30727        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
30728    cx.executor().run_until_parked();
30729    editor
30730        .update(cx, |editor, _window, cx| {
30731            assert!(
30732                cached_hint_labels(editor, cx).is_empty(),
30733                "Second request should also time out with BASE_TIMEOUT, no hints cached"
30734            );
30735        })
30736        .unwrap();
30737
30738    cx.update(|cx| {
30739        SettingsStore::update_global(cx, |store, cx| {
30740            store.update_user_settings(cx, |settings| {
30741                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
30742                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
30743                    button: Some(true),
30744                    notifications: None,
30745                    semantic_token_rules: None,
30746                });
30747            });
30748        });
30749    });
30750    editor
30751        .update(cx, |editor, _window, cx| {
30752            editor.refresh_inlay_hints(
30753                InlayHintRefreshReason::RefreshRequested {
30754                    server_id: fake_server.server.server_id(),
30755                    request_id: Some(2),
30756                },
30757                cx,
30758            );
30759        })
30760        .unwrap();
30761    cx.executor()
30762        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
30763    cx.executor().run_until_parked();
30764    editor
30765        .update(cx, |editor, _window, cx| {
30766            assert_eq!(
30767                vec!["1".to_string()],
30768                cached_hint_labels(editor, cx),
30769                "With extended timeout (BASE * 4), hints should arrive successfully"
30770            );
30771            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
30772        })
30773        .unwrap();
30774}
30775
30776#[gpui::test]
30777async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
30778    init_test(cx, |_| {});
30779    let (editor, cx) = cx.add_window_view(Editor::single_line);
30780    editor.update_in(cx, |editor, window, cx| {
30781        editor.set_text("oops\n\nwow\n", window, cx)
30782    });
30783    cx.run_until_parked();
30784    editor.update(cx, |editor, cx| {
30785        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
30786    });
30787    editor.update(cx, |editor, cx| {
30788        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
30789    });
30790    cx.run_until_parked();
30791    editor.update(cx, |editor, cx| {
30792        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
30793    });
30794}
30795
30796#[gpui::test]
30797async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
30798    init_test(cx, |_| {});
30799
30800    cx.update(|cx| {
30801        register_project_item::<Editor>(cx);
30802    });
30803
30804    let fs = FakeFs::new(cx.executor());
30805    fs.insert_tree("/root1", json!({})).await;
30806    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
30807        .await;
30808
30809    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
30810    let (multi_workspace, cx) =
30811        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30812    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
30813
30814    let worktree_id = project.update(cx, |project, cx| {
30815        project.worktrees(cx).next().unwrap().read(cx).id()
30816    });
30817
30818    let handle = workspace
30819        .update_in(cx, |workspace, window, cx| {
30820            let project_path = (worktree_id, rel_path("one.pdf"));
30821            workspace.open_path(project_path, None, true, window, cx)
30822        })
30823        .await
30824        .unwrap();
30825    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
30826    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
30827    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
30828    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
30829}
30830
30831#[gpui::test]
30832async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
30833    init_test(cx, |_| {});
30834
30835    let language = Arc::new(Language::new(
30836        LanguageConfig::default(),
30837        Some(tree_sitter_rust::LANGUAGE.into()),
30838    ));
30839
30840    // Test hierarchical sibling navigation
30841    let text = r#"
30842        fn outer() {
30843            if condition {
30844                let a = 1;
30845            }
30846            let b = 2;
30847        }
30848
30849        fn another() {
30850            let c = 3;
30851        }
30852    "#;
30853
30854    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
30855    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30856    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
30857
30858    // Wait for parsing to complete
30859    editor
30860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
30861        .await;
30862
30863    editor.update_in(cx, |editor, window, cx| {
30864        // Start by selecting "let a = 1;" inside the if block
30865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30866            s.select_display_ranges([
30867                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
30868            ]);
30869        });
30870
30871        let initial_selection = editor
30872            .selections
30873            .display_ranges(&editor.display_snapshot(cx));
30874        assert_eq!(initial_selection.len(), 1, "Should have one selection");
30875
30876        // Test select next sibling - should move up levels to find the next sibling
30877        // Since "let a = 1;" has no siblings in the if block, it should move up
30878        // to find "let b = 2;" which is a sibling of the if block
30879        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
30880        let next_selection = editor
30881            .selections
30882            .display_ranges(&editor.display_snapshot(cx));
30883
30884        // Should have a selection and it should be different from the initial
30885        assert_eq!(
30886            next_selection.len(),
30887            1,
30888            "Should have one selection after next"
30889        );
30890        assert_ne!(
30891            next_selection[0], initial_selection[0],
30892            "Next sibling selection should be different"
30893        );
30894
30895        // Test hierarchical navigation by going to the end of the current function
30896        // and trying to navigate to the next function
30897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30898            s.select_display_ranges([
30899                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
30900            ]);
30901        });
30902
30903        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
30904        let function_next_selection = editor
30905            .selections
30906            .display_ranges(&editor.display_snapshot(cx));
30907
30908        // Should move to the next function
30909        assert_eq!(
30910            function_next_selection.len(),
30911            1,
30912            "Should have one selection after function next"
30913        );
30914
30915        // Test select previous sibling navigation
30916        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
30917        let prev_selection = editor
30918            .selections
30919            .display_ranges(&editor.display_snapshot(cx));
30920
30921        // Should have a selection and it should be different
30922        assert_eq!(
30923            prev_selection.len(),
30924            1,
30925            "Should have one selection after prev"
30926        );
30927        assert_ne!(
30928            prev_selection[0], function_next_selection[0],
30929            "Previous sibling selection should be different from next"
30930        );
30931    });
30932}
30933
30934#[gpui::test]
30935async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
30936    init_test(cx, |_| {});
30937
30938    let mut cx = EditorTestContext::new(cx).await;
30939    cx.set_state(
30940        "let ˇvariable = 42;
30941let another = variable + 1;
30942let result = variable * 2;",
30943    );
30944
30945    // Set up document highlights manually (simulating LSP response)
30946    cx.update_editor(|editor, _window, cx| {
30947        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
30948
30949        // Create highlights for "variable" occurrences
30950        let highlight_ranges = [
30951            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
30952            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
30953            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
30954        ];
30955
30956        let anchor_ranges: Vec<_> = highlight_ranges
30957            .iter()
30958            .map(|range| range.clone().to_anchors(&buffer_snapshot))
30959            .collect();
30960
30961        editor.highlight_background(
30962            HighlightKey::DocumentHighlightRead,
30963            &anchor_ranges,
30964            |_, theme| theme.colors().editor_document_highlight_read_background,
30965            cx,
30966        );
30967    });
30968
30969    // Go to next highlight - should move to second "variable"
30970    cx.update_editor(|editor, window, cx| {
30971        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
30972    });
30973    cx.assert_editor_state(
30974        "let variable = 42;
30975let another = ˇvariable + 1;
30976let result = variable * 2;",
30977    );
30978
30979    // Go to next highlight - should move to third "variable"
30980    cx.update_editor(|editor, window, cx| {
30981        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
30982    });
30983    cx.assert_editor_state(
30984        "let variable = 42;
30985let another = variable + 1;
30986let result = ˇvariable * 2;",
30987    );
30988
30989    // Go to next highlight - should stay at third "variable" (no wrap-around)
30990    cx.update_editor(|editor, window, cx| {
30991        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
30992    });
30993    cx.assert_editor_state(
30994        "let variable = 42;
30995let another = variable + 1;
30996let result = ˇvariable * 2;",
30997    );
30998
30999    // Now test going backwards from third position
31000    cx.update_editor(|editor, window, cx| {
31001        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31002    });
31003    cx.assert_editor_state(
31004        "let variable = 42;
31005let another = ˇvariable + 1;
31006let result = variable * 2;",
31007    );
31008
31009    // Go to previous highlight - should move to first "variable"
31010    cx.update_editor(|editor, window, cx| {
31011        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31012    });
31013    cx.assert_editor_state(
31014        "let ˇvariable = 42;
31015let another = variable + 1;
31016let result = variable * 2;",
31017    );
31018
31019    // Go to previous highlight - should stay on first "variable"
31020    cx.update_editor(|editor, window, cx| {
31021        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
31022    });
31023    cx.assert_editor_state(
31024        "let ˇvariable = 42;
31025let another = variable + 1;
31026let result = variable * 2;",
31027    );
31028}
31029
31030#[gpui::test]
31031async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
31032    cx: &mut gpui::TestAppContext,
31033) {
31034    init_test(cx, |_| {});
31035
31036    let url = "https://zed.dev";
31037
31038    let markdown_language = Arc::new(Language::new(
31039        LanguageConfig {
31040            name: "Markdown".into(),
31041            ..LanguageConfig::default()
31042        },
31043        None,
31044    ));
31045
31046    let mut cx = EditorTestContext::new(cx).await;
31047    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31048    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
31049
31050    cx.update_editor(|editor, window, cx| {
31051        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31052        editor.paste(&Paste, window, cx);
31053    });
31054
31055    cx.assert_editor_state(&format!(
31056        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
31057    ));
31058}
31059
31060#[gpui::test]
31061async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
31062    init_test(cx, |_| {});
31063
31064    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31065    let mut cx = EditorTestContext::new(cx).await;
31066
31067    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31068
31069    // Case 1: Test if adding a character with multi cursors preserves nested list indents
31070    cx.set_state(&indoc! {"
31071        - [ ] Item 1
31072            - [ ] Item 1.a
31073        - [ˇ] Item 2
31074            - [ˇ] Item 2.a
31075            - [ˇ] Item 2.b
31076        "
31077    });
31078    cx.update_editor(|editor, window, cx| {
31079        editor.handle_input("x", window, cx);
31080    });
31081    cx.run_until_parked();
31082    cx.assert_editor_state(indoc! {"
31083        - [ ] Item 1
31084            - [ ] Item 1.a
31085        - [xˇ] Item 2
31086            - [xˇ] Item 2.a
31087            - [xˇ] Item 2.b
31088        "
31089    });
31090
31091    // Case 2: Test adding new line after nested list continues the list with unchecked task
31092    cx.set_state(&indoc! {"
31093        - [ ] Item 1
31094            - [ ] Item 1.a
31095        - [x] Item 2
31096            - [x] Item 2.a
31097            - [x] Item 2.bˇ"
31098    });
31099    cx.update_editor(|editor, window, cx| {
31100        editor.newline(&Newline, window, cx);
31101    });
31102    cx.assert_editor_state(indoc! {"
31103        - [ ] Item 1
31104            - [ ] Item 1.a
31105        - [x] Item 2
31106            - [x] Item 2.a
31107            - [x] Item 2.b
31108            - [ ] ˇ"
31109    });
31110
31111    // Case 3: Test adding content to continued list item
31112    cx.update_editor(|editor, window, cx| {
31113        editor.handle_input("Item 2.c", window, cx);
31114    });
31115    cx.run_until_parked();
31116    cx.assert_editor_state(indoc! {"
31117        - [ ] Item 1
31118            - [ ] Item 1.a
31119        - [x] Item 2
31120            - [x] Item 2.a
31121            - [x] Item 2.b
31122            - [ ] Item 2.cˇ"
31123    });
31124
31125    // Case 4: Test adding new line after nested ordered list continues with next number
31126    cx.set_state(indoc! {"
31127        1. Item 1
31128            1. Item 1.a
31129        2. Item 2
31130            1. Item 2.a
31131            2. Item 2.bˇ"
31132    });
31133    cx.update_editor(|editor, window, cx| {
31134        editor.newline(&Newline, window, cx);
31135    });
31136    cx.assert_editor_state(indoc! {"
31137        1. Item 1
31138            1. Item 1.a
31139        2. Item 2
31140            1. Item 2.a
31141            2. Item 2.b
31142            3. ˇ"
31143    });
31144
31145    // Case 5: Adding content to continued ordered list item
31146    cx.update_editor(|editor, window, cx| {
31147        editor.handle_input("Item 2.c", window, cx);
31148    });
31149    cx.run_until_parked();
31150    cx.assert_editor_state(indoc! {"
31151        1. Item 1
31152            1. Item 1.a
31153        2. Item 2
31154            1. Item 2.a
31155            2. Item 2.b
31156            3. Item 2.cˇ"
31157    });
31158
31159    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
31160    cx.set_state(indoc! {"
31161        - Item 1
31162            - Item 1.a
31163            - Item 1.a
31164        ˇ"});
31165    cx.update_editor(|editor, window, cx| {
31166        editor.handle_input("-", window, cx);
31167    });
31168    cx.run_until_parked();
31169    cx.assert_editor_state(indoc! {"
31170        - Item 1
31171            - Item 1.a
31172            - Item 1.a
31173"});
31174
31175    // Case 7: Test blockquote newline preserves something
31176    cx.set_state(indoc! {"
31177        > Item 1ˇ"
31178    });
31179    cx.update_editor(|editor, window, cx| {
31180        editor.newline(&Newline, window, cx);
31181    });
31182    cx.assert_editor_state(indoc! {"
31183        > Item 1
31184        ˇ"
31185    });
31186}
31187
31188#[gpui::test]
31189async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
31190    cx: &mut gpui::TestAppContext,
31191) {
31192    init_test(cx, |_| {});
31193
31194    let url = "https://zed.dev";
31195
31196    let markdown_language = Arc::new(Language::new(
31197        LanguageConfig {
31198            name: "Markdown".into(),
31199            ..LanguageConfig::default()
31200        },
31201        None,
31202    ));
31203
31204    let mut cx = EditorTestContext::new(cx).await;
31205    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31206    cx.set_state(&format!(
31207        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
31208    ));
31209
31210    cx.update_editor(|editor, window, cx| {
31211        editor.copy(&Copy, window, cx);
31212    });
31213
31214    cx.set_state(&format!(
31215        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
31216    ));
31217
31218    cx.update_editor(|editor, window, cx| {
31219        editor.paste(&Paste, window, cx);
31220    });
31221
31222    cx.assert_editor_state(&format!(
31223        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
31224    ));
31225}
31226
31227#[gpui::test]
31228async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
31229    cx: &mut gpui::TestAppContext,
31230) {
31231    init_test(cx, |_| {});
31232
31233    let url = "https://zed.dev";
31234
31235    let markdown_language = Arc::new(Language::new(
31236        LanguageConfig {
31237            name: "Markdown".into(),
31238            ..LanguageConfig::default()
31239        },
31240        None,
31241    ));
31242
31243    let mut cx = EditorTestContext::new(cx).await;
31244    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31245    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
31246
31247    cx.update_editor(|editor, window, cx| {
31248        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31249        editor.paste(&Paste, window, cx);
31250    });
31251
31252    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
31253}
31254
31255#[gpui::test]
31256async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
31257    cx: &mut gpui::TestAppContext,
31258) {
31259    init_test(cx, |_| {});
31260
31261    let text = "Awesome";
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("Hello, «editorˇ».\nZed is «ˇgreat»");
31274
31275    cx.update_editor(|editor, window, cx| {
31276        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
31277        editor.paste(&Paste, window, cx);
31278    });
31279
31280    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
31281}
31282
31283#[gpui::test]
31284async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
31285    cx: &mut gpui::TestAppContext,
31286) {
31287    init_test(cx, |_| {});
31288
31289    let url = "https://zed.dev";
31290
31291    let markdown_language = Arc::new(Language::new(
31292        LanguageConfig {
31293            name: "Rust".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ˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
31302
31303    cx.update_editor(|editor, window, cx| {
31304        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31305        editor.paste(&Paste, window, cx);
31306    });
31307
31308    cx.assert_editor_state(&format!(
31309        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
31310    ));
31311}
31312
31313#[gpui::test]
31314async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
31315    cx: &mut TestAppContext,
31316) {
31317    init_test(cx, |_| {});
31318
31319    let url = "https://zed.dev";
31320
31321    let markdown_language = Arc::new(Language::new(
31322        LanguageConfig {
31323            name: "Markdown".into(),
31324            ..LanguageConfig::default()
31325        },
31326        None,
31327    ));
31328
31329    let (editor, cx) = cx.add_window_view(|window, cx| {
31330        let multi_buffer = MultiBuffer::build_multi(
31331            [
31332                ("this will embed -> link", vec![Point::row_range(0..1)]),
31333                ("this will replace -> link", vec![Point::row_range(0..1)]),
31334            ],
31335            cx,
31336        );
31337        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
31338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31339            s.select_ranges(vec![
31340                Point::new(0, 19)..Point::new(0, 23),
31341                Point::new(1, 21)..Point::new(1, 25),
31342            ])
31343        });
31344        let snapshot = multi_buffer.read(cx).snapshot(cx);
31345        let first_buffer_id = snapshot.all_buffer_ids().next().unwrap();
31346        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
31347        first_buffer.update(cx, |buffer, cx| {
31348            buffer.set_language(Some(markdown_language.clone()), cx);
31349        });
31350
31351        editor
31352    });
31353    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
31354
31355    cx.update_editor(|editor, window, cx| {
31356        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
31357        editor.paste(&Paste, window, cx);
31358    });
31359
31360    cx.assert_editor_state(&format!(
31361        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
31362    ));
31363}
31364
31365#[gpui::test]
31366async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
31367    init_test(cx, |_| {});
31368
31369    let fs = FakeFs::new(cx.executor());
31370    fs.insert_tree(
31371        path!("/project"),
31372        json!({
31373            "first.rs": "# First Document\nSome content here.",
31374            "second.rs": "Plain text content for second file.",
31375        }),
31376    )
31377    .await;
31378
31379    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
31380    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31381    let cx = &mut VisualTestContext::from_window(*window, cx);
31382
31383    let language = rust_lang();
31384    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
31385    language_registry.add(language.clone());
31386    let mut fake_servers = language_registry.register_fake_lsp(
31387        "Rust",
31388        FakeLspAdapter {
31389            ..FakeLspAdapter::default()
31390        },
31391    );
31392
31393    let buffer1 = project
31394        .update(cx, |project, cx| {
31395            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
31396        })
31397        .await
31398        .unwrap();
31399    let buffer2 = project
31400        .update(cx, |project, cx| {
31401            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
31402        })
31403        .await
31404        .unwrap();
31405
31406    let multi_buffer = cx.new(|cx| {
31407        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
31408        multi_buffer.set_excerpts_for_path(
31409            PathKey::for_buffer(&buffer1, cx),
31410            buffer1.clone(),
31411            [Point::zero()..buffer1.read(cx).max_point()],
31412            3,
31413            cx,
31414        );
31415        multi_buffer.set_excerpts_for_path(
31416            PathKey::for_buffer(&buffer2, cx),
31417            buffer2.clone(),
31418            [Point::zero()..buffer1.read(cx).max_point()],
31419            3,
31420            cx,
31421        );
31422        multi_buffer
31423    });
31424
31425    let (editor, cx) = cx.add_window_view(|window, cx| {
31426        Editor::new(
31427            EditorMode::full(),
31428            multi_buffer,
31429            Some(project.clone()),
31430            window,
31431            cx,
31432        )
31433    });
31434
31435    let fake_language_server = fake_servers.next().await.unwrap();
31436
31437    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
31438
31439    let save = editor.update_in(cx, |editor, window, cx| {
31440        assert!(editor.is_dirty(cx));
31441
31442        editor.save(
31443            SaveOptions {
31444                format: true,
31445                autosave: true,
31446            },
31447            project,
31448            window,
31449            cx,
31450        )
31451    });
31452    let (start_edit_tx, start_edit_rx) = oneshot::channel();
31453    let (done_edit_tx, done_edit_rx) = oneshot::channel();
31454    let mut done_edit_rx = Some(done_edit_rx);
31455    let mut start_edit_tx = Some(start_edit_tx);
31456
31457    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
31458        start_edit_tx.take().unwrap().send(()).unwrap();
31459        let done_edit_rx = done_edit_rx.take().unwrap();
31460        async move {
31461            done_edit_rx.await.unwrap();
31462            Ok(None)
31463        }
31464    });
31465
31466    start_edit_rx.await.unwrap();
31467    buffer2
31468        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
31469        .unwrap();
31470
31471    done_edit_tx.send(()).unwrap();
31472
31473    save.await.unwrap();
31474    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
31475}
31476
31477#[gpui::test]
31478fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
31479    init_test(cx, |_| {});
31480
31481    let editor = cx.add_window(|window, cx| {
31482        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
31483        build_editor(buffer, window, cx)
31484    });
31485
31486    editor
31487        .update(cx, |editor, window, cx| {
31488            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31489                s.select_display_ranges([
31490                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
31491                ])
31492            });
31493
31494            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
31495
31496            assert_eq!(
31497                editor.display_text(cx),
31498                "line1\nline2\nline2",
31499                "Duplicating last line upward should create duplicate above, not on same line"
31500            );
31501
31502            assert_eq!(
31503                editor
31504                    .selections
31505                    .display_ranges(&editor.display_snapshot(cx)),
31506                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
31507                "Selection should move to the duplicated line"
31508            );
31509        })
31510        .unwrap();
31511}
31512
31513#[gpui::test]
31514async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
31515    init_test(cx, |_| {});
31516
31517    let mut cx = EditorTestContext::new(cx).await;
31518
31519    cx.set_state("line1\nline2ˇ");
31520
31521    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
31522
31523    let clipboard_text = cx
31524        .read_from_clipboard()
31525        .and_then(|item| item.text().as_deref().map(str::to_string));
31526
31527    assert_eq!(
31528        clipboard_text,
31529        Some("line2\n".to_string()),
31530        "Copying a line without trailing newline should include a newline"
31531    );
31532
31533    cx.set_state("line1\nˇ");
31534
31535    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31536
31537    cx.assert_editor_state("line1\nline2\nˇ");
31538}
31539
31540#[gpui::test]
31541async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
31542    init_test(cx, |_| {});
31543
31544    let mut cx = EditorTestContext::new(cx).await;
31545
31546    cx.set_state("ˇline1\nˇline2\nˇline3\n");
31547
31548    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
31549
31550    let clipboard_text = cx
31551        .read_from_clipboard()
31552        .and_then(|item| item.text().as_deref().map(str::to_string));
31553
31554    assert_eq!(
31555        clipboard_text,
31556        Some("line1\nline2\nline3\n".to_string()),
31557        "Copying multiple lines should include a single newline between lines"
31558    );
31559
31560    cx.set_state("lineA\nˇ");
31561
31562    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31563
31564    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
31565}
31566
31567#[gpui::test]
31568async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
31569    init_test(cx, |_| {});
31570
31571    let mut cx = EditorTestContext::new(cx).await;
31572
31573    cx.set_state("ˇline1\nˇline2\nˇline3\n");
31574
31575    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
31576
31577    let clipboard_text = cx
31578        .read_from_clipboard()
31579        .and_then(|item| item.text().as_deref().map(str::to_string));
31580
31581    assert_eq!(
31582        clipboard_text,
31583        Some("line1\nline2\nline3\n".to_string()),
31584        "Copying multiple lines should include a single newline between lines"
31585    );
31586
31587    cx.set_state("lineA\nˇ");
31588
31589    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
31590
31591    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
31592}
31593
31594#[gpui::test]
31595async fn test_end_of_editor_context(cx: &mut TestAppContext) {
31596    init_test(cx, |_| {});
31597
31598    let mut cx = EditorTestContext::new(cx).await;
31599
31600    cx.set_state("line1\nline2ˇ");
31601    cx.update_editor(|e, window, cx| {
31602        e.set_mode(EditorMode::SingleLine);
31603        assert!(!e.key_context(window, cx).contains("start_of_input"));
31604        assert!(e.key_context(window, cx).contains("end_of_input"));
31605    });
31606    cx.set_state("ˇline1\nline2");
31607    cx.update_editor(|e, window, cx| {
31608        e.set_mode(EditorMode::SingleLine);
31609        assert!(e.key_context(window, cx).contains("start_of_input"));
31610        assert!(!e.key_context(window, cx).contains("end_of_input"));
31611    });
31612    cx.set_state("line1ˇ\nline2");
31613    cx.update_editor(|e, window, cx| {
31614        e.set_mode(EditorMode::SingleLine);
31615        assert!(!e.key_context(window, cx).contains("start_of_input"));
31616        assert!(!e.key_context(window, cx).contains("end_of_input"));
31617    });
31618
31619    cx.set_state("line1\nline2ˇ");
31620    cx.update_editor(|e, window, cx| {
31621        e.set_mode(EditorMode::AutoHeight {
31622            min_lines: 1,
31623            max_lines: Some(4),
31624        });
31625        assert!(!e.key_context(window, cx).contains("start_of_input"));
31626        assert!(e.key_context(window, cx).contains("end_of_input"));
31627    });
31628    cx.set_state("ˇline1\nline2");
31629    cx.update_editor(|e, window, cx| {
31630        e.set_mode(EditorMode::AutoHeight {
31631            min_lines: 1,
31632            max_lines: Some(4),
31633        });
31634        assert!(e.key_context(window, cx).contains("start_of_input"));
31635        assert!(!e.key_context(window, cx).contains("end_of_input"));
31636    });
31637    cx.set_state("line1ˇ\nline2");
31638    cx.update_editor(|e, window, cx| {
31639        e.set_mode(EditorMode::AutoHeight {
31640            min_lines: 1,
31641            max_lines: Some(4),
31642        });
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
31648#[gpui::test]
31649async fn test_sticky_scroll(cx: &mut TestAppContext) {
31650    init_test(cx, |_| {});
31651    let mut cx = EditorTestContext::new(cx).await;
31652
31653    let buffer = indoc! {"
31654            ˇfn foo() {
31655                let abc = 123;
31656            }
31657            struct Bar;
31658            impl Bar {
31659                fn new() -> Self {
31660                    Self
31661                }
31662            }
31663            fn baz() {
31664            }
31665        "};
31666    cx.set_state(&buffer);
31667
31668    cx.update_editor(|e, _, cx| {
31669        e.buffer()
31670            .read(cx)
31671            .as_singleton()
31672            .unwrap()
31673            .update(cx, |buffer, cx| {
31674                buffer.set_language(Some(rust_lang()), cx);
31675            })
31676    });
31677
31678    let mut sticky_headers = |offset: ScrollOffset| {
31679        cx.update_editor(|e, window, cx| {
31680            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31681        });
31682        cx.run_until_parked();
31683        cx.update_editor(|e, window, cx| {
31684            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31685                .into_iter()
31686                .map(
31687                    |StickyHeader {
31688                         start_point,
31689                         offset,
31690                         ..
31691                     }| { (start_point, offset) },
31692                )
31693                .collect::<Vec<_>>()
31694        })
31695    };
31696
31697    let fn_foo = Point { row: 0, column: 0 };
31698    let impl_bar = Point { row: 4, column: 0 };
31699    let fn_new = Point { row: 5, column: 4 };
31700
31701    assert_eq!(sticky_headers(0.0), vec![]);
31702    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
31703    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
31704    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
31705    assert_eq!(sticky_headers(2.0), vec![]);
31706    assert_eq!(sticky_headers(2.5), vec![]);
31707    assert_eq!(sticky_headers(3.0), vec![]);
31708    assert_eq!(sticky_headers(3.5), vec![]);
31709    assert_eq!(sticky_headers(4.0), vec![]);
31710    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
31711    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
31712    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
31713    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
31714    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
31715    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
31716    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
31717    assert_eq!(sticky_headers(8.0), vec![]);
31718    assert_eq!(sticky_headers(8.5), vec![]);
31719    assert_eq!(sticky_headers(9.0), vec![]);
31720    assert_eq!(sticky_headers(9.5), vec![]);
31721    assert_eq!(sticky_headers(10.0), vec![]);
31722}
31723
31724#[gpui::test]
31725async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(
31726    executor: BackgroundExecutor,
31727    cx: &mut TestAppContext,
31728) {
31729    init_test(cx, |_| {});
31730    let mut cx = EditorTestContext::new(cx).await;
31731
31732    let diff_base = indoc! {"
31733        fn foo() {
31734            let a = 1;
31735            let b = 2;
31736            let c = 3;
31737            let d = 4;
31738            let e = 5;
31739        }
31740    "};
31741
31742    let buffer = indoc! {"
31743        ˇfn foo() {
31744        }
31745    "};
31746
31747    cx.set_state(&buffer);
31748
31749    cx.update_editor(|e, _, cx| {
31750        e.buffer()
31751            .read(cx)
31752            .as_singleton()
31753            .unwrap()
31754            .update(cx, |buffer, cx| {
31755                buffer.set_language(Some(rust_lang()), cx);
31756            })
31757    });
31758
31759    cx.set_head_text(diff_base);
31760    executor.run_until_parked();
31761
31762    cx.update_editor(|editor, window, cx| {
31763        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
31764    });
31765    executor.run_until_parked();
31766
31767    // After expanding, the display should look like:
31768    //   row 0: fn foo() {
31769    //   row 1: -    let a = 1;   (deleted)
31770    //   row 2: -    let b = 2;   (deleted)
31771    //   row 3: -    let c = 3;   (deleted)
31772    //   row 4: -    let d = 4;   (deleted)
31773    //   row 5: -    let e = 5;   (deleted)
31774    //   row 6: }
31775    //
31776    // fn foo() spans display rows 0-6. Scrolling into the deleted region
31777    // (rows 1-5) should still show fn foo() as a sticky header.
31778
31779    let fn_foo = Point { row: 0, column: 0 };
31780
31781    let mut sticky_headers = |offset: ScrollOffset| {
31782        cx.update_editor(|e, window, cx| {
31783            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31784        });
31785        cx.run_until_parked();
31786        cx.update_editor(|e, window, cx| {
31787            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31788                .into_iter()
31789                .map(
31790                    |StickyHeader {
31791                         start_point,
31792                         offset,
31793                         ..
31794                     }| { (start_point, offset) },
31795                )
31796                .collect::<Vec<_>>()
31797        })
31798    };
31799
31800    assert_eq!(sticky_headers(0.0), vec![]);
31801    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
31802    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
31803    // Scrolling into deleted lines: fn foo() should still be a sticky header.
31804    assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]);
31805    assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]);
31806    assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]);
31807    assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]);
31808    assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]);
31809    // Past the closing brace: no more sticky header.
31810    assert_eq!(sticky_headers(6.0), vec![]);
31811}
31812
31813#[gpui::test]
31814async fn test_no_duplicated_sticky_headers(cx: &mut TestAppContext) {
31815    init_test(cx, |_| {});
31816    let mut cx = EditorTestContext::new(cx).await;
31817
31818    cx.set_state(indoc! {"
31819        ˇimpl Foo { fn bar() {
31820            let x = 1;
31821            fn baz() {
31822                let y = 2;
31823            }
31824        } }
31825    "});
31826
31827    cx.update_editor(|e, _, cx| {
31828        e.buffer()
31829            .read(cx)
31830            .as_singleton()
31831            .unwrap()
31832            .update(cx, |buffer, cx| {
31833                buffer.set_language(Some(rust_lang()), cx);
31834            })
31835    });
31836
31837    let mut sticky_headers = |offset: ScrollOffset| {
31838        cx.update_editor(|e, window, cx| {
31839            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
31840        });
31841        cx.run_until_parked();
31842        cx.update_editor(|e, window, cx| {
31843            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
31844                .into_iter()
31845                .map(
31846                    |StickyHeader {
31847                         start_point,
31848                         offset,
31849                         ..
31850                     }| { (start_point, offset) },
31851                )
31852                .collect::<Vec<_>>()
31853        })
31854    };
31855
31856    let struct_foo = Point { row: 0, column: 0 };
31857    let fn_baz = Point { row: 2, column: 4 };
31858
31859    assert_eq!(sticky_headers(0.0), vec![]);
31860    assert_eq!(sticky_headers(0.5), vec![(struct_foo, 0.0)]);
31861    assert_eq!(sticky_headers(1.0), vec![(struct_foo, 0.0)]);
31862    assert_eq!(sticky_headers(1.5), vec![(struct_foo, 0.0), (fn_baz, 1.0)]);
31863    assert_eq!(sticky_headers(2.0), vec![(struct_foo, 0.0), (fn_baz, 1.0)]);
31864    assert_eq!(sticky_headers(2.5), vec![(struct_foo, 0.0), (fn_baz, 0.5)]);
31865    assert_eq!(sticky_headers(3.0), vec![(struct_foo, 0.0)]);
31866    assert_eq!(sticky_headers(3.5), vec![(struct_foo, 0.0)]);
31867    assert_eq!(sticky_headers(4.0), vec![(struct_foo, 0.0)]);
31868    assert_eq!(sticky_headers(4.5), vec![(struct_foo, -0.5)]);
31869    assert_eq!(sticky_headers(5.0), vec![]);
31870}
31871
31872#[gpui::test]
31873fn test_relative_line_numbers(cx: &mut TestAppContext) {
31874    init_test(cx, |_| {});
31875
31876    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
31877    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
31878    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
31879
31880    let multibuffer = cx.new(|cx| {
31881        let mut multibuffer = MultiBuffer::new(ReadWrite);
31882        multibuffer.set_excerpts_for_path(
31883            PathKey::sorted(0),
31884            buffer_1.clone(),
31885            [Point::new(0, 0)..Point::new(2, 0)],
31886            0,
31887            cx,
31888        );
31889        multibuffer.set_excerpts_for_path(
31890            PathKey::sorted(1),
31891            buffer_2.clone(),
31892            [Point::new(0, 0)..Point::new(2, 0)],
31893            0,
31894            cx,
31895        );
31896        multibuffer.set_excerpts_for_path(
31897            PathKey::sorted(2),
31898            buffer_3.clone(),
31899            [Point::new(0, 0)..Point::new(2, 0)],
31900            0,
31901            cx,
31902        );
31903        multibuffer
31904    });
31905
31906    // wrapped contents of multibuffer:
31907    //    aaa
31908    //    aaa
31909    //    aaa
31910    //    a
31911    //    bbb
31912    //
31913    //    ccc
31914    //    ccc
31915    //    ccc
31916    //    c
31917    //    ddd
31918    //
31919    //    eee
31920    //    fff
31921    //    fff
31922    //    fff
31923    //    f
31924
31925    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
31926    _ = editor.update(cx, |editor, window, cx| {
31927        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
31928
31929        // includes trailing newlines.
31930        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
31931        let expected_wrapped_line_numbers = [
31932            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
31933        ];
31934
31935        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31936            s.select_ranges([
31937                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
31938            ]);
31939        });
31940
31941        let snapshot = editor.snapshot(window, cx);
31942
31943        // these are all 0-indexed
31944        let base_display_row = DisplayRow(11);
31945        let base_row = 3;
31946        let wrapped_base_row = 7;
31947
31948        // test not counting wrapped lines
31949        let expected_relative_numbers = expected_line_numbers
31950            .into_iter()
31951            .enumerate()
31952            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
31953            .filter(|(_, relative_line_number)| *relative_line_number != 0)
31954            .collect_vec();
31955        let actual_relative_numbers = snapshot
31956            .calculate_relative_line_numbers(
31957                &(DisplayRow(0)..DisplayRow(24)),
31958                base_display_row,
31959                false,
31960            )
31961            .into_iter()
31962            .sorted()
31963            .collect_vec();
31964        assert_eq!(expected_relative_numbers, actual_relative_numbers);
31965        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
31966        for (display_row, relative_number) in expected_relative_numbers {
31967            assert_eq!(
31968                relative_number,
31969                snapshot
31970                    .relative_line_delta(display_row, base_display_row, false)
31971                    .unsigned_abs() as u32,
31972            );
31973        }
31974
31975        // test counting wrapped lines
31976        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
31977            .into_iter()
31978            .enumerate()
31979            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
31980            .filter(|(row, _)| *row != base_display_row)
31981            .collect_vec();
31982        let actual_relative_numbers = snapshot
31983            .calculate_relative_line_numbers(
31984                &(DisplayRow(0)..DisplayRow(24)),
31985                base_display_row,
31986                true,
31987            )
31988            .into_iter()
31989            .sorted()
31990            .collect_vec();
31991        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
31992        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
31993        for (display_row, relative_number) in expected_wrapped_relative_numbers {
31994            assert_eq!(
31995                relative_number,
31996                snapshot
31997                    .relative_line_delta(display_row, base_display_row, true)
31998                    .unsigned_abs() as u32,
31999            );
32000        }
32001    });
32002}
32003
32004#[gpui::test]
32005async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
32006    init_test(cx, |_| {});
32007    cx.update(|cx| {
32008        SettingsStore::update_global(cx, |store, cx| {
32009            store.update_user_settings(cx, |settings| {
32010                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
32011                    enabled: Some(true),
32012                })
32013            });
32014        });
32015    });
32016    let mut cx = EditorTestContext::new(cx).await;
32017
32018    let line_height = cx.update_editor(|editor, window, cx| {
32019        editor
32020            .style(cx)
32021            .text
32022            .line_height_in_pixels(window.rem_size())
32023    });
32024
32025    let buffer = indoc! {"
32026            ˇfn foo() {
32027                let abc = 123;
32028            }
32029            struct Bar;
32030            impl Bar {
32031                fn new() -> Self {
32032                    Self
32033                }
32034            }
32035            fn baz() {
32036            }
32037        "};
32038    cx.set_state(&buffer);
32039
32040    cx.update_editor(|e, _, cx| {
32041        e.buffer()
32042            .read(cx)
32043            .as_singleton()
32044            .unwrap()
32045            .update(cx, |buffer, cx| {
32046                buffer.set_language(Some(rust_lang()), cx);
32047            })
32048    });
32049
32050    let fn_foo = || empty_range(0, 0);
32051    let impl_bar = || empty_range(4, 0);
32052    let fn_new = || empty_range(5, 0);
32053
32054    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
32055        cx.update_editor(|e, window, cx| {
32056            e.scroll(
32057                gpui::Point {
32058                    x: 0.,
32059                    y: scroll_offset,
32060                },
32061                None,
32062                window,
32063                cx,
32064            );
32065        });
32066        cx.run_until_parked();
32067        cx.simulate_click(
32068            gpui::Point {
32069                x: px(0.),
32070                y: click_offset as f32 * line_height,
32071            },
32072            Modifiers::none(),
32073        );
32074        cx.run_until_parked();
32075        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
32076    };
32077    assert_eq!(
32078        scroll_and_click(
32079            4.5, // impl Bar is halfway off the screen
32080            0.0  // click top of screen
32081        ),
32082        // scrolled to impl Bar
32083        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32084    );
32085
32086    assert_eq!(
32087        scroll_and_click(
32088            4.5,  // impl Bar is halfway off the screen
32089            0.25  // click middle of impl Bar
32090        ),
32091        // scrolled to impl Bar
32092        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32093    );
32094
32095    assert_eq!(
32096        scroll_and_click(
32097            4.5, // impl Bar is halfway off the screen
32098            1.5  // click below impl Bar (e.g. fn new())
32099        ),
32100        // scrolled to fn new() - this is below the impl Bar header which has persisted
32101        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
32102    );
32103
32104    assert_eq!(
32105        scroll_and_click(
32106            5.5,  // fn new is halfway underneath impl Bar
32107            0.75  // click on the overlap of impl Bar and fn new()
32108        ),
32109        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
32110    );
32111
32112    assert_eq!(
32113        scroll_and_click(
32114            5.5,  // fn new is halfway underneath impl Bar
32115            1.25  // click on the visible part of fn new()
32116        ),
32117        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
32118    );
32119
32120    assert_eq!(
32121        scroll_and_click(
32122            1.5, // fn foo is halfway off the screen
32123            0.0  // click top of screen
32124        ),
32125        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
32126    );
32127
32128    assert_eq!(
32129        scroll_and_click(
32130            1.5,  // fn foo is halfway off the screen
32131            0.75  // click visible part of let abc...
32132        )
32133        .0,
32134        // no change in scroll
32135        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
32136        (gpui::Point { x: 0., y: 1.5 })
32137    );
32138
32139    // Verify clicking at a specific x position within a sticky header places
32140    // the cursor at the corresponding column.
32141    let (text_origin_x, em_width) = cx.update_editor(|editor, _, _| {
32142        let position_map = editor.last_position_map.as_ref().unwrap();
32143        (
32144            position_map.text_hitbox.bounds.origin.x,
32145            position_map.em_layout_width,
32146        )
32147    });
32148
32149    // Click on "impl Bar {" sticky header at column 5 (the 'B' in 'Bar').
32150    // The text "impl Bar {" starts at column 0, so column 5 = 'B'.
32151    let click_x = text_origin_x + em_width * 5.5;
32152    cx.update_editor(|e, window, cx| {
32153        e.scroll(gpui::Point { x: 0., y: 4.5 }, None, window, cx);
32154    });
32155    cx.run_until_parked();
32156    cx.simulate_click(
32157        gpui::Point {
32158            x: click_x,
32159            y: 0.25 * line_height,
32160        },
32161        Modifiers::none(),
32162    );
32163    cx.run_until_parked();
32164    let (scroll_pos, selections) =
32165        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)));
32166    assert_eq!(scroll_pos, gpui::Point { x: 0., y: 4. });
32167    assert_eq!(selections, vec![empty_range(4, 5)]);
32168}
32169
32170#[gpui::test]
32171async fn test_clicking_sticky_header_sets_character_select_mode(cx: &mut TestAppContext) {
32172    init_test(cx, |_| {});
32173    cx.update(|cx| {
32174        SettingsStore::update_global(cx, |store, cx| {
32175            store.update_user_settings(cx, |settings| {
32176                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
32177                    enabled: Some(true),
32178                })
32179            });
32180        });
32181    });
32182    let mut cx = EditorTestContext::new(cx).await;
32183
32184    let line_height = cx.update_editor(|editor, window, cx| {
32185        editor
32186            .style(cx)
32187            .text
32188            .line_height_in_pixels(window.rem_size())
32189    });
32190
32191    let buffer = indoc! {"
32192            fn foo() {
32193                let abc = 123;
32194            }
32195            ˇstruct Bar;
32196        "};
32197    cx.set_state(&buffer);
32198
32199    cx.update_editor(|editor, _, cx| {
32200        editor
32201            .buffer()
32202            .read(cx)
32203            .as_singleton()
32204            .unwrap()
32205            .update(cx, |buffer, cx| {
32206                buffer.set_language(Some(rust_lang()), cx);
32207            })
32208    });
32209
32210    let text_origin_x = cx.update_editor(|editor, _, _| {
32211        editor
32212            .last_position_map
32213            .as_ref()
32214            .unwrap()
32215            .text_hitbox
32216            .bounds
32217            .origin
32218            .x
32219    });
32220
32221    cx.update_editor(|editor, window, cx| {
32222        // Double click on `struct` to select it
32223        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 1), false, 2, window, cx);
32224        editor.end_selection(window, cx);
32225
32226        // Scroll down one row to make `fn foo() {` a sticky header
32227        editor.scroll(gpui::Point { x: 0., y: 1. }, None, window, cx);
32228    });
32229    cx.run_until_parked();
32230
32231    // Click at the start of the `fn foo() {` sticky header
32232    cx.simulate_click(
32233        gpui::Point {
32234            x: text_origin_x,
32235            y: 0.5 * line_height,
32236        },
32237        Modifiers::none(),
32238    );
32239    cx.run_until_parked();
32240
32241    // Shift-click at the end of `fn foo() {` to select the whole row
32242    cx.update_editor(|editor, window, cx| {
32243        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
32244        editor.end_selection(window, cx);
32245    });
32246    cx.run_until_parked();
32247
32248    let selections = cx.update_editor(|editor, _, cx| display_ranges(editor, cx));
32249    assert_eq!(
32250        selections,
32251        vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 10)]
32252    );
32253}
32254
32255#[gpui::test]
32256async fn test_next_prev_reference(cx: &mut TestAppContext) {
32257    const CYCLE_POSITIONS: &[&'static str] = &[
32258        indoc! {"
32259            fn foo() {
32260                let ˇabc = 123;
32261                let x = abc + 1;
32262                let y = abc + 2;
32263                let z = abc + 2;
32264            }
32265        "},
32266        indoc! {"
32267            fn foo() {
32268                let abc = 123;
32269                let x = ˇabc + 1;
32270                let y = abc + 2;
32271                let z = abc + 2;
32272            }
32273        "},
32274        indoc! {"
32275            fn foo() {
32276                let abc = 123;
32277                let x = abc + 1;
32278                let y = ˇabc + 2;
32279                let z = abc + 2;
32280            }
32281        "},
32282        indoc! {"
32283            fn foo() {
32284                let abc = 123;
32285                let x = abc + 1;
32286                let y = abc + 2;
32287                let z = ˇabc + 2;
32288            }
32289        "},
32290    ];
32291
32292    init_test(cx, |_| {});
32293
32294    let mut cx = EditorLspTestContext::new_rust(
32295        lsp::ServerCapabilities {
32296            references_provider: Some(lsp::OneOf::Left(true)),
32297            ..Default::default()
32298        },
32299        cx,
32300    )
32301    .await;
32302
32303    // importantly, the cursor is in the middle
32304    cx.set_state(indoc! {"
32305        fn foo() {
32306            let aˇbc = 123;
32307            let x = abc + 1;
32308            let y = abc + 2;
32309            let z = abc + 2;
32310        }
32311    "});
32312
32313    let reference_ranges = [
32314        lsp::Position::new(1, 8),
32315        lsp::Position::new(2, 12),
32316        lsp::Position::new(3, 12),
32317        lsp::Position::new(4, 12),
32318    ]
32319    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
32320
32321    cx.lsp
32322        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
32323            Ok(Some(
32324                reference_ranges
32325                    .map(|range| lsp::Location {
32326                        uri: params.text_document_position.text_document.uri.clone(),
32327                        range,
32328                    })
32329                    .to_vec(),
32330            ))
32331        });
32332
32333    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
32334        cx.update_editor(|editor, window, cx| {
32335            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
32336        })
32337        .unwrap()
32338        .await
32339        .unwrap()
32340    };
32341
32342    _move(Direction::Next, 1, &mut cx).await;
32343    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32344
32345    _move(Direction::Next, 1, &mut cx).await;
32346    cx.assert_editor_state(CYCLE_POSITIONS[2]);
32347
32348    _move(Direction::Next, 1, &mut cx).await;
32349    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32350
32351    // loops back to the start
32352    _move(Direction::Next, 1, &mut cx).await;
32353    cx.assert_editor_state(CYCLE_POSITIONS[0]);
32354
32355    // loops back to the end
32356    _move(Direction::Prev, 1, &mut cx).await;
32357    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32358
32359    _move(Direction::Prev, 1, &mut cx).await;
32360    cx.assert_editor_state(CYCLE_POSITIONS[2]);
32361
32362    _move(Direction::Prev, 1, &mut cx).await;
32363    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32364
32365    _move(Direction::Prev, 1, &mut cx).await;
32366    cx.assert_editor_state(CYCLE_POSITIONS[0]);
32367
32368    _move(Direction::Next, 3, &mut cx).await;
32369    cx.assert_editor_state(CYCLE_POSITIONS[3]);
32370
32371    _move(Direction::Prev, 2, &mut cx).await;
32372    cx.assert_editor_state(CYCLE_POSITIONS[1]);
32373}
32374
32375#[gpui::test]
32376async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
32377    init_test(cx, |_| {});
32378
32379    let (editor, cx) = cx.add_window_view(|window, cx| {
32380        let multi_buffer = MultiBuffer::build_multi(
32381            [
32382                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32383                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32384            ],
32385            cx,
32386        );
32387        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32388    });
32389
32390    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
32391    let buffer_ids = cx.multibuffer(|mb, cx| {
32392        mb.snapshot(cx)
32393            .excerpts()
32394            .map(|excerpt| excerpt.context.start.buffer_id)
32395            .collect::<Vec<_>>()
32396    });
32397
32398    cx.assert_excerpts_with_selections(indoc! {"
32399        [EXCERPT]
32400        ˇ1
32401        2
32402        3
32403        [EXCERPT]
32404        1
32405        2
32406        3
32407        "});
32408
32409    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
32410    cx.update_editor(|editor, window, cx| {
32411        editor.change_selections(None.into(), window, cx, |s| {
32412            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32413        });
32414    });
32415    cx.assert_excerpts_with_selections(indoc! {"
32416        [EXCERPT]
32417        1
3241832419        3
32420        [EXCERPT]
32421        1
32422        2
32423        3
32424        "});
32425
32426    cx.update_editor(|editor, window, cx| {
32427        editor
32428            .select_all_matches(&SelectAllMatches, window, cx)
32429            .unwrap();
32430    });
32431    cx.assert_excerpts_with_selections(indoc! {"
32432        [EXCERPT]
32433        1
3243432435        3
32436        [EXCERPT]
32437        1
3243832439        3
32440        "});
32441
32442    cx.update_editor(|editor, window, cx| {
32443        editor.handle_input("X", window, cx);
32444    });
32445    cx.assert_excerpts_with_selections(indoc! {"
32446        [EXCERPT]
32447        1
3244832449        3
32450        [EXCERPT]
32451        1
3245232453        3
32454        "});
32455
32456    // Scenario 2: Select "2", then fold second buffer before insertion
32457    cx.update_multibuffer(|mb, cx| {
32458        for buffer_id in buffer_ids.iter() {
32459            let buffer = mb.buffer(*buffer_id).unwrap();
32460            buffer.update(cx, |buffer, cx| {
32461                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
32462            });
32463        }
32464    });
32465
32466    // Select "2" and select all matches
32467    cx.update_editor(|editor, window, cx| {
32468        editor.change_selections(None.into(), window, cx, |s| {
32469            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32470        });
32471        editor
32472            .select_all_matches(&SelectAllMatches, window, cx)
32473            .unwrap();
32474    });
32475
32476    // Fold second buffer - should remove selections from folded buffer
32477    cx.update_editor(|editor, _, cx| {
32478        editor.fold_buffer(buffer_ids[1], cx);
32479    });
32480    cx.assert_excerpts_with_selections(indoc! {"
32481        [EXCERPT]
32482        1
3248332484        3
32485        [EXCERPT]
32486        [FOLDED]
32487        "});
32488
32489    // Insert text - should only affect first buffer
32490    cx.update_editor(|editor, window, cx| {
32491        editor.handle_input("Y", window, cx);
32492    });
32493    cx.update_editor(|editor, _, cx| {
32494        editor.unfold_buffer(buffer_ids[1], cx);
32495    });
32496    cx.assert_excerpts_with_selections(indoc! {"
32497        [EXCERPT]
32498        1
3249932500        3
32501        [EXCERPT]
32502        1
32503        2
32504        3
32505        "});
32506
32507    // Scenario 3: Select "2", then fold first buffer before insertion
32508    cx.update_multibuffer(|mb, cx| {
32509        for buffer_id in buffer_ids.iter() {
32510            let buffer = mb.buffer(*buffer_id).unwrap();
32511            buffer.update(cx, |buffer, cx| {
32512                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
32513            });
32514        }
32515    });
32516
32517    // Select "2" and select all matches
32518    cx.update_editor(|editor, window, cx| {
32519        editor.change_selections(None.into(), window, cx, |s| {
32520            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
32521        });
32522        editor
32523            .select_all_matches(&SelectAllMatches, window, cx)
32524            .unwrap();
32525    });
32526
32527    // Fold first buffer - should remove selections from folded buffer
32528    cx.update_editor(|editor, _, cx| {
32529        editor.fold_buffer(buffer_ids[0], cx);
32530    });
32531    cx.assert_excerpts_with_selections(indoc! {"
32532        [EXCERPT]
32533        [FOLDED]
32534        [EXCERPT]
32535        1
3253632537        3
32538        "});
32539
32540    // Insert text - should only affect second buffer
32541    cx.update_editor(|editor, window, cx| {
32542        editor.handle_input("Z", window, cx);
32543    });
32544    cx.update_editor(|editor, _, cx| {
32545        editor.unfold_buffer(buffer_ids[0], cx);
32546    });
32547    cx.assert_excerpts_with_selections(indoc! {"
32548        [EXCERPT]
32549        1
32550        2
32551        3
32552        [EXCERPT]
32553        1
3255432555        3
32556        "});
32557
32558    // Test correct folded header is selected upon fold
32559    cx.update_editor(|editor, _, cx| {
32560        editor.fold_buffer(buffer_ids[0], cx);
32561        editor.fold_buffer(buffer_ids[1], cx);
32562    });
32563    cx.assert_excerpts_with_selections(indoc! {"
32564        [EXCERPT]
32565        [FOLDED]
32566        [EXCERPT]
32567        ˇ[FOLDED]
32568        "});
32569
32570    // Test selection inside folded buffer unfolds it on type
32571    cx.update_editor(|editor, window, cx| {
32572        editor.handle_input("W", window, cx);
32573    });
32574    cx.update_editor(|editor, _, cx| {
32575        editor.unfold_buffer(buffer_ids[0], cx);
32576    });
32577    cx.assert_excerpts_with_selections(indoc! {"
32578        [EXCERPT]
32579        1
32580        2
32581        3
32582        [EXCERPT]
32583        Wˇ1
32584        Z
32585        3
32586        "});
32587}
32588
32589#[gpui::test]
32590async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
32591    init_test(cx, |_| {});
32592
32593    let (editor, cx) = cx.add_window_view(|window, cx| {
32594        let multi_buffer = MultiBuffer::build_multi(
32595            [
32596                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
32597                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
32598            ],
32599            cx,
32600        );
32601        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32602    });
32603
32604    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
32605
32606    cx.assert_excerpts_with_selections(indoc! {"
32607        [EXCERPT]
32608        ˇ1
32609        2
32610        3
32611        [EXCERPT]
32612        1
32613        2
32614        3
32615        4
32616        5
32617        6
32618        7
32619        8
32620        9
32621        "});
32622
32623    cx.update_editor(|editor, window, cx| {
32624        editor.change_selections(None.into(), window, cx, |s| {
32625            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
32626        });
32627    });
32628
32629    cx.assert_excerpts_with_selections(indoc! {"
32630        [EXCERPT]
32631        1
32632        2
32633        3
32634        [EXCERPT]
32635        1
32636        2
32637        3
32638        4
32639        5
32640        6
32641        ˇ7
32642        8
32643        9
32644        "});
32645
32646    cx.update_editor(|editor, _window, cx| {
32647        editor.set_vertical_scroll_margin(0, cx);
32648    });
32649
32650    cx.update_editor(|editor, window, cx| {
32651        assert_eq!(editor.vertical_scroll_margin(), 0);
32652        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
32653        assert_eq!(
32654            editor.snapshot(window, cx).scroll_position(),
32655            gpui::Point::new(0., 12.0)
32656        );
32657    });
32658
32659    cx.update_editor(|editor, _window, cx| {
32660        editor.set_vertical_scroll_margin(3, cx);
32661    });
32662
32663    cx.update_editor(|editor, window, cx| {
32664        assert_eq!(editor.vertical_scroll_margin(), 3);
32665        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
32666        assert_eq!(
32667            editor.snapshot(window, cx).scroll_position(),
32668            gpui::Point::new(0., 9.0)
32669        );
32670    });
32671}
32672
32673#[gpui::test]
32674async fn test_find_references_single_case(cx: &mut TestAppContext) {
32675    init_test(cx, |_| {});
32676    let mut cx = EditorLspTestContext::new_rust(
32677        lsp::ServerCapabilities {
32678            references_provider: Some(lsp::OneOf::Left(true)),
32679            ..lsp::ServerCapabilities::default()
32680        },
32681        cx,
32682    )
32683    .await;
32684
32685    let before = indoc!(
32686        r#"
32687        fn main() {
32688            let aˇbc = 123;
32689            let xyz = abc;
32690        }
32691        "#
32692    );
32693    let after = indoc!(
32694        r#"
32695        fn main() {
32696            let abc = 123;
32697            let xyz = ˇabc;
32698        }
32699        "#
32700    );
32701
32702    cx.lsp
32703        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
32704            Ok(Some(vec![
32705                lsp::Location {
32706                    uri: params.text_document_position.text_document.uri.clone(),
32707                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
32708                },
32709                lsp::Location {
32710                    uri: params.text_document_position.text_document.uri,
32711                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
32712                },
32713            ]))
32714        });
32715
32716    cx.set_state(before);
32717
32718    let action = FindAllReferences {
32719        always_open_multibuffer: false,
32720    };
32721
32722    let navigated = cx
32723        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
32724        .expect("should have spawned a task")
32725        .await
32726        .unwrap();
32727
32728    assert_eq!(navigated, Navigated::No);
32729
32730    cx.run_until_parked();
32731
32732    cx.assert_editor_state(after);
32733}
32734
32735#[gpui::test]
32736async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
32737    init_test(cx, |settings| {
32738        settings.defaults.tab_size = Some(2.try_into().unwrap());
32739    });
32740
32741    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
32742    let mut cx = EditorTestContext::new(cx).await;
32743    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
32744
32745    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
32746    cx.set_state(indoc! {"
32747        - [ ] taskˇ
32748    "});
32749    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32750    cx.wait_for_autoindent_applied().await;
32751    cx.assert_editor_state(indoc! {"
32752        - [ ] task
32753        - [ ] ˇ
32754    "});
32755
32756    // Case 2: Works with checked task items too
32757    cx.set_state(indoc! {"
32758        - [x] completed taskˇ
32759    "});
32760    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32761    cx.wait_for_autoindent_applied().await;
32762    cx.assert_editor_state(indoc! {"
32763        - [x] completed task
32764        - [ ] ˇ
32765    "});
32766
32767    // Case 2.1: Works with uppercase checked marker too
32768    cx.set_state(indoc! {"
32769        - [X] completed taskˇ
32770    "});
32771    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32772    cx.wait_for_autoindent_applied().await;
32773    cx.assert_editor_state(indoc! {"
32774        - [X] completed task
32775        - [ ] ˇ
32776    "});
32777
32778    // Case 3: Cursor position doesn't matter - content after marker is what counts
32779    cx.set_state(indoc! {"
32780        - [ ] taˇsk
32781    "});
32782    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32783    cx.wait_for_autoindent_applied().await;
32784    cx.assert_editor_state(indoc! {"
32785        - [ ] ta
32786        - [ ] ˇsk
32787    "});
32788
32789    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
32790    cx.set_state(indoc! {"
32791        - [ ]  ˇ
32792    "});
32793    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32794    cx.wait_for_autoindent_applied().await;
32795    cx.assert_editor_state(
32796        indoc! {"
32797        - [ ]$$
32798        ˇ
32799    "}
32800        .replace("$", " ")
32801        .as_str(),
32802    );
32803
32804    // Case 5: Adding newline with content adds marker preserving indentation
32805    cx.set_state(indoc! {"
32806        - [ ] task
32807          - [ ] indentedˇ
32808    "});
32809    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32810    cx.wait_for_autoindent_applied().await;
32811    cx.assert_editor_state(indoc! {"
32812        - [ ] task
32813          - [ ] indented
32814          - [ ] ˇ
32815    "});
32816
32817    // Case 6: Adding newline with cursor right after prefix, unindents
32818    cx.set_state(indoc! {"
32819        - [ ] task
32820          - [ ] sub task
32821            - [ ] ˇ
32822    "});
32823    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32824    cx.wait_for_autoindent_applied().await;
32825    cx.assert_editor_state(indoc! {"
32826        - [ ] task
32827          - [ ] sub task
32828          - [ ] ˇ
32829    "});
32830    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32831    cx.wait_for_autoindent_applied().await;
32832
32833    // Case 7: Adding newline with cursor right after prefix, removes marker
32834    cx.assert_editor_state(indoc! {"
32835        - [ ] task
32836          - [ ] sub task
32837        - [ ] ˇ
32838    "});
32839    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32840    cx.wait_for_autoindent_applied().await;
32841    cx.assert_editor_state(indoc! {"
32842        - [ ] task
32843          - [ ] sub task
32844        ˇ
32845    "});
32846
32847    // Case 8: Cursor before or inside prefix does not add marker
32848    cx.set_state(indoc! {"
32849        ˇ- [ ] task
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
32855        ˇ- [ ] task
32856    "});
32857
32858    cx.set_state(indoc! {"
32859        - [ˇ ] task
32860    "});
32861    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32862    cx.wait_for_autoindent_applied().await;
32863    cx.assert_editor_state(indoc! {"
32864        - [
32865        ˇ
32866        ] task
32867    "});
32868}
32869
32870#[gpui::test]
32871async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
32872    init_test(cx, |settings| {
32873        settings.defaults.tab_size = Some(2.try_into().unwrap());
32874    });
32875
32876    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
32877    let mut cx = EditorTestContext::new(cx).await;
32878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
32879
32880    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
32881    cx.set_state(indoc! {"
32882        - itemˇ
32883    "});
32884    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32885    cx.wait_for_autoindent_applied().await;
32886    cx.assert_editor_state(indoc! {"
32887        - item
32888        - ˇ
32889    "});
32890
32891    // Case 2: Works with different markers
32892    cx.set_state(indoc! {"
32893        * starred itemˇ
32894    "});
32895    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32896    cx.wait_for_autoindent_applied().await;
32897    cx.assert_editor_state(indoc! {"
32898        * starred item
32899        * ˇ
32900    "});
32901
32902    cx.set_state(indoc! {"
32903        + plus itemˇ
32904    "});
32905    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32906    cx.wait_for_autoindent_applied().await;
32907    cx.assert_editor_state(indoc! {"
32908        + plus item
32909        + ˇ
32910    "});
32911
32912    // Case 3: Cursor position doesn't matter - content after marker is what counts
32913    cx.set_state(indoc! {"
32914        - itˇem
32915    "});
32916    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32917    cx.wait_for_autoindent_applied().await;
32918    cx.assert_editor_state(indoc! {"
32919        - it
32920        - ˇem
32921    "});
32922
32923    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
32924    cx.set_state(indoc! {"
32925        -  ˇ
32926    "});
32927    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32928    cx.wait_for_autoindent_applied().await;
32929    cx.assert_editor_state(
32930        indoc! {"
32931        - $
32932        ˇ
32933    "}
32934        .replace("$", " ")
32935        .as_str(),
32936    );
32937
32938    // Case 5: Adding newline with content adds marker preserving indentation
32939    cx.set_state(indoc! {"
32940        - item
32941          - indentedˇ
32942    "});
32943    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32944    cx.wait_for_autoindent_applied().await;
32945    cx.assert_editor_state(indoc! {"
32946        - item
32947          - indented
32948          - ˇ
32949    "});
32950
32951    // Case 6: Adding newline with cursor right after marker, unindents
32952    cx.set_state(indoc! {"
32953        - item
32954          - sub item
32955            - ˇ
32956    "});
32957    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32958    cx.wait_for_autoindent_applied().await;
32959    cx.assert_editor_state(indoc! {"
32960        - item
32961          - sub item
32962          - ˇ
32963    "});
32964    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32965    cx.wait_for_autoindent_applied().await;
32966
32967    // Case 7: Adding newline with cursor right after marker, removes marker
32968    cx.assert_editor_state(indoc! {"
32969        - item
32970          - sub item
32971        - ˇ
32972    "});
32973    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32974    cx.wait_for_autoindent_applied().await;
32975    cx.assert_editor_state(indoc! {"
32976        - item
32977          - sub item
32978        ˇ
32979    "});
32980
32981    // Case 8: Cursor before or inside prefix does not add marker
32982    cx.set_state(indoc! {"
32983        ˇ- item
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
32989        ˇ- item
32990    "});
32991
32992    cx.set_state(indoc! {"
32993        -ˇ item
32994    "});
32995    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
32996    cx.wait_for_autoindent_applied().await;
32997    cx.assert_editor_state(indoc! {"
32998        -
32999        ˇitem
33000    "});
33001}
33002
33003#[gpui::test]
33004async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
33005    init_test(cx, |settings| {
33006        settings.defaults.tab_size = Some(2.try_into().unwrap());
33007    });
33008
33009    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33010    let mut cx = EditorTestContext::new(cx).await;
33011    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33012
33013    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
33014    cx.set_state(indoc! {"
33015        1. first itemˇ
33016    "});
33017    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33018    cx.wait_for_autoindent_applied().await;
33019    cx.assert_editor_state(indoc! {"
33020        1. first item
33021        2. ˇ
33022    "});
33023
33024    // Case 2: Works with larger numbers
33025    cx.set_state(indoc! {"
33026        10. tenth itemˇ
33027    "});
33028    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33029    cx.wait_for_autoindent_applied().await;
33030    cx.assert_editor_state(indoc! {"
33031        10. tenth item
33032        11. ˇ
33033    "});
33034
33035    // Case 3: Cursor position doesn't matter - content after marker is what counts
33036    cx.set_state(indoc! {"
33037        1. itˇem
33038    "});
33039    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33040    cx.wait_for_autoindent_applied().await;
33041    cx.assert_editor_state(indoc! {"
33042        1. it
33043        2. ˇem
33044    "});
33045
33046    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
33047    cx.set_state(indoc! {"
33048        1.  ˇ
33049    "});
33050    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33051    cx.wait_for_autoindent_applied().await;
33052    cx.assert_editor_state(
33053        indoc! {"
33054        1. $
33055        ˇ
33056    "}
33057        .replace("$", " ")
33058        .as_str(),
33059    );
33060
33061    // Case 5: Adding newline with content adds marker preserving indentation
33062    cx.set_state(indoc! {"
33063        1. item
33064          2. indentedˇ
33065    "});
33066    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33067    cx.wait_for_autoindent_applied().await;
33068    cx.assert_editor_state(indoc! {"
33069        1. item
33070          2. indented
33071          3. ˇ
33072    "});
33073
33074    // Case 6: Adding newline with cursor right after marker, unindents
33075    cx.set_state(indoc! {"
33076        1. item
33077          2. sub item
33078            3. ˇ
33079    "});
33080    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33081    cx.wait_for_autoindent_applied().await;
33082    cx.assert_editor_state(indoc! {"
33083        1. item
33084          2. sub item
33085          1. ˇ
33086    "});
33087    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33088    cx.wait_for_autoindent_applied().await;
33089
33090    // Case 7: Adding newline with cursor right after marker, removes marker
33091    cx.assert_editor_state(indoc! {"
33092        1. item
33093          2. sub item
33094        1. ˇ
33095    "});
33096    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33097    cx.wait_for_autoindent_applied().await;
33098    cx.assert_editor_state(indoc! {"
33099        1. item
33100          2. sub item
33101        ˇ
33102    "});
33103
33104    // Case 8: Cursor before or inside prefix does not add marker
33105    cx.set_state(indoc! {"
33106        ˇ1. item
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
33112        ˇ1. item
33113    "});
33114
33115    cx.set_state(indoc! {"
33116        1ˇ. item
33117    "});
33118    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33119    cx.wait_for_autoindent_applied().await;
33120    cx.assert_editor_state(indoc! {"
33121        1
33122        ˇ. item
33123    "});
33124}
33125
33126#[gpui::test]
33127async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
33128    init_test(cx, |settings| {
33129        settings.defaults.tab_size = Some(2.try_into().unwrap());
33130    });
33131
33132    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33133    let mut cx = EditorTestContext::new(cx).await;
33134    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33135
33136    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
33137    cx.set_state(indoc! {"
33138        1. first item
33139          1. sub first item
33140          2. sub second item
33141          3. ˇ
33142    "});
33143    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
33144    cx.wait_for_autoindent_applied().await;
33145    cx.assert_editor_state(indoc! {"
33146        1. first item
33147          1. sub first item
33148          2. sub second item
33149        1. ˇ
33150    "});
33151}
33152
33153#[gpui::test]
33154async fn test_tab_list_indent(cx: &mut TestAppContext) {
33155    init_test(cx, |settings| {
33156        settings.defaults.tab_size = Some(2.try_into().unwrap());
33157    });
33158
33159    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
33160    let mut cx = EditorTestContext::new(cx).await;
33161    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
33162
33163    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
33164    cx.set_state(indoc! {"
33165        - ˇitem
33166    "});
33167    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33168    cx.wait_for_autoindent_applied().await;
33169    let expected = indoc! {"
33170        $$- ˇitem
33171    "};
33172    cx.assert_editor_state(expected.replace("$", " ").as_str());
33173
33174    // Case 2: Task list - cursor after prefix
33175    cx.set_state(indoc! {"
33176        - [ ] ˇtask
33177    "});
33178    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33179    cx.wait_for_autoindent_applied().await;
33180    let expected = indoc! {"
33181        $$- [ ] ˇtask
33182    "};
33183    cx.assert_editor_state(expected.replace("$", " ").as_str());
33184
33185    // Case 3: Ordered list - cursor after prefix
33186    cx.set_state(indoc! {"
33187        1. ˇfirst
33188    "});
33189    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33190    cx.wait_for_autoindent_applied().await;
33191    let expected = indoc! {"
33192        $$1. ˇfirst
33193    "};
33194    cx.assert_editor_state(expected.replace("$", " ").as_str());
33195
33196    // Case 4: With existing indentation - adds more indent
33197    let initial = indoc! {"
33198        $$- ˇitem
33199    "};
33200    cx.set_state(initial.replace("$", " ").as_str());
33201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33202    cx.wait_for_autoindent_applied().await;
33203    let expected = indoc! {"
33204        $$$$- ˇitem
33205    "};
33206    cx.assert_editor_state(expected.replace("$", " ").as_str());
33207
33208    // Case 5: Empty list item
33209    cx.set_state(indoc! {"
33210        - ˇ
33211    "});
33212    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33213    cx.wait_for_autoindent_applied().await;
33214    let expected = indoc! {"
33215        $$- ˇ
33216    "};
33217    cx.assert_editor_state(expected.replace("$", " ").as_str());
33218
33219    // Case 6: Cursor at end of line with content
33220    cx.set_state(indoc! {"
33221        - itemˇ
33222    "});
33223    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33224    cx.wait_for_autoindent_applied().await;
33225    let expected = indoc! {"
33226        $$- itemˇ
33227    "};
33228    cx.assert_editor_state(expected.replace("$", " ").as_str());
33229
33230    // Case 7: Cursor at start of list item, indents it
33231    cx.set_state(indoc! {"
33232        - item
33233        ˇ  - sub item
33234    "});
33235    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33236    cx.wait_for_autoindent_applied().await;
33237    let expected = indoc! {"
33238        - item
33239          ˇ  - sub item
33240    "};
33241    cx.assert_editor_state(expected);
33242
33243    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
33244    cx.update_editor(|_, _, cx| {
33245        SettingsStore::update_global(cx, |store, cx| {
33246            store.update_user_settings(cx, |settings| {
33247                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
33248            });
33249        });
33250    });
33251    cx.set_state(indoc! {"
33252        - item
33253        ˇ  - sub item
33254    "});
33255    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
33256    cx.wait_for_autoindent_applied().await;
33257    let expected = indoc! {"
33258        - item
33259          ˇ- sub item
33260    "};
33261    cx.assert_editor_state(expected);
33262}
33263
33264#[gpui::test]
33265async fn test_local_worktree_trust(cx: &mut TestAppContext) {
33266    init_test(cx, |_| {});
33267    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
33268
33269    cx.update(|cx| {
33270        SettingsStore::update_global(cx, |store, cx| {
33271            store.update_user_settings(cx, |settings| {
33272                settings.project.all_languages.defaults.inlay_hints =
33273                    Some(InlayHintSettingsContent {
33274                        enabled: Some(true),
33275                        ..InlayHintSettingsContent::default()
33276                    });
33277            });
33278        });
33279    });
33280
33281    let fs = FakeFs::new(cx.executor());
33282    fs.insert_tree(
33283        path!("/project"),
33284        json!({
33285            ".zed": {
33286                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
33287            },
33288            "main.rs": "fn main() {}"
33289        }),
33290    )
33291    .await;
33292
33293    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
33294    let server_name = "override-rust-analyzer";
33295    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
33296
33297    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
33298    language_registry.add(rust_lang());
33299
33300    let capabilities = lsp::ServerCapabilities {
33301        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
33302        ..lsp::ServerCapabilities::default()
33303    };
33304    let mut fake_language_servers = language_registry.register_fake_lsp(
33305        "Rust",
33306        FakeLspAdapter {
33307            name: server_name,
33308            capabilities,
33309            initializer: Some(Box::new({
33310                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
33311                move |fake_server| {
33312                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
33313                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
33314                        move |_params, _| {
33315                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
33316                            async move {
33317                                Ok(Some(vec![lsp::InlayHint {
33318                                    position: lsp::Position::new(0, 0),
33319                                    label: lsp::InlayHintLabel::String("hint".to_string()),
33320                                    kind: None,
33321                                    text_edits: None,
33322                                    tooltip: None,
33323                                    padding_left: None,
33324                                    padding_right: None,
33325                                    data: None,
33326                                }]))
33327                            }
33328                        },
33329                    );
33330                }
33331            })),
33332            ..FakeLspAdapter::default()
33333        },
33334    );
33335
33336    cx.run_until_parked();
33337
33338    let worktree_id = project.read_with(cx, |project, cx| {
33339        project
33340            .worktrees(cx)
33341            .next()
33342            .map(|wt| wt.read(cx).id())
33343            .expect("should have a worktree")
33344    });
33345    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
33346
33347    let trusted_worktrees =
33348        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
33349
33350    let can_trust = trusted_worktrees.update(cx, |store, cx| {
33351        store.can_trust(&worktree_store, worktree_id, cx)
33352    });
33353    assert!(!can_trust, "worktree should be restricted initially");
33354
33355    let buffer_before_approval = project
33356        .update(cx, |project, cx| {
33357            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
33358        })
33359        .await
33360        .unwrap();
33361
33362    let (editor, cx) = cx.add_window_view(|window, cx| {
33363        Editor::new(
33364            EditorMode::full(),
33365            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
33366            Some(project.clone()),
33367            window,
33368            cx,
33369        )
33370    });
33371    cx.run_until_parked();
33372    let fake_language_server = fake_language_servers.next();
33373
33374    cx.read(|cx| {
33375        assert_eq!(
33376            language::language_settings::LanguageSettings::for_buffer(
33377                buffer_before_approval.read(cx),
33378                cx
33379            )
33380            .language_servers,
33381            ["...".to_string()],
33382            "local .zed/settings.json must not apply before trust approval"
33383        )
33384    });
33385
33386    editor.update_in(cx, |editor, window, cx| {
33387        editor.handle_input("1", window, cx);
33388    });
33389    cx.run_until_parked();
33390    cx.executor()
33391        .advance_clock(std::time::Duration::from_secs(1));
33392    assert_eq!(
33393        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
33394        0,
33395        "inlay hints must not be queried before trust approval"
33396    );
33397
33398    trusted_worktrees.update(cx, |store, cx| {
33399        store.trust(
33400            &worktree_store,
33401            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
33402            cx,
33403        );
33404    });
33405    cx.run_until_parked();
33406
33407    cx.read(|cx| {
33408        assert_eq!(
33409            language::language_settings::LanguageSettings::for_buffer(
33410                buffer_before_approval.read(cx),
33411                cx
33412            )
33413            .language_servers,
33414            ["override-rust-analyzer".to_string()],
33415            "local .zed/settings.json should apply after trust approval"
33416        )
33417    });
33418    let _fake_language_server = fake_language_server.await.unwrap();
33419    editor.update_in(cx, |editor, window, cx| {
33420        editor.handle_input("1", window, cx);
33421    });
33422    cx.run_until_parked();
33423    cx.executor()
33424        .advance_clock(std::time::Duration::from_secs(1));
33425    assert!(
33426        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
33427        "inlay hints should be queried after trust approval"
33428    );
33429
33430    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
33431        store.can_trust(&worktree_store, worktree_id, cx)
33432    });
33433    assert!(can_trust_after, "worktree should be trusted after trust()");
33434}
33435
33436#[gpui::test]
33437fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
33438    // This test reproduces a bug where drawing an editor at a position above the viewport
33439    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
33440    // causes an infinite loop in blocks_in_range.
33441    //
33442    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
33443    // the content mask intersection produces visible_bounds with origin at the viewport top.
33444    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
33445    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
33446    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
33447    init_test(cx, |_| {});
33448
33449    let window = cx.add_window(|_, _| gpui::Empty);
33450    let mut cx = VisualTestContext::from_window(*window, cx);
33451
33452    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
33453    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
33454
33455    // Simulate a small viewport (500x500 pixels at origin 0,0)
33456    cx.simulate_resize(gpui::size(px(500.), px(500.)));
33457
33458    // Draw the editor at a very negative Y position, simulating an editor that's been
33459    // scrolled way above the visible viewport (like in a List that has scrolled past it).
33460    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
33461    // This should NOT hang - it should just render nothing.
33462    cx.draw(
33463        gpui::point(px(0.), px(-10000.)),
33464        gpui::size(px(500.), px(3000.)),
33465        |_, _| editor.clone().into_any_element(),
33466    );
33467
33468    // If we get here without hanging, the test passes
33469}
33470
33471#[gpui::test]
33472async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
33473    init_test(cx, |_| {});
33474
33475    let fs = FakeFs::new(cx.executor());
33476    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33477        .await;
33478
33479    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33480    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33481    let workspace = window
33482        .read_with(cx, |mw, _| mw.workspace().clone())
33483        .unwrap();
33484    let cx = &mut VisualTestContext::from_window(*window, cx);
33485
33486    let editor = workspace
33487        .update_in(cx, |workspace, window, cx| {
33488            workspace.open_abs_path(
33489                PathBuf::from(path!("/root/file.txt")),
33490                OpenOptions::default(),
33491                window,
33492                cx,
33493            )
33494        })
33495        .await
33496        .unwrap()
33497        .downcast::<Editor>()
33498        .unwrap();
33499
33500    // Enable diff review button mode
33501    editor.update(cx, |editor, cx| {
33502        editor.set_show_diff_review_button(true, cx);
33503    });
33504
33505    // Initially, no indicator should be present
33506    editor.update(cx, |editor, _cx| {
33507        assert!(
33508            editor.gutter_diff_review_indicator.0.is_none(),
33509            "Indicator should be None initially"
33510        );
33511    });
33512}
33513
33514#[gpui::test]
33515async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
33516    init_test(cx, |_| {});
33517
33518    // Register DisableAiSettings and set disable_ai to true
33519    cx.update(|cx| {
33520        project::DisableAiSettings::register(cx);
33521        project::DisableAiSettings::override_global(
33522            project::DisableAiSettings { disable_ai: true },
33523            cx,
33524        );
33525    });
33526
33527    let fs = FakeFs::new(cx.executor());
33528    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33529        .await;
33530
33531    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33532    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33533    let workspace = window
33534        .read_with(cx, |mw, _| mw.workspace().clone())
33535        .unwrap();
33536    let cx = &mut VisualTestContext::from_window(*window, cx);
33537
33538    let editor = workspace
33539        .update_in(cx, |workspace, window, cx| {
33540            workspace.open_abs_path(
33541                PathBuf::from(path!("/root/file.txt")),
33542                OpenOptions::default(),
33543                window,
33544                cx,
33545            )
33546        })
33547        .await
33548        .unwrap()
33549        .downcast::<Editor>()
33550        .unwrap();
33551
33552    // Enable diff review button mode
33553    editor.update(cx, |editor, cx| {
33554        editor.set_show_diff_review_button(true, cx);
33555    });
33556
33557    // Verify AI is disabled
33558    cx.read(|cx| {
33559        assert!(
33560            project::DisableAiSettings::get_global(cx).disable_ai,
33561            "AI should be disabled"
33562        );
33563    });
33564
33565    // The indicator should not be created when AI is disabled
33566    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
33567    editor.update(cx, |editor, _cx| {
33568        assert!(
33569            editor.gutter_diff_review_indicator.0.is_none(),
33570            "Indicator should be None when AI is disabled"
33571        );
33572    });
33573}
33574
33575#[gpui::test]
33576async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
33577    init_test(cx, |_| {});
33578
33579    // Register DisableAiSettings and set disable_ai to false
33580    cx.update(|cx| {
33581        project::DisableAiSettings::register(cx);
33582        project::DisableAiSettings::override_global(
33583            project::DisableAiSettings { disable_ai: false },
33584            cx,
33585        );
33586    });
33587
33588    let fs = FakeFs::new(cx.executor());
33589    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
33590        .await;
33591
33592    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
33593    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
33594    let workspace = window
33595        .read_with(cx, |mw, _| mw.workspace().clone())
33596        .unwrap();
33597    let cx = &mut VisualTestContext::from_window(*window, cx);
33598
33599    let editor = workspace
33600        .update_in(cx, |workspace, window, cx| {
33601            workspace.open_abs_path(
33602                PathBuf::from(path!("/root/file.txt")),
33603                OpenOptions::default(),
33604                window,
33605                cx,
33606            )
33607        })
33608        .await
33609        .unwrap()
33610        .downcast::<Editor>()
33611        .unwrap();
33612
33613    // Enable diff review button mode
33614    editor.update(cx, |editor, cx| {
33615        editor.set_show_diff_review_button(true, cx);
33616    });
33617
33618    // Verify AI is enabled
33619    cx.read(|cx| {
33620        assert!(
33621            !project::DisableAiSettings::get_global(cx).disable_ai,
33622            "AI should be enabled"
33623        );
33624    });
33625
33626    // The show_diff_review_button flag should be true
33627    editor.update(cx, |editor, _cx| {
33628        assert!(
33629            editor.show_diff_review_button(),
33630            "show_diff_review_button should be true"
33631        );
33632    });
33633}
33634
33635/// Helper function to create a DiffHunkKey for testing.
33636/// Uses Anchor::Min as a placeholder anchor since these tests don't need
33637/// real buffer positioning.
33638fn test_hunk_key(file_path: &str) -> DiffHunkKey {
33639    DiffHunkKey {
33640        file_path: if file_path.is_empty() {
33641            Arc::from(util::rel_path::RelPath::empty())
33642        } else {
33643            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
33644        },
33645        hunk_start_anchor: Anchor::Min,
33646    }
33647}
33648
33649/// Helper function to create a DiffHunkKey with a specific anchor for testing.
33650fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
33651    DiffHunkKey {
33652        file_path: if file_path.is_empty() {
33653            Arc::from(util::rel_path::RelPath::empty())
33654        } else {
33655            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
33656        },
33657        hunk_start_anchor: anchor,
33658    }
33659}
33660
33661/// Helper function to add a review comment with default anchors for testing.
33662fn add_test_comment(
33663    editor: &mut Editor,
33664    key: DiffHunkKey,
33665    comment: &str,
33666    cx: &mut Context<Editor>,
33667) -> usize {
33668    editor.add_review_comment(key, comment.to_string(), Anchor::Min..Anchor::Max, cx)
33669}
33670
33671#[gpui::test]
33672fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
33673    init_test(cx, |_| {});
33674
33675    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33676
33677    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33678        let key = test_hunk_key("");
33679
33680        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
33681
33682        let snapshot = editor.buffer().read(cx).snapshot(cx);
33683        assert_eq!(editor.total_review_comment_count(), 1);
33684        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
33685
33686        let comments = editor.comments_for_hunk(&key, &snapshot);
33687        assert_eq!(comments.len(), 1);
33688        assert_eq!(comments[0].comment, "Test comment");
33689        assert_eq!(comments[0].id, id);
33690    });
33691}
33692
33693#[gpui::test]
33694fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
33695    init_test(cx, |_| {});
33696
33697    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33698
33699    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33700        let snapshot = editor.buffer().read(cx).snapshot(cx);
33701        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
33702        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
33703        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
33704        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
33705
33706        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
33707        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
33708
33709        let snapshot = editor.buffer().read(cx).snapshot(cx);
33710        assert_eq!(editor.total_review_comment_count(), 2);
33711        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
33712        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
33713
33714        assert_eq!(
33715            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
33716            "Comment for file1"
33717        );
33718        assert_eq!(
33719            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
33720            "Comment for file2"
33721        );
33722    });
33723}
33724
33725#[gpui::test]
33726fn test_review_comment_remove(cx: &mut TestAppContext) {
33727    init_test(cx, |_| {});
33728
33729    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33730
33731    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33732        let key = test_hunk_key("");
33733
33734        let id = add_test_comment(editor, key, "To be removed", cx);
33735
33736        assert_eq!(editor.total_review_comment_count(), 1);
33737
33738        let removed = editor.remove_review_comment(id, cx);
33739        assert!(removed);
33740        assert_eq!(editor.total_review_comment_count(), 0);
33741
33742        // Try to remove again
33743        let removed_again = editor.remove_review_comment(id, cx);
33744        assert!(!removed_again);
33745    });
33746}
33747
33748#[gpui::test]
33749fn test_review_comment_update(cx: &mut TestAppContext) {
33750    init_test(cx, |_| {});
33751
33752    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33753
33754    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33755        let key = test_hunk_key("");
33756
33757        let id = add_test_comment(editor, key.clone(), "Original text", cx);
33758
33759        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
33760        assert!(updated);
33761
33762        let snapshot = editor.buffer().read(cx).snapshot(cx);
33763        let comments = editor.comments_for_hunk(&key, &snapshot);
33764        assert_eq!(comments[0].comment, "Updated text");
33765        assert!(!comments[0].is_editing); // Should clear editing flag
33766    });
33767}
33768
33769#[gpui::test]
33770fn test_review_comment_take_all(cx: &mut TestAppContext) {
33771    init_test(cx, |_| {});
33772
33773    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33774
33775    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
33776        let snapshot = editor.buffer().read(cx).snapshot(cx);
33777        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
33778        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
33779        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
33780        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
33781
33782        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
33783        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
33784        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
33785
33786        // IDs should be sequential starting from 0
33787        assert_eq!(id1, 0);
33788        assert_eq!(id2, 1);
33789        assert_eq!(id3, 2);
33790
33791        assert_eq!(editor.total_review_comment_count(), 3);
33792
33793        let taken = editor.take_all_review_comments(cx);
33794
33795        // Should have 2 entries (one per hunk)
33796        assert_eq!(taken.len(), 2);
33797
33798        // Total comments should be 3
33799        let total: usize = taken
33800            .iter()
33801            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
33802            .sum();
33803        assert_eq!(total, 3);
33804
33805        // Storage should be empty
33806        assert_eq!(editor.total_review_comment_count(), 0);
33807
33808        // After taking all comments, ID counter should reset
33809        // New comments should get IDs starting from 0 again
33810        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
33811        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
33812
33813        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
33814        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
33815    });
33816}
33817
33818#[gpui::test]
33819fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
33820    init_test(cx, |_| {});
33821
33822    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33823
33824    // Show overlay
33825    editor
33826        .update(cx, |editor, window, cx| {
33827            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33828        })
33829        .unwrap();
33830
33831    // Verify overlay is shown
33832    editor
33833        .update(cx, |editor, _window, cx| {
33834            assert!(!editor.diff_review_overlays.is_empty());
33835            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
33836            assert!(editor.diff_review_prompt_editor().is_some());
33837        })
33838        .unwrap();
33839
33840    // Dismiss overlay
33841    editor
33842        .update(cx, |editor, _window, cx| {
33843            editor.dismiss_all_diff_review_overlays(cx);
33844        })
33845        .unwrap();
33846
33847    // Verify overlay is dismissed
33848    editor
33849        .update(cx, |editor, _window, cx| {
33850            assert!(editor.diff_review_overlays.is_empty());
33851            assert_eq!(editor.diff_review_line_range(cx), None);
33852            assert!(editor.diff_review_prompt_editor().is_none());
33853        })
33854        .unwrap();
33855}
33856
33857#[gpui::test]
33858fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
33859    init_test(cx, |_| {});
33860
33861    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33862
33863    // Show overlay
33864    editor
33865        .update(cx, |editor, window, cx| {
33866            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33867        })
33868        .unwrap();
33869
33870    // Verify overlay is shown
33871    editor
33872        .update(cx, |editor, _window, _cx| {
33873            assert!(!editor.diff_review_overlays.is_empty());
33874        })
33875        .unwrap();
33876
33877    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
33878    editor
33879        .update(cx, |editor, window, cx| {
33880            editor.dismiss_menus_and_popups(true, window, cx);
33881        })
33882        .unwrap();
33883
33884    // Verify overlay is dismissed
33885    editor
33886        .update(cx, |editor, _window, _cx| {
33887            assert!(editor.diff_review_overlays.is_empty());
33888        })
33889        .unwrap();
33890}
33891
33892#[gpui::test]
33893fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
33894    init_test(cx, |_| {});
33895
33896    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33897
33898    // Show overlay
33899    editor
33900        .update(cx, |editor, window, cx| {
33901            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
33902        })
33903        .unwrap();
33904
33905    // Try to submit without typing anything (empty comment)
33906    editor
33907        .update(cx, |editor, window, cx| {
33908            editor.submit_diff_review_comment(window, cx);
33909        })
33910        .unwrap();
33911
33912    // Verify no comment was added
33913    editor
33914        .update(cx, |editor, _window, _cx| {
33915            assert_eq!(editor.total_review_comment_count(), 0);
33916        })
33917        .unwrap();
33918
33919    // Try to submit with whitespace-only comment
33920    editor
33921        .update(cx, |editor, window, cx| {
33922            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
33923                prompt_editor.update(cx, |pe, cx| {
33924                    pe.insert("   \n\t  ", window, cx);
33925                });
33926            }
33927            editor.submit_diff_review_comment(window, cx);
33928        })
33929        .unwrap();
33930
33931    // Verify still no comment was added
33932    editor
33933        .update(cx, |editor, _window, _cx| {
33934            assert_eq!(editor.total_review_comment_count(), 0);
33935        })
33936        .unwrap();
33937}
33938
33939#[gpui::test]
33940fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
33941    init_test(cx, |_| {});
33942
33943    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33944
33945    // Add a comment directly
33946    let comment_id = editor
33947        .update(cx, |editor, _window, cx| {
33948            let key = test_hunk_key("");
33949            add_test_comment(editor, key, "Original comment", cx)
33950        })
33951        .unwrap();
33952
33953    // Set comment to editing mode
33954    editor
33955        .update(cx, |editor, _window, cx| {
33956            editor.set_comment_editing(comment_id, true, cx);
33957        })
33958        .unwrap();
33959
33960    // Verify editing flag is set
33961    editor
33962        .update(cx, |editor, _window, cx| {
33963            let key = test_hunk_key("");
33964            let snapshot = editor.buffer().read(cx).snapshot(cx);
33965            let comments = editor.comments_for_hunk(&key, &snapshot);
33966            assert_eq!(comments.len(), 1);
33967            assert!(comments[0].is_editing);
33968        })
33969        .unwrap();
33970
33971    // Update the comment
33972    editor
33973        .update(cx, |editor, _window, cx| {
33974            let updated =
33975                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
33976            assert!(updated);
33977        })
33978        .unwrap();
33979
33980    // Verify comment was updated and editing flag is cleared
33981    editor
33982        .update(cx, |editor, _window, cx| {
33983            let key = test_hunk_key("");
33984            let snapshot = editor.buffer().read(cx).snapshot(cx);
33985            let comments = editor.comments_for_hunk(&key, &snapshot);
33986            assert_eq!(comments[0].comment, "Updated comment");
33987            assert!(!comments[0].is_editing);
33988        })
33989        .unwrap();
33990}
33991
33992#[gpui::test]
33993fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
33994    init_test(cx, |_| {});
33995
33996    // Create an editor with some text
33997    let editor = cx.add_window(|window, cx| {
33998        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
33999        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34000        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34001    });
34002
34003    // Add a comment with an anchor on line 2
34004    editor
34005        .update(cx, |editor, _window, cx| {
34006            let snapshot = editor.buffer().read(cx).snapshot(cx);
34007            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
34008            let key = DiffHunkKey {
34009                file_path: Arc::from(util::rel_path::RelPath::empty()),
34010                hunk_start_anchor: anchor,
34011            };
34012            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
34013            assert_eq!(editor.total_review_comment_count(), 1);
34014        })
34015        .unwrap();
34016
34017    // Delete all content (this should orphan the comment's anchor)
34018    editor
34019        .update(cx, |editor, window, cx| {
34020            editor.select_all(&SelectAll, window, cx);
34021            editor.insert("completely new content", window, cx);
34022        })
34023        .unwrap();
34024
34025    // Trigger cleanup
34026    editor
34027        .update(cx, |editor, _window, cx| {
34028            editor.cleanup_orphaned_review_comments(cx);
34029            // Comment should be removed because its anchor is invalid
34030            assert_eq!(editor.total_review_comment_count(), 0);
34031        })
34032        .unwrap();
34033}
34034
34035#[gpui::test]
34036fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
34037    init_test(cx, |_| {});
34038
34039    // Create an editor with some text
34040    let editor = cx.add_window(|window, cx| {
34041        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
34042        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34043        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34044    });
34045
34046    // Add a comment with an anchor on line 2
34047    editor
34048        .update(cx, |editor, _window, cx| {
34049            let snapshot = editor.buffer().read(cx).snapshot(cx);
34050            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
34051            let key = DiffHunkKey {
34052                file_path: Arc::from(util::rel_path::RelPath::empty()),
34053                hunk_start_anchor: anchor,
34054            };
34055            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
34056            assert_eq!(editor.total_review_comment_count(), 1);
34057        })
34058        .unwrap();
34059
34060    // Edit the buffer - this should trigger cleanup via on_buffer_event
34061    // Delete all content which orphans the anchor
34062    editor
34063        .update(cx, |editor, window, cx| {
34064            editor.select_all(&SelectAll, window, cx);
34065            editor.insert("completely new content", window, cx);
34066            // The cleanup is called automatically in on_buffer_event when Edited fires
34067        })
34068        .unwrap();
34069
34070    // Verify cleanup happened automatically (not manually triggered)
34071    editor
34072        .update(cx, |editor, _window, _cx| {
34073            // Comment should be removed because its anchor became invalid
34074            // and cleanup was called automatically on buffer edit
34075            assert_eq!(editor.total_review_comment_count(), 0);
34076        })
34077        .unwrap();
34078}
34079
34080#[gpui::test]
34081fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
34082    init_test(cx, |_| {});
34083
34084    // This test verifies that comments can be stored for multiple different hunks
34085    // and that hunk_comment_count correctly identifies comments per hunk.
34086    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34087
34088    _ = editor.update(cx, |editor, _window, cx| {
34089        let snapshot = editor.buffer().read(cx).snapshot(cx);
34090
34091        // Create two different hunk keys (simulating two different files)
34092        let anchor = snapshot.anchor_before(Point::new(0, 0));
34093        let key1 = DiffHunkKey {
34094            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
34095            hunk_start_anchor: anchor,
34096        };
34097        let key2 = DiffHunkKey {
34098            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
34099            hunk_start_anchor: anchor,
34100        };
34101
34102        // Add comments to first hunk
34103        editor.add_review_comment(
34104            key1.clone(),
34105            "Comment 1 for file1".to_string(),
34106            anchor..anchor,
34107            cx,
34108        );
34109        editor.add_review_comment(
34110            key1.clone(),
34111            "Comment 2 for file1".to_string(),
34112            anchor..anchor,
34113            cx,
34114        );
34115
34116        // Add comment to second hunk
34117        editor.add_review_comment(
34118            key2.clone(),
34119            "Comment for file2".to_string(),
34120            anchor..anchor,
34121            cx,
34122        );
34123
34124        // Verify total count
34125        assert_eq!(editor.total_review_comment_count(), 3);
34126
34127        // Verify per-hunk counts
34128        let snapshot = editor.buffer().read(cx).snapshot(cx);
34129        assert_eq!(
34130            editor.hunk_comment_count(&key1, &snapshot),
34131            2,
34132            "file1 should have 2 comments"
34133        );
34134        assert_eq!(
34135            editor.hunk_comment_count(&key2, &snapshot),
34136            1,
34137            "file2 should have 1 comment"
34138        );
34139
34140        // Verify comments_for_hunk returns correct comments
34141        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
34142        assert_eq!(file1_comments.len(), 2);
34143        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
34144        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
34145
34146        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
34147        assert_eq!(file2_comments.len(), 1);
34148        assert_eq!(file2_comments[0].comment, "Comment for file2");
34149    });
34150}
34151
34152#[gpui::test]
34153fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
34154    init_test(cx, |_| {});
34155
34156    // This test verifies that hunk_keys_match correctly identifies when two
34157    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
34158    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34159
34160    _ = editor.update(cx, |editor, _window, cx| {
34161        let snapshot = editor.buffer().read(cx).snapshot(cx);
34162        let anchor = snapshot.anchor_before(Point::new(0, 0));
34163
34164        // Create two keys with the same file path and anchor
34165        let key1 = DiffHunkKey {
34166            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
34167            hunk_start_anchor: anchor,
34168        };
34169        let key2 = DiffHunkKey {
34170            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
34171            hunk_start_anchor: anchor,
34172        };
34173
34174        // Add comment to first key
34175        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
34176
34177        // Verify second key (same hunk) finds the comment
34178        let snapshot = editor.buffer().read(cx).snapshot(cx);
34179        assert_eq!(
34180            editor.hunk_comment_count(&key2, &snapshot),
34181            1,
34182            "Same hunk should find the comment"
34183        );
34184
34185        // Create a key with different file path
34186        let different_file_key = DiffHunkKey {
34187            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
34188            hunk_start_anchor: anchor,
34189        };
34190
34191        // Different file should not find the comment
34192        assert_eq!(
34193            editor.hunk_comment_count(&different_file_key, &snapshot),
34194            0,
34195            "Different file should not find the comment"
34196        );
34197    });
34198}
34199
34200#[gpui::test]
34201fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
34202    init_test(cx, |_| {});
34203
34204    // This test verifies that set_diff_review_comments_expanded correctly
34205    // updates the expanded state of overlays.
34206    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34207
34208    // Show overlay
34209    editor
34210        .update(cx, |editor, window, cx| {
34211            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
34212        })
34213        .unwrap();
34214
34215    // Verify initially expanded (default)
34216    editor
34217        .update(cx, |editor, _window, _cx| {
34218            assert!(
34219                editor.diff_review_overlays[0].comments_expanded,
34220                "Should be expanded by default"
34221            );
34222        })
34223        .unwrap();
34224
34225    // Set to collapsed using the public method
34226    editor
34227        .update(cx, |editor, _window, cx| {
34228            editor.set_diff_review_comments_expanded(false, cx);
34229        })
34230        .unwrap();
34231
34232    // Verify collapsed
34233    editor
34234        .update(cx, |editor, _window, _cx| {
34235            assert!(
34236                !editor.diff_review_overlays[0].comments_expanded,
34237                "Should be collapsed after setting to false"
34238            );
34239        })
34240        .unwrap();
34241
34242    // Set back to expanded
34243    editor
34244        .update(cx, |editor, _window, cx| {
34245            editor.set_diff_review_comments_expanded(true, cx);
34246        })
34247        .unwrap();
34248
34249    // Verify expanded again
34250    editor
34251        .update(cx, |editor, _window, _cx| {
34252            assert!(
34253                editor.diff_review_overlays[0].comments_expanded,
34254                "Should be expanded after setting to true"
34255            );
34256        })
34257        .unwrap();
34258}
34259
34260#[gpui::test]
34261fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
34262    init_test(cx, |_| {});
34263
34264    // Create an editor with multiple lines of text
34265    let editor = cx.add_window(|window, cx| {
34266        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
34267        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34268        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34269    });
34270
34271    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
34272    editor
34273        .update(cx, |editor, window, cx| {
34274            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
34275        })
34276        .unwrap();
34277
34278    // Verify line range
34279    editor
34280        .update(cx, |editor, _window, cx| {
34281            assert!(!editor.diff_review_overlays.is_empty());
34282            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
34283        })
34284        .unwrap();
34285
34286    // Dismiss and test with reversed range (end < start)
34287    editor
34288        .update(cx, |editor, _window, cx| {
34289            editor.dismiss_all_diff_review_overlays(cx);
34290        })
34291        .unwrap();
34292
34293    // Show overlay with reversed range - should normalize it
34294    editor
34295        .update(cx, |editor, window, cx| {
34296            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
34297        })
34298        .unwrap();
34299
34300    // Verify range is normalized (start <= end)
34301    editor
34302        .update(cx, |editor, _window, cx| {
34303            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
34304        })
34305        .unwrap();
34306}
34307
34308#[gpui::test]
34309fn test_diff_review_drag_state(cx: &mut TestAppContext) {
34310    init_test(cx, |_| {});
34311
34312    let editor = cx.add_window(|window, cx| {
34313        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
34314        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34315        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
34316    });
34317
34318    // Initially no drag state
34319    editor
34320        .update(cx, |editor, _window, _cx| {
34321            assert!(editor.diff_review_drag_state.is_none());
34322        })
34323        .unwrap();
34324
34325    // Start drag at row 1
34326    editor
34327        .update(cx, |editor, window, cx| {
34328            editor.start_diff_review_drag(DisplayRow(1), window, cx);
34329        })
34330        .unwrap();
34331
34332    // Verify drag state is set
34333    editor
34334        .update(cx, |editor, window, cx| {
34335            assert!(editor.diff_review_drag_state.is_some());
34336            let snapshot = editor.snapshot(window, cx);
34337            let range = editor
34338                .diff_review_drag_state
34339                .as_ref()
34340                .unwrap()
34341                .row_range(&snapshot.display_snapshot);
34342            assert_eq!(*range.start(), DisplayRow(1));
34343            assert_eq!(*range.end(), DisplayRow(1));
34344        })
34345        .unwrap();
34346
34347    // Update drag to row 3
34348    editor
34349        .update(cx, |editor, window, cx| {
34350            editor.update_diff_review_drag(DisplayRow(3), window, cx);
34351        })
34352        .unwrap();
34353
34354    // Verify drag state is updated
34355    editor
34356        .update(cx, |editor, window, cx| {
34357            assert!(editor.diff_review_drag_state.is_some());
34358            let snapshot = editor.snapshot(window, cx);
34359            let range = editor
34360                .diff_review_drag_state
34361                .as_ref()
34362                .unwrap()
34363                .row_range(&snapshot.display_snapshot);
34364            assert_eq!(*range.start(), DisplayRow(1));
34365            assert_eq!(*range.end(), DisplayRow(3));
34366        })
34367        .unwrap();
34368
34369    // End drag - should show overlay
34370    editor
34371        .update(cx, |editor, window, cx| {
34372            editor.end_diff_review_drag(window, cx);
34373        })
34374        .unwrap();
34375
34376    // Verify drag state is cleared and overlay is shown
34377    editor
34378        .update(cx, |editor, _window, cx| {
34379            assert!(editor.diff_review_drag_state.is_none());
34380            assert!(!editor.diff_review_overlays.is_empty());
34381            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
34382        })
34383        .unwrap();
34384}
34385
34386#[gpui::test]
34387fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
34388    init_test(cx, |_| {});
34389
34390    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34391
34392    // Start drag
34393    editor
34394        .update(cx, |editor, window, cx| {
34395            editor.start_diff_review_drag(DisplayRow(0), window, cx);
34396        })
34397        .unwrap();
34398
34399    // Verify drag state is set
34400    editor
34401        .update(cx, |editor, _window, _cx| {
34402            assert!(editor.diff_review_drag_state.is_some());
34403        })
34404        .unwrap();
34405
34406    // Cancel drag
34407    editor
34408        .update(cx, |editor, _window, cx| {
34409            editor.cancel_diff_review_drag(cx);
34410        })
34411        .unwrap();
34412
34413    // Verify drag state is cleared and no overlay was created
34414    editor
34415        .update(cx, |editor, _window, _cx| {
34416            assert!(editor.diff_review_drag_state.is_none());
34417            assert!(editor.diff_review_overlays.is_empty());
34418        })
34419        .unwrap();
34420}
34421
34422#[gpui::test]
34423fn test_calculate_overlay_height(cx: &mut TestAppContext) {
34424    init_test(cx, |_| {});
34425
34426    // This test verifies that calculate_overlay_height returns correct heights
34427    // based on comment count and expanded state.
34428    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
34429
34430    _ = editor.update(cx, |editor, _window, cx| {
34431        let snapshot = editor.buffer().read(cx).snapshot(cx);
34432        let anchor = snapshot.anchor_before(Point::new(0, 0));
34433        let key = DiffHunkKey {
34434            file_path: Arc::from(util::rel_path::RelPath::empty()),
34435            hunk_start_anchor: anchor,
34436        };
34437
34438        // No comments: base height of 2
34439        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
34440        assert_eq!(
34441            height_no_comments, 2,
34442            "Base height should be 2 with no comments"
34443        );
34444
34445        // Add one comment
34446        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
34447
34448        let snapshot = editor.buffer().read(cx).snapshot(cx);
34449
34450        // With comments expanded: base (2) + header (1) + 2 per comment
34451        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
34452        assert_eq!(
34453            height_expanded,
34454            2 + 1 + 2, // base + header + 1 comment * 2
34455            "Height with 1 comment expanded"
34456        );
34457
34458        // With comments collapsed: base (2) + header (1)
34459        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
34460        assert_eq!(
34461            height_collapsed,
34462            2 + 1, // base + header only
34463            "Height with comments collapsed"
34464        );
34465
34466        // Add more comments
34467        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
34468        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
34469
34470        let snapshot = editor.buffer().read(cx).snapshot(cx);
34471
34472        // With 3 comments expanded
34473        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
34474        assert_eq!(
34475            height_3_expanded,
34476            2 + 1 + (3 * 2), // base + header + 3 comments * 2
34477            "Height with 3 comments expanded"
34478        );
34479
34480        // Collapsed height stays the same regardless of comment count
34481        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
34482        assert_eq!(
34483            height_3_collapsed,
34484            2 + 1, // base + header only
34485            "Height with 3 comments collapsed should be same as 1 comment collapsed"
34486        );
34487    });
34488}
34489
34490#[gpui::test]
34491async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
34492    init_test(cx, |_| {});
34493
34494    let language = Arc::new(Language::new(
34495        LanguageConfig::default(),
34496        Some(tree_sitter_rust::LANGUAGE.into()),
34497    ));
34498
34499    let text = r#"
34500        fn main() {
34501            let x = foo(1, 2);
34502        }
34503    "#
34504    .unindent();
34505
34506    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34507    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34508    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34509
34510    editor
34511        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34512        .await;
34513
34514    // Test case 1: Move to end of syntax nodes
34515    editor.update_in(cx, |editor, window, cx| {
34516        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34517            s.select_display_ranges([
34518                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
34519            ]);
34520        });
34521    });
34522    editor.update(cx, |editor, cx| {
34523        assert_text_with_selections(
34524            editor,
34525            indoc! {r#"
34526                fn main() {
34527                    let x = foo(ˇ1, 2);
34528                }
34529            "#},
34530            cx,
34531        );
34532    });
34533    editor.update_in(cx, |editor, window, cx| {
34534        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34535    });
34536    editor.update(cx, |editor, cx| {
34537        assert_text_with_selections(
34538            editor,
34539            indoc! {r#"
34540                fn main() {
34541                    let x = foo(1ˇ, 2);
34542                }
34543            "#},
34544            cx,
34545        );
34546    });
34547    editor.update_in(cx, |editor, window, cx| {
34548        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
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);
3458434585            "#},
34586            cx,
34587        );
34588    });
34589
34590    // Test case 2: Move to start of syntax nodes
34591    editor.update_in(cx, |editor, window, cx| {
34592        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34593            s.select_display_ranges([
34594                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
34595            ]);
34596        });
34597    });
34598    editor.update(cx, |editor, cx| {
34599        assert_text_with_selections(
34600            editor,
34601            indoc! {r#"
34602                fn main() {
34603                    let x = foo(1, 2ˇ);
34604                }
34605            "#},
34606            cx,
34607        );
34608    });
34609    editor.update_in(cx, |editor, window, cx| {
34610        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34611    });
34612    editor.update(cx, |editor, cx| {
34613        assert_text_with_selections(
34614            editor,
34615            indoc! {r#"
34616                fn main() {
34617                    let x = fooˇ(1, 2);
34618                }
34619            "#},
34620            cx,
34621        );
34622    });
34623    editor.update_in(cx, |editor, window, cx| {
34624        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
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}
34680
34681#[gpui::test]
34682async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
34683    init_test(cx, |_| {});
34684
34685    let language = Arc::new(Language::new(
34686        LanguageConfig::default(),
34687        Some(tree_sitter_rust::LANGUAGE.into()),
34688    ));
34689
34690    let text = r#"
34691        fn main() {
34692            let x = foo(1, 2);
34693            let y = bar(3, 4);
34694        }
34695    "#
34696    .unindent();
34697
34698    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34699    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34700    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34701
34702    editor
34703        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34704        .await;
34705
34706    // Test case 1: Move to end of syntax nodes with two cursors
34707    editor.update_in(cx, |editor, window, cx| {
34708        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34709            s.select_display_ranges([
34710                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
34711                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
34712            ]);
34713        });
34714    });
34715    editor.update(cx, |editor, cx| {
34716        assert_text_with_selections(
34717            editor,
34718            indoc! {r#"
34719                fn main() {
34720                    let x = foo(1, 2ˇ);
34721                    let y = bar(3, 4ˇ);
34722                }
34723            "#},
34724            cx,
34725        );
34726    });
34727    editor.update_in(cx, |editor, window, cx| {
34728        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34729    });
34730    editor.update(cx, |editor, cx| {
34731        assert_text_with_selections(
34732            editor,
34733            indoc! {r#"
34734                fn main() {
34735                    let x = foo(1, 2)ˇ;
34736                    let y = bar(3, 4)ˇ;
34737                }
34738            "#},
34739            cx,
34740        );
34741    });
34742    editor.update_in(cx, |editor, window, cx| {
34743        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34744    });
34745    editor.update(cx, |editor, cx| {
34746        assert_text_with_selections(
34747            editor,
34748            indoc! {r#"
34749                fn main() {
34750                    let x = foo(1, 2);ˇ
34751                    let y = bar(3, 4);ˇ
34752                }
34753            "#},
34754            cx,
34755        );
34756    });
34757
34758    // Test case 2: Move to start of syntax nodes with two cursors
34759    editor.update_in(cx, |editor, window, cx| {
34760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34761            s.select_display_ranges([
34762                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
34763                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
34764            ]);
34765        });
34766    });
34767    editor.update(cx, |editor, cx| {
34768        assert_text_with_selections(
34769            editor,
34770            indoc! {r#"
34771                fn main() {
34772                    let x = foo(1, ˇ2);
34773                    let y = bar(3, ˇ4);
34774                }
34775            "#},
34776            cx,
34777        );
34778    });
34779    editor.update_in(cx, |editor, window, cx| {
34780        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34781    });
34782    editor.update(cx, |editor, cx| {
34783        assert_text_with_selections(
34784            editor,
34785            indoc! {r#"
34786                fn main() {
34787                    let x = fooˇ(1, 2);
34788                    let y = barˇ(3, 4);
34789                }
34790            "#},
34791            cx,
34792        );
34793    });
34794    editor.update_in(cx, |editor, window, cx| {
34795        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34796    });
34797    editor.update(cx, |editor, cx| {
34798        assert_text_with_selections(
34799            editor,
34800            indoc! {r#"
34801                fn main() {
34802                    let x = ˇfoo(1, 2);
34803                    let y = ˇbar(3, 4);
34804                }
34805            "#},
34806            cx,
34807        );
34808    });
34809    editor.update_in(cx, |editor, window, cx| {
34810        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34811    });
34812    editor.update(cx, |editor, cx| {
34813        assert_text_with_selections(
34814            editor,
34815            indoc! {r#"
34816                fn main() {
34817                    ˇlet x = foo(1, 2);
34818                    ˇlet y = bar(3, 4);
34819                }
34820            "#},
34821            cx,
34822        );
34823    });
34824}
34825
34826#[gpui::test]
34827async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
34828    cx: &mut TestAppContext,
34829) {
34830    init_test(cx, |_| {});
34831
34832    let language = Arc::new(Language::new(
34833        LanguageConfig::default(),
34834        Some(tree_sitter_rust::LANGUAGE.into()),
34835    ));
34836
34837    let text = r#"
34838        fn main() {
34839            let x = foo(1, 2);
34840            let msg = "hello world";
34841        }
34842    "#
34843    .unindent();
34844
34845    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
34846    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34847    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34848
34849    editor
34850        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34851        .await;
34852
34853    // Test case 1: With existing selection, move_to_end keeps selection
34854    editor.update_in(cx, |editor, window, cx| {
34855        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34856            s.select_display_ranges([
34857                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
34858            ]);
34859        });
34860    });
34861    editor.update(cx, |editor, cx| {
34862        assert_text_with_selections(
34863            editor,
34864            indoc! {r#"
34865                fn main() {
34866                    let x = «foo(1, 2)ˇ»;
34867                    let msg = "hello world";
34868                }
34869            "#},
34870            cx,
34871        );
34872    });
34873    editor.update_in(cx, |editor, window, cx| {
34874        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34875    });
34876    editor.update(cx, |editor, cx| {
34877        assert_text_with_selections(
34878            editor,
34879            indoc! {r#"
34880                fn main() {
34881                    let x = «foo(1, 2)ˇ»;
34882                    let msg = "hello world";
34883                }
34884            "#},
34885            cx,
34886        );
34887    });
34888
34889    // Test case 2: Move to end within a string
34890    editor.update_in(cx, |editor, window, cx| {
34891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34892            s.select_display_ranges([
34893                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
34894            ]);
34895        });
34896    });
34897    editor.update(cx, |editor, cx| {
34898        assert_text_with_selections(
34899            editor,
34900            indoc! {r#"
34901                fn main() {
34902                    let x = foo(1, 2);
34903                    let msg = "ˇhello world";
34904                }
34905            "#},
34906            cx,
34907        );
34908    });
34909    editor.update_in(cx, |editor, window, cx| {
34910        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
34911    });
34912    editor.update(cx, |editor, cx| {
34913        assert_text_with_selections(
34914            editor,
34915            indoc! {r#"
34916                fn main() {
34917                    let x = foo(1, 2);
34918                    let msg = "hello worldˇ";
34919                }
34920            "#},
34921            cx,
34922        );
34923    });
34924
34925    // Test case 3: Move to start within a string
34926    editor.update_in(cx, |editor, window, cx| {
34927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34928            s.select_display_ranges([
34929                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
34930            ]);
34931        });
34932    });
34933    editor.update(cx, |editor, cx| {
34934        assert_text_with_selections(
34935            editor,
34936            indoc! {r#"
34937                fn main() {
34938                    let x = foo(1, 2);
34939                    let msg = "hello ˇworld";
34940                }
34941            "#},
34942            cx,
34943        );
34944    });
34945    editor.update_in(cx, |editor, window, cx| {
34946        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
34947    });
34948    editor.update(cx, |editor, cx| {
34949        assert_text_with_selections(
34950            editor,
34951            indoc! {r#"
34952                fn main() {
34953                    let x = foo(1, 2);
34954                    let msg = "ˇhello world";
34955                }
34956            "#},
34957            cx,
34958        );
34959    });
34960}
34961
34962#[gpui::test]
34963async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
34964    init_test(cx, |_| {});
34965
34966    let language = Arc::new(Language::new(
34967        LanguageConfig::default(),
34968        Some(tree_sitter_rust::LANGUAGE.into()),
34969    ));
34970
34971    // Test Group 1.1: Cursor in String - First Jump (Select to End)
34972    let text = r#"let msg = "foo bar baz";"#.unindent();
34973
34974    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
34975    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34976    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34977
34978    editor
34979        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34980        .await;
34981
34982    editor.update_in(cx, |editor, window, cx| {
34983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34984            s.select_display_ranges([
34985                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
34986            ]);
34987        });
34988    });
34989    editor.update(cx, |editor, cx| {
34990        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
34991    });
34992    editor.update_in(cx, |editor, window, cx| {
34993        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
34994    });
34995    editor.update(cx, |editor, cx| {
34996        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
34997    });
34998
34999    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
35000    editor.update_in(cx, |editor, window, cx| {
35001        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35002    });
35003    editor.update(cx, |editor, cx| {
35004        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
35005    });
35006
35007    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
35008    editor.update_in(cx, |editor, window, cx| {
35009        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35010    });
35011    editor.update(cx, |editor, cx| {
35012        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
35013    });
35014
35015    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
35016    editor.update_in(cx, |editor, window, cx| {
35017        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35018            s.select_display_ranges([
35019                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
35020            ]);
35021        });
35022    });
35023    editor.update(cx, |editor, cx| {
35024        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
35025    });
35026    editor.update_in(cx, |editor, window, cx| {
35027        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35028    });
35029    editor.update(cx, |editor, cx| {
35030        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
35031    });
35032
35033    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
35034    editor.update_in(cx, |editor, window, cx| {
35035        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35036    });
35037    editor.update(cx, |editor, cx| {
35038        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
35039    });
35040
35041    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
35042    editor.update_in(cx, |editor, window, cx| {
35043        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35044    });
35045    editor.update(cx, |editor, cx| {
35046        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
35047    });
35048
35049    // Test Group 2.1: Let Statement Progression (Select to End)
35050    let text = r#"
35051fn main() {
35052    let x = "hello";
35053}
35054"#
35055    .unindent();
35056
35057    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35058    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35059    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35060
35061    editor
35062        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35063        .await;
35064
35065    editor.update_in(cx, |editor, window, cx| {
35066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35067            s.select_display_ranges([
35068                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
35069            ]);
35070        });
35071    });
35072    editor.update(cx, |editor, cx| {
35073        assert_text_with_selections(
35074            editor,
35075            indoc! {r#"
35076                fn main() {
35077                    let xˇ = "hello";
35078                }
35079            "#},
35080            cx,
35081        );
35082    });
35083    editor.update_in(cx, |editor, window, cx| {
35084        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35085    });
35086    editor.update(cx, |editor, cx| {
35087        assert_text_with_selections(
35088            editor,
35089            indoc! {r##"
35090                fn main() {
35091                    let x« = "hello";ˇ»
35092                }
35093            "##},
35094            cx,
35095        );
35096    });
35097    editor.update_in(cx, |editor, window, cx| {
35098        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
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
35112    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
35113    let text = r#"let x = "hello";"#.unindent();
35114
35115    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35116    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35117    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35118
35119    editor
35120        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35121        .await;
35122
35123    editor.update_in(cx, |editor, window, cx| {
35124        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35125            s.select_display_ranges([
35126                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
35127            ]);
35128        });
35129    });
35130    editor.update(cx, |editor, cx| {
35131        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
35132    });
35133    editor.update_in(cx, |editor, window, cx| {
35134        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35135    });
35136    editor.update(cx, |editor, cx| {
35137        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
35138    });
35139
35140    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
35141    editor.update_in(cx, |editor, window, cx| {
35142        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35143            s.select_display_ranges([
35144                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
35145            ]);
35146        });
35147    });
35148    editor.update(cx, |editor, cx| {
35149        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
35150    });
35151    editor.update_in(cx, |editor, window, cx| {
35152        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
35153    });
35154    editor.update(cx, |editor, cx| {
35155        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
35156    });
35157
35158    // Test Group 3.1: Create Selection from Cursor (Select to End)
35159    let text = r#"let x = "hello world";"#.unindent();
35160
35161    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35162    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35164
35165    editor
35166        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35167        .await;
35168
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), 14)..DisplayPoint::new(DisplayRow(0), 14)
35173            ]);
35174        });
35175    });
35176    editor.update(cx, |editor, cx| {
35177        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
35178    });
35179    editor.update_in(cx, |editor, window, cx| {
35180        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35181    });
35182    editor.update(cx, |editor, cx| {
35183        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
35184    });
35185
35186    // Test Group 3.2: Extend Existing Selection (Select to End)
35187    editor.update_in(cx, |editor, window, cx| {
35188        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35189            s.select_display_ranges([
35190                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
35191            ]);
35192        });
35193    });
35194    editor.update(cx, |editor, cx| {
35195        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
35196    });
35197    editor.update_in(cx, |editor, window, cx| {
35198        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35199    });
35200    editor.update(cx, |editor, cx| {
35201        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
35202    });
35203
35204    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
35205    let text = r#"let x = "hello"; let y = 42;"#.unindent();
35206
35207    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35208    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35209    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35210
35211    editor
35212        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35213        .await;
35214
35215    editor.update_in(cx, |editor, window, cx| {
35216        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35217            s.select_display_ranges([
35218                // Cursor inside string content
35219                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
35220                // Cursor at let statement semicolon
35221                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
35222                // Cursor inside integer literal
35223                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
35224            ]);
35225        });
35226    });
35227    editor.update(cx, |editor, cx| {
35228        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
35229    });
35230    editor.update_in(cx, |editor, window, cx| {
35231        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35232    });
35233    editor.update(cx, |editor, cx| {
35234        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
35235    });
35236
35237    // Test Group 4.2: Multiple Cursors on Separate Lines
35238    let text = r#"
35239let x = "hello";
35240let y = 42;
35241"#
35242    .unindent();
35243
35244    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35245    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35246    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35247
35248    editor
35249        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35250        .await;
35251
35252    editor.update_in(cx, |editor, window, cx| {
35253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35254            s.select_display_ranges([
35255                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
35256                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
35257            ]);
35258        });
35259    });
35260
35261    editor.update(cx, |editor, cx| {
35262        assert_text_with_selections(
35263            editor,
35264            indoc! {r#"
35265                let x = "helˇlo";
35266                let y = 4ˇ2;
35267            "#},
35268            cx,
35269        );
35270    });
35271    editor.update_in(cx, |editor, window, cx| {
35272        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35273    });
35274    editor.update(cx, |editor, cx| {
35275        assert_text_with_selections(
35276            editor,
35277            indoc! {r#"
35278                let x = "hel«loˇ»";
35279                let y = 4«2ˇ»;
35280            "#},
35281            cx,
35282        );
35283    });
35284
35285    // Test Group 5.1: Nested Function Calls
35286    let text = r#"let result = foo(bar("arg"));"#.unindent();
35287
35288    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35289    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35290    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35291
35292    editor
35293        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35294        .await;
35295
35296    editor.update_in(cx, |editor, window, cx| {
35297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35298            s.select_display_ranges([
35299                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
35300            ]);
35301        });
35302    });
35303    editor.update(cx, |editor, cx| {
35304        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
35305    });
35306    editor.update_in(cx, |editor, window, cx| {
35307        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35308    });
35309    editor.update(cx, |editor, cx| {
35310        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
35311    });
35312    editor.update_in(cx, |editor, window, cx| {
35313        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35314    });
35315    editor.update(cx, |editor, cx| {
35316        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
35317    });
35318    editor.update_in(cx, |editor, window, cx| {
35319        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35320    });
35321    editor.update(cx, |editor, cx| {
35322        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
35323    });
35324
35325    // Test Group 6.1: Block Comments
35326    let text = r#"let x = /* multi
35327                             line
35328                             comment */;"#
35329        .unindent();
35330
35331    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35333    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35334
35335    editor
35336        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35337        .await;
35338
35339    editor.update_in(cx, |editor, window, cx| {
35340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35341            s.select_display_ranges([
35342                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
35343            ]);
35344        });
35345    });
35346    editor.update(cx, |editor, cx| {
35347        assert_text_with_selections(
35348            editor,
35349            indoc! {r#"
35350let x = /* multiˇ
35351line
35352comment */;"#},
35353            cx,
35354        );
35355    });
35356    editor.update_in(cx, |editor, window, cx| {
35357        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35358    });
35359    editor.update(cx, |editor, cx| {
35360        assert_text_with_selections(
35361            editor,
35362            indoc! {r#"
35363let x = /* multi«
35364line
35365comment */ˇ»;"#},
35366            cx,
35367        );
35368    });
35369
35370    // Test Group 6.2: Array/Vector Literals
35371    let text = r#"let arr = [1, 2, 3];"#.unindent();
35372
35373    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
35374    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
35375    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
35376
35377    editor
35378        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
35379        .await;
35380
35381    editor.update_in(cx, |editor, window, cx| {
35382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
35383            s.select_display_ranges([
35384                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
35385            ]);
35386        });
35387    });
35388    editor.update(cx, |editor, cx| {
35389        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
35390    });
35391    editor.update_in(cx, |editor, window, cx| {
35392        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35393    });
35394    editor.update(cx, |editor, cx| {
35395        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
35396    });
35397    editor.update_in(cx, |editor, window, cx| {
35398        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
35399    });
35400    editor.update(cx, |editor, cx| {
35401        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
35402    });
35403}
35404
35405#[gpui::test]
35406async fn test_restore_and_next(cx: &mut TestAppContext) {
35407    init_test(cx, |_| {});
35408    let mut cx = EditorTestContext::new(cx).await;
35409
35410    let diff_base = r#"
35411        one
35412        two
35413        three
35414        four
35415        five
35416        "#
35417    .unindent();
35418
35419    cx.set_state(
35420        &r#"
35421        ONE
35422        two
35423        ˇTHREE
35424        four
35425        FIVE
35426        "#
35427        .unindent(),
35428    );
35429    cx.set_head_text(&diff_base);
35430
35431    cx.update_editor(|editor, window, cx| {
35432        editor.set_expand_all_diff_hunks(cx);
35433        editor.restore_and_next(&Default::default(), window, cx);
35434    });
35435    cx.run_until_parked();
35436
35437    cx.assert_state_with_diff(
35438        r#"
35439        - one
35440        + ONE
35441          two
35442          three
35443          four
35444        - ˇfive
35445        + FIVE
35446        "#
35447        .unindent(),
35448    );
35449
35450    cx.update_editor(|editor, window, cx| {
35451        editor.restore_and_next(&Default::default(), window, cx);
35452    });
35453    cx.run_until_parked();
35454
35455    cx.assert_state_with_diff(
35456        r#"
35457        - one
35458        + ONE
35459          two
35460          three
35461          four
35462          ˇfive
35463        "#
35464        .unindent(),
35465    );
35466}
35467
35468#[gpui::test]
35469async fn test_restore_hunk_with_stale_base_text(cx: &mut TestAppContext) {
35470    // Regression test: prepare_restore_change must read base_text from the same
35471    // snapshot the hunk came from, not from the live BufferDiff entity. The live
35472    // entity's base_text may have already been updated asynchronously (e.g.
35473    // because git HEAD changed) while the MultiBufferSnapshot still holds the
35474    // old hunk byte ranges — using both together causes Rope::slice to panic
35475    // when the old range exceeds the new base text length.
35476    init_test(cx, |_| {});
35477    let mut cx = EditorTestContext::new(cx).await;
35478
35479    let long_base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten\n";
35480    cx.set_state("ˇONE\ntwo\nTHREE\nfour\nFIVE\nsix\nseven\neight\nnine\nten\n");
35481    cx.set_head_text(long_base_text);
35482
35483    let buffer_id = cx.update_buffer(|buffer, _| buffer.remote_id());
35484
35485    // Verify we have hunks from the initial diff.
35486    let has_hunks = cx.update_editor(|editor, window, cx| {
35487        let snapshot = editor.snapshot(window, cx);
35488        let hunks = snapshot
35489            .buffer_snapshot()
35490            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len());
35491        hunks.count() > 0
35492    });
35493    assert!(has_hunks, "should have diff hunks before restoring");
35494
35495    // Now trigger a git HEAD change to a much shorter base text.
35496    // After this, the live BufferDiff entity's base_text buffer will be
35497    // updated synchronously (inside set_snapshot_with_secondary_inner),
35498    // but DiffChanged is deferred until parsing_idle completes.
35499    // We step the executor tick-by-tick to find the window where the
35500    // live base_text is already short but the MultiBuffer snapshot is
35501    // still stale (old hunks + old base_text).
35502    let short_base_text = "short\n";
35503    let fs = cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).fs().as_fake());
35504    let path = cx.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
35505    fs.set_head_for_repo(
35506        &Path::new(path!("/root")).join(".git"),
35507        &[(path.as_unix_str(), short_base_text.to_string())],
35508        "newcommit",
35509    );
35510
35511    // Step the executor tick-by-tick. At each step, check whether the
35512    // race condition exists: live BufferDiff has short base text but
35513    // the MultiBuffer snapshot still has old (long) hunks.
35514    let mut found_race = false;
35515    for _ in 0..200 {
35516        cx.executor().tick();
35517
35518        let race_exists = cx.update_editor(|editor, _window, cx| {
35519            let multi_buffer = editor.buffer().read(cx);
35520            let diff_entity = match multi_buffer.diff_for(buffer_id) {
35521                Some(d) => d,
35522                None => return false,
35523            };
35524            let live_base_len = diff_entity.read(cx).base_text(cx).len();
35525            let snapshot = multi_buffer.snapshot(cx);
35526            let snapshot_base_len = snapshot
35527                .diff_for_buffer_id(buffer_id)
35528                .map(|d| d.base_text().len());
35529            // Race: live base text is shorter than what the snapshot knows.
35530            live_base_len < long_base_text.len() && snapshot_base_len == Some(long_base_text.len())
35531        });
35532
35533        if race_exists {
35534            found_race = true;
35535            // The race window is open: the live entity has new (short) base
35536            // text but the MultiBuffer snapshot still has old hunks with byte
35537            // ranges computed against the old long base text. Attempt restore.
35538            // Without the fix, this panics with "cannot summarize past end of
35539            // rope". With the fix, it reads base_text from the stale snapshot
35540            // (consistent with the stale hunks) and succeeds.
35541            cx.update_editor(|editor, window, cx| {
35542                editor.select_all(&SelectAll, window, cx);
35543                editor.git_restore(&Default::default(), window, cx);
35544            });
35545            break;
35546        }
35547    }
35548
35549    assert!(
35550        found_race,
35551        "failed to observe the race condition between \
35552        live BufferDiff base_text and stale MultiBuffer snapshot; \
35553        the test may need adjustment if the async diff pipeline changed"
35554    );
35555}
35556
35557#[gpui::test]
35558async fn test_align_selections(cx: &mut TestAppContext) {
35559    init_test(cx, |_| {});
35560    let mut cx = EditorTestContext::new(cx).await;
35561
35562    // 1) one cursor, no action
35563    let before = " abc\n  abc\nabc\n     ˇabc";
35564    cx.set_state(before);
35565    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35566    cx.assert_editor_state(before);
35567
35568    // 2) multiple cursors at different rows
35569    let before = indoc!(
35570        r#"
35571            let aˇbc = 123;
35572            let  xˇyz = 456;
35573            let   fˇoo = 789;
35574            let    bˇar = 0;
35575        "#
35576    );
35577    let after = indoc!(
35578        r#"
35579            let a   ˇbc = 123;
35580            let  x  ˇyz = 456;
35581            let   f ˇoo = 789;
35582            let    bˇar = 0;
35583        "#
35584    );
35585    cx.set_state(before);
35586    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35587    cx.assert_editor_state(after);
35588
35589    // 3) multiple selections at different rows
35590    let before = indoc!(
35591        r#"
35592            let «ˇabc» = 123;
35593            let  «ˇxyz» = 456;
35594            let   «ˇfoo» = 789;
35595            let    «ˇbar» = 0;
35596        "#
35597    );
35598    let after = indoc!(
35599        r#"
35600            let    «ˇabc» = 123;
35601            let    «ˇxyz» = 456;
35602            let    «ˇfoo» = 789;
35603            let    «ˇbar» = 0;
35604        "#
35605    );
35606    cx.set_state(before);
35607    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35608    cx.assert_editor_state(after);
35609
35610    // 4) multiple selections at different rows, inverted head
35611    let before = indoc!(
35612        r#"
35613            let    «abcˇ» = 123;
35614            // comment
35615            let  «xyzˇ» = 456;
35616            let «fooˇ» = 789;
35617            let    «barˇ» = 0;
35618        "#
35619    );
35620    let after = indoc!(
35621        r#"
35622            let    «abcˇ» = 123;
35623            // comment
35624            let    «xyzˇ» = 456;
35625            let    «fooˇ» = 789;
35626            let    «barˇ» = 0;
35627        "#
35628    );
35629    cx.set_state(before);
35630    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35631    cx.assert_editor_state(after);
35632}
35633
35634#[gpui::test]
35635async fn test_align_selections_multicolumn(cx: &mut TestAppContext) {
35636    init_test(cx, |_| {});
35637    let mut cx = EditorTestContext::new(cx).await;
35638
35639    // 1) Multicolumn, one non affected editor row
35640    let before = indoc!(
35641        r#"
35642            name «|ˇ» age «|ˇ» height «|ˇ» note
35643            Matthew «|ˇ» 7 «|ˇ» 2333 «|ˇ» smart
35644            Mike «|ˇ» 1234 «|ˇ» 567 «|ˇ» lazy
35645            Anything that is not selected
35646            Miles «|ˇ» 88 «|ˇ» 99 «|ˇ» funny
35647        "#
35648    );
35649    let after = indoc!(
35650        r#"
35651            name    «|ˇ» age  «|ˇ» height «|ˇ» note
35652            Matthew «|ˇ» 7    «|ˇ» 2333   «|ˇ» smart
35653            Mike    «|ˇ» 1234 «|ˇ» 567    «|ˇ» lazy
35654            Anything that is not selected
35655            Miles   «|ˇ» 88   «|ˇ» 99     «|ˇ» funny
35656        "#
35657    );
35658    cx.set_state(before);
35659    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35660    cx.assert_editor_state(after);
35661
35662    // 2) not all alignment rows has the number of alignment columns
35663    let before = indoc!(
35664        r#"
35665            name «|ˇ» age «|ˇ» height
35666            Matthew «|ˇ» 7 «|ˇ» 2333
35667            Mike «|ˇ» 1234
35668            Miles «|ˇ» 88 «|ˇ» 99
35669        "#
35670    );
35671    let after = indoc!(
35672        r#"
35673            name    «|ˇ» age «|ˇ» height
35674            Matthew «|ˇ» 7   «|ˇ» 2333
35675            Mike    «|ˇ» 1234
35676            Miles   «|ˇ» 88  «|ˇ» 99
35677        "#
35678    );
35679    cx.set_state(before);
35680    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35681    cx.assert_editor_state(after);
35682
35683    // 3) A aligned column shall stay aligned
35684    let before = indoc!(
35685        r#"
35686            $ ˇa    ˇa
35687            $  ˇa   ˇa
35688            $   ˇa  ˇa
35689            $    ˇa ˇa
35690        "#
35691    );
35692    let after = indoc!(
35693        r#"
35694            $    ˇa    ˇa
35695            $    ˇa    ˇa
35696            $    ˇa    ˇa
35697            $    ˇa    ˇa
35698        "#
35699    );
35700    cx.set_state(before);
35701    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
35702    cx.assert_editor_state(after);
35703}
35704
35705#[gpui::test]
35706async fn test_custom_fallback_highlights(cx: &mut TestAppContext) {
35707    init_test(cx, |_| {});
35708
35709    let mut cx = EditorTestContext::new(cx).await;
35710    cx.set_state(indoc! {"fn main(self, variable: TType) {ˇ}"});
35711
35712    let variable_color = Hsla::green();
35713    let function_color = Hsla::blue();
35714
35715    let test_cases = [
35716        ("@variable", Some(variable_color)),
35717        ("@type", None),
35718        ("@type @variable", Some(variable_color)),
35719        ("@variable @type", Some(variable_color)),
35720        ("@variable @function", Some(function_color)),
35721        ("@function @variable", Some(variable_color)),
35722    ];
35723
35724    for (test_case, expected) in test_cases {
35725        let custom_rust_lang = Arc::into_inner(rust_lang())
35726            .unwrap()
35727            .with_highlights_query(format! {r#"(type_identifier) {test_case}"#}.as_str())
35728            .unwrap();
35729        let theme = setup_syntax_highlighting(Arc::new(custom_rust_lang), &mut cx);
35730        let expected = expected.map_or_else(Vec::new, |expected_color| {
35731            vec![(24..29, HighlightStyle::color(expected_color))]
35732        });
35733
35734        cx.update_editor(|editor, window, cx| {
35735            let snapshot = editor.snapshot(window, cx);
35736            assert_eq!(
35737                expected,
35738                snapshot.combined_highlights(MultiBufferOffset(0)..snapshot.buffer().len(), &theme),
35739                "Test case with '{test_case}' highlights query did not pass",
35740            );
35741        });
35742    }
35743}
35744
35745fn setup_syntax_highlighting(
35746    language: Arc<Language>,
35747    cx: &mut EditorTestContext,
35748) -> Arc<SyntaxTheme> {
35749    let syntax = Arc::new(SyntaxTheme::new_test(vec![
35750        ("keyword", Hsla::red()),
35751        ("function", Hsla::blue()),
35752        ("variable", Hsla::green()),
35753        ("number", Hsla::default()),
35754        ("operator", Hsla::default()),
35755        ("punctuation.bracket", Hsla::default()),
35756        ("punctuation.delimiter", Hsla::default()),
35757    ]));
35758
35759    language.set_theme(&syntax);
35760
35761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
35762    cx.executor().run_until_parked();
35763    cx.update_editor(|editor, window, cx| {
35764        editor.set_style(
35765            EditorStyle {
35766                syntax: syntax.clone(),
35767                ..EditorStyle::default()
35768            },
35769            window,
35770            cx,
35771        );
35772    });
35773
35774    syntax
35775}