editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::{CompletionParams, DEFAULT_LSP_REQUEST_TIMEOUT};
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, GlobalLspSettingsContent,
   52    IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
   53    ProjectSettingsContent, SearchSettingsContent, SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MultiWorkspace, NavigationEntry, OpenOptions,
   71    ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
  859    let workspace = window
  860        .read_with(cx, |mw, _| mw.workspace().clone())
  861        .unwrap();
  862    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
  863
  864    _ = window.update(cx, |_mw, window, cx| {
  865        cx.new(|cx| {
  866            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  867            let mut editor = build_editor(buffer, window, cx);
  868            let handle = cx.entity();
  869            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  870
  871            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  872                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  873            }
  874
  875            // Move the cursor a small distance.
  876            // Nothing is added to the navigation history.
  877            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  878                s.select_display_ranges([
  879                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  880                ])
  881            });
  882            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  883                s.select_display_ranges([
  884                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  885                ])
  886            });
  887            assert!(pop_history(&mut editor, cx).is_none());
  888
  889            // Move the cursor a large distance.
  890            // The history can jump back to the previous position.
  891            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  892                s.select_display_ranges([
  893                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  894                ])
  895            });
  896            let nav_entry = pop_history(&mut editor, cx).unwrap();
  897            editor.navigate(nav_entry.data.unwrap(), window, cx);
  898            assert_eq!(nav_entry.item.id(), cx.entity_id());
  899            assert_eq!(
  900                editor
  901                    .selections
  902                    .display_ranges(&editor.display_snapshot(cx)),
  903                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  904            );
  905            assert!(pop_history(&mut editor, cx).is_none());
  906
  907            // Move the cursor a small distance via the mouse.
  908            // Nothing is added to the navigation history.
  909            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  910            editor.end_selection(window, cx);
  911            assert_eq!(
  912                editor
  913                    .selections
  914                    .display_ranges(&editor.display_snapshot(cx)),
  915                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  916            );
  917            assert!(pop_history(&mut editor, cx).is_none());
  918
  919            // Move the cursor a large distance via the mouse.
  920            // The history can jump back to the previous position.
  921            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  922            editor.end_selection(window, cx);
  923            assert_eq!(
  924                editor
  925                    .selections
  926                    .display_ranges(&editor.display_snapshot(cx)),
  927                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  928            );
  929            let nav_entry = pop_history(&mut editor, cx).unwrap();
  930            editor.navigate(nav_entry.data.unwrap(), window, cx);
  931            assert_eq!(nav_entry.item.id(), cx.entity_id());
  932            assert_eq!(
  933                editor
  934                    .selections
  935                    .display_ranges(&editor.display_snapshot(cx)),
  936                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  937            );
  938            assert!(pop_history(&mut editor, cx).is_none());
  939
  940            // Set scroll position to check later
  941            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  942            let original_scroll_position = editor
  943                .scroll_manager
  944                .native_anchor(&editor.display_snapshot(cx), cx);
  945
  946            // Jump to the end of the document and adjust scroll
  947            editor.move_to_end(&MoveToEnd, window, cx);
  948            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  949            assert_ne!(
  950                editor
  951                    .scroll_manager
  952                    .native_anchor(&editor.display_snapshot(cx), cx),
  953                original_scroll_position
  954            );
  955
  956            let nav_entry = pop_history(&mut editor, cx).unwrap();
  957            editor.navigate(nav_entry.data.unwrap(), window, cx);
  958            assert_eq!(
  959                editor
  960                    .scroll_manager
  961                    .native_anchor(&editor.display_snapshot(cx), cx),
  962                original_scroll_position
  963            );
  964
  965            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  966            let mut invalid_anchor = editor
  967                .scroll_manager
  968                .native_anchor(&editor.display_snapshot(cx), cx)
  969                .anchor;
  970            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  971            let invalid_point = Point::new(9999, 0);
  972            editor.navigate(
  973                Arc::new(NavigationData {
  974                    cursor_anchor: invalid_anchor,
  975                    cursor_position: invalid_point,
  976                    scroll_anchor: ScrollAnchor {
  977                        anchor: invalid_anchor,
  978                        offset: Default::default(),
  979                    },
  980                    scroll_top_row: invalid_point.row,
  981                }),
  982                window,
  983                cx,
  984            );
  985            assert_eq!(
  986                editor
  987                    .selections
  988                    .display_ranges(&editor.display_snapshot(cx)),
  989                &[editor.max_point(cx)..editor.max_point(cx)]
  990            );
  991            assert_eq!(
  992                editor.scroll_position(cx),
  993                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  994            );
  995
  996            editor
  997        })
  998    });
  999}
 1000
 1001#[gpui::test]
 1002fn test_cancel(cx: &mut TestAppContext) {
 1003    init_test(cx, |_| {});
 1004
 1005    let editor = cx.add_window(|window, cx| {
 1006        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 1007        build_editor(buffer, window, cx)
 1008    });
 1009
 1010    _ = editor.update(cx, |editor, window, cx| {
 1011        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
 1012        editor.update_selection(
 1013            DisplayPoint::new(DisplayRow(1), 1),
 1014            0,
 1015            gpui::Point::<f32>::default(),
 1016            window,
 1017            cx,
 1018        );
 1019        editor.end_selection(window, cx);
 1020
 1021        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1022        editor.update_selection(
 1023            DisplayPoint::new(DisplayRow(0), 3),
 1024            0,
 1025            gpui::Point::<f32>::default(),
 1026            window,
 1027            cx,
 1028        );
 1029        editor.end_selection(window, cx);
 1030        assert_eq!(
 1031            display_ranges(editor, cx),
 1032            [
 1033                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1034                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1035            ]
 1036        );
 1037    });
 1038
 1039    _ = editor.update(cx, |editor, window, cx| {
 1040        editor.cancel(&Cancel, window, cx);
 1041        assert_eq!(
 1042            display_ranges(editor, cx),
 1043            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1044        );
 1045    });
 1046
 1047    _ = editor.update(cx, |editor, window, cx| {
 1048        editor.cancel(&Cancel, window, cx);
 1049        assert_eq!(
 1050            display_ranges(editor, cx),
 1051            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1052        );
 1053    });
 1054}
 1055
 1056#[gpui::test]
 1057fn test_fold_action(cx: &mut TestAppContext) {
 1058    init_test(cx, |_| {});
 1059
 1060    let editor = cx.add_window(|window, cx| {
 1061        let buffer = MultiBuffer::build_simple(
 1062            &"
 1063                impl Foo {
 1064                    // Hello!
 1065
 1066                    fn a() {
 1067                        1
 1068                    }
 1069
 1070                    fn b() {
 1071                        2
 1072                    }
 1073
 1074                    fn c() {
 1075                        3
 1076                    }
 1077                }
 1078            "
 1079            .unindent(),
 1080            cx,
 1081        );
 1082        build_editor(buffer, window, cx)
 1083    });
 1084
 1085    _ = editor.update(cx, |editor, window, cx| {
 1086        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1087            s.select_display_ranges([
 1088                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1089            ]);
 1090        });
 1091        editor.fold(&Fold, window, cx);
 1092        assert_eq!(
 1093            editor.display_text(cx),
 1094            "
 1095                impl Foo {
 1096                    // Hello!
 1097
 1098                    fn a() {
 1099                        1
 1100                    }
 1101
 1102                    fn b() {⋯
 1103                    }
 1104
 1105                    fn c() {⋯
 1106                    }
 1107                }
 1108            "
 1109            .unindent(),
 1110        );
 1111
 1112        editor.fold(&Fold, window, cx);
 1113        assert_eq!(
 1114            editor.display_text(cx),
 1115            "
 1116                impl Foo {⋯
 1117                }
 1118            "
 1119            .unindent(),
 1120        );
 1121
 1122        editor.unfold_lines(&UnfoldLines, window, cx);
 1123        assert_eq!(
 1124            editor.display_text(cx),
 1125            "
 1126                impl Foo {
 1127                    // Hello!
 1128
 1129                    fn a() {
 1130                        1
 1131                    }
 1132
 1133                    fn b() {⋯
 1134                    }
 1135
 1136                    fn c() {⋯
 1137                    }
 1138                }
 1139            "
 1140            .unindent(),
 1141        );
 1142
 1143        editor.unfold_lines(&UnfoldLines, window, cx);
 1144        assert_eq!(
 1145            editor.display_text(cx),
 1146            editor.buffer.read(cx).read(cx).text()
 1147        );
 1148    });
 1149}
 1150
 1151#[gpui::test]
 1152fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1153    init_test(cx, |_| {});
 1154
 1155    let editor = cx.add_window(|window, cx| {
 1156        let buffer = MultiBuffer::build_simple(
 1157            &"
 1158                class Foo:
 1159                    # Hello!
 1160
 1161                    def a():
 1162                        print(1)
 1163
 1164                    def b():
 1165                        print(2)
 1166
 1167                    def c():
 1168                        print(3)
 1169            "
 1170            .unindent(),
 1171            cx,
 1172        );
 1173        build_editor(buffer, window, cx)
 1174    });
 1175
 1176    _ = editor.update(cx, |editor, window, cx| {
 1177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1178            s.select_display_ranges([
 1179                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1180            ]);
 1181        });
 1182        editor.fold(&Fold, window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:
 1187                    # Hello!
 1188
 1189                    def a():
 1190                        print(1)
 1191
 1192                    def b():⋯
 1193
 1194                    def c():⋯
 1195            "
 1196            .unindent(),
 1197        );
 1198
 1199        editor.fold(&Fold, window, cx);
 1200        assert_eq!(
 1201            editor.display_text(cx),
 1202            "
 1203                class Foo:⋯
 1204            "
 1205            .unindent(),
 1206        );
 1207
 1208        editor.unfold_lines(&UnfoldLines, window, cx);
 1209        assert_eq!(
 1210            editor.display_text(cx),
 1211            "
 1212                class Foo:
 1213                    # Hello!
 1214
 1215                    def a():
 1216                        print(1)
 1217
 1218                    def b():⋯
 1219
 1220                    def c():⋯
 1221            "
 1222            .unindent(),
 1223        );
 1224
 1225        editor.unfold_lines(&UnfoldLines, window, cx);
 1226        assert_eq!(
 1227            editor.display_text(cx),
 1228            editor.buffer.read(cx).read(cx).text()
 1229        );
 1230    });
 1231}
 1232
 1233#[gpui::test]
 1234fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1235    init_test(cx, |_| {});
 1236
 1237    let editor = cx.add_window(|window, cx| {
 1238        let buffer = MultiBuffer::build_simple(
 1239            &"
 1240                class Foo:
 1241                    # Hello!
 1242
 1243                    def a():
 1244                        print(1)
 1245
 1246                    def b():
 1247                        print(2)
 1248
 1249
 1250                    def c():
 1251                        print(3)
 1252
 1253
 1254            "
 1255            .unindent(),
 1256            cx,
 1257        );
 1258        build_editor(buffer, window, cx)
 1259    });
 1260
 1261    _ = editor.update(cx, |editor, window, cx| {
 1262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1263            s.select_display_ranges([
 1264                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1265            ]);
 1266        });
 1267        editor.fold(&Fold, window, cx);
 1268        assert_eq!(
 1269            editor.display_text(cx),
 1270            "
 1271                class Foo:
 1272                    # Hello!
 1273
 1274                    def a():
 1275                        print(1)
 1276
 1277                    def b():⋯
 1278
 1279
 1280                    def c():⋯
 1281
 1282
 1283            "
 1284            .unindent(),
 1285        );
 1286
 1287        editor.fold(&Fold, window, cx);
 1288        assert_eq!(
 1289            editor.display_text(cx),
 1290            "
 1291                class Foo:⋯
 1292
 1293
 1294            "
 1295            .unindent(),
 1296        );
 1297
 1298        editor.unfold_lines(&UnfoldLines, window, cx);
 1299        assert_eq!(
 1300            editor.display_text(cx),
 1301            "
 1302                class Foo:
 1303                    # Hello!
 1304
 1305                    def a():
 1306                        print(1)
 1307
 1308                    def b():⋯
 1309
 1310
 1311                    def c():⋯
 1312
 1313
 1314            "
 1315            .unindent(),
 1316        );
 1317
 1318        editor.unfold_lines(&UnfoldLines, window, cx);
 1319        assert_eq!(
 1320            editor.display_text(cx),
 1321            editor.buffer.read(cx).read(cx).text()
 1322        );
 1323    });
 1324}
 1325
 1326#[gpui::test]
 1327fn test_fold_at_level(cx: &mut TestAppContext) {
 1328    init_test(cx, |_| {});
 1329
 1330    let editor = cx.add_window(|window, cx| {
 1331        let buffer = MultiBuffer::build_simple(
 1332            &"
 1333                class Foo:
 1334                    # Hello!
 1335
 1336                    def a():
 1337                        print(1)
 1338
 1339                    def b():
 1340                        print(2)
 1341
 1342
 1343                class Bar:
 1344                    # World!
 1345
 1346                    def a():
 1347                        print(1)
 1348
 1349                    def b():
 1350                        print(2)
 1351
 1352
 1353            "
 1354            .unindent(),
 1355            cx,
 1356        );
 1357        build_editor(buffer, window, cx)
 1358    });
 1359
 1360    _ = editor.update(cx, |editor, window, cx| {
 1361        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1362        assert_eq!(
 1363            editor.display_text(cx),
 1364            "
 1365                class Foo:
 1366                    # Hello!
 1367
 1368                    def a():⋯
 1369
 1370                    def b():⋯
 1371
 1372
 1373                class Bar:
 1374                    # World!
 1375
 1376                    def a():⋯
 1377
 1378                    def b():⋯
 1379
 1380
 1381            "
 1382            .unindent(),
 1383        );
 1384
 1385        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1386        assert_eq!(
 1387            editor.display_text(cx),
 1388            "
 1389                class Foo:⋯
 1390
 1391
 1392                class Bar:⋯
 1393
 1394
 1395            "
 1396            .unindent(),
 1397        );
 1398
 1399        editor.unfold_all(&UnfoldAll, window, cx);
 1400        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1401        assert_eq!(
 1402            editor.display_text(cx),
 1403            "
 1404                class Foo:
 1405                    # Hello!
 1406
 1407                    def a():
 1408                        print(1)
 1409
 1410                    def b():
 1411                        print(2)
 1412
 1413
 1414                class Bar:
 1415                    # World!
 1416
 1417                    def a():
 1418                        print(1)
 1419
 1420                    def b():
 1421                        print(2)
 1422
 1423
 1424            "
 1425            .unindent(),
 1426        );
 1427
 1428        assert_eq!(
 1429            editor.display_text(cx),
 1430            editor.buffer.read(cx).read(cx).text()
 1431        );
 1432        let (_, positions) = marked_text_ranges(
 1433            &"
 1434                       class Foo:
 1435                           # Hello!
 1436
 1437                           def a():
 1438                              print(1)
 1439
 1440                           def b():
 1441                               p«riˇ»nt(2)
 1442
 1443
 1444                       class Bar:
 1445                           # World!
 1446
 1447                           def a():
 1448                               «ˇprint(1)
 1449
 1450                           def b():
 1451                               print(2)»
 1452
 1453
 1454                   "
 1455            .unindent(),
 1456            true,
 1457        );
 1458
 1459        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1460            s.select_ranges(
 1461                positions
 1462                    .iter()
 1463                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1464            )
 1465        });
 1466
 1467        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1468        assert_eq!(
 1469            editor.display_text(cx),
 1470            "
 1471                class Foo:
 1472                    # Hello!
 1473
 1474                    def a():⋯
 1475
 1476                    def b():
 1477                        print(2)
 1478
 1479
 1480                class Bar:
 1481                    # World!
 1482
 1483                    def a():
 1484                        print(1)
 1485
 1486                    def b():
 1487                        print(2)
 1488
 1489
 1490            "
 1491            .unindent(),
 1492        );
 1493    });
 1494}
 1495
 1496#[gpui::test]
 1497fn test_move_cursor(cx: &mut TestAppContext) {
 1498    init_test(cx, |_| {});
 1499
 1500    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1501    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1502
 1503    buffer.update(cx, |buffer, cx| {
 1504        buffer.edit(
 1505            vec![
 1506                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1507                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1508            ],
 1509            None,
 1510            cx,
 1511        );
 1512    });
 1513    _ = editor.update(cx, |editor, window, cx| {
 1514        assert_eq!(
 1515            display_ranges(editor, cx),
 1516            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1517        );
 1518
 1519        editor.move_down(&MoveDown, window, cx);
 1520        assert_eq!(
 1521            display_ranges(editor, cx),
 1522            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1523        );
 1524
 1525        editor.move_right(&MoveRight, window, cx);
 1526        assert_eq!(
 1527            display_ranges(editor, cx),
 1528            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1529        );
 1530
 1531        editor.move_left(&MoveLeft, window, cx);
 1532        assert_eq!(
 1533            display_ranges(editor, cx),
 1534            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1535        );
 1536
 1537        editor.move_up(&MoveUp, window, cx);
 1538        assert_eq!(
 1539            display_ranges(editor, cx),
 1540            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1541        );
 1542
 1543        editor.move_to_end(&MoveToEnd, window, cx);
 1544        assert_eq!(
 1545            display_ranges(editor, cx),
 1546            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1547        );
 1548
 1549        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1550        assert_eq!(
 1551            display_ranges(editor, cx),
 1552            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1553        );
 1554
 1555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1556            s.select_display_ranges([
 1557                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1558            ]);
 1559        });
 1560        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1561        assert_eq!(
 1562            display_ranges(editor, cx),
 1563            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1564        );
 1565
 1566        editor.select_to_end(&SelectToEnd, window, cx);
 1567        assert_eq!(
 1568            display_ranges(editor, cx),
 1569            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1570        );
 1571    });
 1572}
 1573
 1574#[gpui::test]
 1575fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1576    init_test(cx, |_| {});
 1577
 1578    let editor = cx.add_window(|window, cx| {
 1579        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1580        build_editor(buffer, window, cx)
 1581    });
 1582
 1583    assert_eq!('🟥'.len_utf8(), 4);
 1584    assert_eq!('α'.len_utf8(), 2);
 1585
 1586    _ = editor.update(cx, |editor, window, cx| {
 1587        editor.fold_creases(
 1588            vec![
 1589                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1590                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1591                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1592            ],
 1593            true,
 1594            window,
 1595            cx,
 1596        );
 1597        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1598
 1599        editor.move_right(&MoveRight, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1605
 1606        editor.move_down(&MoveDown, window, cx);
 1607        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1608        editor.move_left(&MoveLeft, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1610        editor.move_left(&MoveLeft, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1612        editor.move_left(&MoveLeft, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1614
 1615        editor.move_down(&MoveDown, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1617        editor.move_right(&MoveRight, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1619        editor.move_right(&MoveRight, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1621        editor.move_right(&MoveRight, window, cx);
 1622        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1623
 1624        editor.move_up(&MoveUp, window, cx);
 1625        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1626        editor.move_down(&MoveDown, window, cx);
 1627        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1628        editor.move_up(&MoveUp, window, cx);
 1629        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1630
 1631        editor.move_up(&MoveUp, window, cx);
 1632        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1633        editor.move_left(&MoveLeft, window, cx);
 1634        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1635        editor.move_left(&MoveLeft, window, cx);
 1636        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1637    });
 1638}
 1639
 1640#[gpui::test]
 1641fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1642    init_test(cx, |_| {});
 1643
 1644    let editor = cx.add_window(|window, cx| {
 1645        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1646        build_editor(buffer, window, cx)
 1647    });
 1648    _ = editor.update(cx, |editor, window, cx| {
 1649        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1650            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1651        });
 1652
 1653        // moving above start of document should move selection to start of document,
 1654        // but the next move down should still be at the original goal_x
 1655        editor.move_up(&MoveUp, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1660
 1661        editor.move_down(&MoveDown, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1663
 1664        editor.move_down(&MoveDown, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_down(&MoveDown, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1669
 1670        // moving past end of document should not change goal_x
 1671        editor.move_down(&MoveDown, window, cx);
 1672        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1673
 1674        editor.move_down(&MoveDown, window, cx);
 1675        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1676
 1677        editor.move_up(&MoveUp, window, cx);
 1678        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1679
 1680        editor.move_up(&MoveUp, window, cx);
 1681        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1682
 1683        editor.move_up(&MoveUp, window, cx);
 1684        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1685    });
 1686}
 1687
 1688#[gpui::test]
 1689fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1690    init_test(cx, |_| {});
 1691    let move_to_beg = MoveToBeginningOfLine {
 1692        stop_at_soft_wraps: true,
 1693        stop_at_indent: true,
 1694    };
 1695
 1696    let delete_to_beg = DeleteToBeginningOfLine {
 1697        stop_at_indent: false,
 1698    };
 1699
 1700    let move_to_end = MoveToEndOfLine {
 1701        stop_at_soft_wraps: true,
 1702    };
 1703
 1704    let editor = cx.add_window(|window, cx| {
 1705        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1706        build_editor(buffer, window, cx)
 1707    });
 1708    _ = editor.update(cx, |editor, window, cx| {
 1709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1710            s.select_display_ranges([
 1711                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1712                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1713            ]);
 1714        });
 1715    });
 1716
 1717    _ = editor.update(cx, |editor, window, cx| {
 1718        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1719        assert_eq!(
 1720            display_ranges(editor, cx),
 1721            &[
 1722                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1723                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1724            ]
 1725        );
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1730        assert_eq!(
 1731            display_ranges(editor, cx),
 1732            &[
 1733                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1734                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1735            ]
 1736        );
 1737    });
 1738
 1739    _ = editor.update(cx, |editor, window, cx| {
 1740        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1741        assert_eq!(
 1742            display_ranges(editor, cx),
 1743            &[
 1744                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1745                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1746            ]
 1747        );
 1748    });
 1749
 1750    _ = editor.update(cx, |editor, window, cx| {
 1751        editor.move_to_end_of_line(&move_to_end, window, cx);
 1752        assert_eq!(
 1753            display_ranges(editor, cx),
 1754            &[
 1755                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1756                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1757            ]
 1758        );
 1759    });
 1760
 1761    // Moving to the end of line again is a no-op.
 1762    _ = editor.update(cx, |editor, window, cx| {
 1763        editor.move_to_end_of_line(&move_to_end, window, cx);
 1764        assert_eq!(
 1765            display_ranges(editor, cx),
 1766            &[
 1767                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1768                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1769            ]
 1770        );
 1771    });
 1772
 1773    _ = editor.update(cx, |editor, window, cx| {
 1774        editor.move_left(&MoveLeft, window, cx);
 1775        editor.select_to_beginning_of_line(
 1776            &SelectToBeginningOfLine {
 1777                stop_at_soft_wraps: true,
 1778                stop_at_indent: true,
 1779            },
 1780            window,
 1781            cx,
 1782        );
 1783        assert_eq!(
 1784            display_ranges(editor, cx),
 1785            &[
 1786                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1787                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1788            ]
 1789        );
 1790    });
 1791
 1792    _ = editor.update(cx, |editor, window, cx| {
 1793        editor.select_to_beginning_of_line(
 1794            &SelectToBeginningOfLine {
 1795                stop_at_soft_wraps: true,
 1796                stop_at_indent: true,
 1797            },
 1798            window,
 1799            cx,
 1800        );
 1801        assert_eq!(
 1802            display_ranges(editor, cx),
 1803            &[
 1804                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1805                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1806            ]
 1807        );
 1808    });
 1809
 1810    _ = editor.update(cx, |editor, window, cx| {
 1811        editor.select_to_beginning_of_line(
 1812            &SelectToBeginningOfLine {
 1813                stop_at_soft_wraps: true,
 1814                stop_at_indent: true,
 1815            },
 1816            window,
 1817            cx,
 1818        );
 1819        assert_eq!(
 1820            display_ranges(editor, cx),
 1821            &[
 1822                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1823                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1824            ]
 1825        );
 1826    });
 1827
 1828    _ = editor.update(cx, |editor, window, cx| {
 1829        editor.select_to_end_of_line(
 1830            &SelectToEndOfLine {
 1831                stop_at_soft_wraps: true,
 1832            },
 1833            window,
 1834            cx,
 1835        );
 1836        assert_eq!(
 1837            display_ranges(editor, cx),
 1838            &[
 1839                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1840                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1841            ]
 1842        );
 1843    });
 1844
 1845    _ = editor.update(cx, |editor, window, cx| {
 1846        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1847        assert_eq!(editor.display_text(cx), "ab\n  de");
 1848        assert_eq!(
 1849            display_ranges(editor, cx),
 1850            &[
 1851                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1852                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1853            ]
 1854        );
 1855    });
 1856
 1857    _ = editor.update(cx, |editor, window, cx| {
 1858        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1859        assert_eq!(editor.display_text(cx), "\n");
 1860        assert_eq!(
 1861            display_ranges(editor, cx),
 1862            &[
 1863                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1864                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1865            ]
 1866        );
 1867    });
 1868}
 1869
 1870#[gpui::test]
 1871fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1872    init_test(cx, |_| {});
 1873    let move_to_beg = MoveToBeginningOfLine {
 1874        stop_at_soft_wraps: false,
 1875        stop_at_indent: false,
 1876    };
 1877
 1878    let move_to_end = MoveToEndOfLine {
 1879        stop_at_soft_wraps: false,
 1880    };
 1881
 1882    let editor = cx.add_window(|window, cx| {
 1883        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1884        build_editor(buffer, window, cx)
 1885    });
 1886
 1887    _ = editor.update(cx, |editor, window, cx| {
 1888        editor.set_wrap_width(Some(140.0.into()), cx);
 1889
 1890        // We expect the following lines after wrapping
 1891        // ```
 1892        // thequickbrownfox
 1893        // jumpedoverthelazydo
 1894        // gs
 1895        // ```
 1896        // The final `gs` was soft-wrapped onto a new line.
 1897        assert_eq!(
 1898            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1899            editor.display_text(cx),
 1900        );
 1901
 1902        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1903        // Start the cursor at the `k` on the first line
 1904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1905            s.select_display_ranges([
 1906                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1907            ]);
 1908        });
 1909
 1910        // Moving to the beginning of the line should put us at the beginning of the line.
 1911        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1912        assert_eq!(
 1913            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1914            display_ranges(editor, cx)
 1915        );
 1916
 1917        // Moving to the end of the line should put us at the end of the line.
 1918        editor.move_to_end_of_line(&move_to_end, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1925        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1927            s.select_display_ranges([
 1928                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1929            ]);
 1930        });
 1931
 1932        // Moving to the beginning of the line should put us at the start of the second line of
 1933        // display text, i.e., the `j`.
 1934        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1937            display_ranges(editor, cx)
 1938        );
 1939
 1940        // Moving to the beginning of the line again should be a no-op.
 1941        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1942        assert_eq!(
 1943            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1944            display_ranges(editor, cx)
 1945        );
 1946
 1947        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1948        // next display line.
 1949        editor.move_to_end_of_line(&move_to_end, window, cx);
 1950        assert_eq!(
 1951            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1952            display_ranges(editor, cx)
 1953        );
 1954
 1955        // Moving to the end of the line again should be a no-op.
 1956        editor.move_to_end_of_line(&move_to_end, window, cx);
 1957        assert_eq!(
 1958            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1959            display_ranges(editor, cx)
 1960        );
 1961    });
 1962}
 1963
 1964#[gpui::test]
 1965fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1966    init_test(cx, |_| {});
 1967
 1968    let move_to_beg = MoveToBeginningOfLine {
 1969        stop_at_soft_wraps: true,
 1970        stop_at_indent: true,
 1971    };
 1972
 1973    let select_to_beg = SelectToBeginningOfLine {
 1974        stop_at_soft_wraps: true,
 1975        stop_at_indent: true,
 1976    };
 1977
 1978    let delete_to_beg = DeleteToBeginningOfLine {
 1979        stop_at_indent: true,
 1980    };
 1981
 1982    let move_to_end = MoveToEndOfLine {
 1983        stop_at_soft_wraps: false,
 1984    };
 1985
 1986    let editor = cx.add_window(|window, cx| {
 1987        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1988        build_editor(buffer, window, cx)
 1989    });
 1990
 1991    _ = editor.update(cx, |editor, window, cx| {
 1992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1993            s.select_display_ranges([
 1994                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1995                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1996            ]);
 1997        });
 1998
 1999        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2000        // and the second cursor at the first non-whitespace character in the line.
 2001        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2002        assert_eq!(
 2003            display_ranges(editor, cx),
 2004            &[
 2005                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2006                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2007            ]
 2008        );
 2009
 2010        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2011        // and should move the second cursor to the beginning of the line.
 2012        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2013        assert_eq!(
 2014            display_ranges(editor, cx),
 2015            &[
 2016                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2017                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2018            ]
 2019        );
 2020
 2021        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2022        // and should move the second cursor back to the first non-whitespace character in the line.
 2023        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2024        assert_eq!(
 2025            display_ranges(editor, cx),
 2026            &[
 2027                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2028                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2029            ]
 2030        );
 2031
 2032        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2033        // and to the first non-whitespace character in the line for the second cursor.
 2034        editor.move_to_end_of_line(&move_to_end, window, cx);
 2035        editor.move_left(&MoveLeft, window, cx);
 2036        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2037        assert_eq!(
 2038            display_ranges(editor, cx),
 2039            &[
 2040                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2041                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2042            ]
 2043        );
 2044
 2045        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2046        // and should select to the beginning of the line for the second cursor.
 2047        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2048        assert_eq!(
 2049            display_ranges(editor, cx),
 2050            &[
 2051                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2052                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2053            ]
 2054        );
 2055
 2056        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2057        // and should delete to the first non-whitespace character in the line for the second cursor.
 2058        editor.move_to_end_of_line(&move_to_end, window, cx);
 2059        editor.move_left(&MoveLeft, window, cx);
 2060        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2061        assert_eq!(editor.text(cx), "c\n  f");
 2062    });
 2063}
 2064
 2065#[gpui::test]
 2066fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2067    init_test(cx, |_| {});
 2068
 2069    let move_to_beg = MoveToBeginningOfLine {
 2070        stop_at_soft_wraps: true,
 2071        stop_at_indent: true,
 2072    };
 2073
 2074    let editor = cx.add_window(|window, cx| {
 2075        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2076        build_editor(buffer, window, cx)
 2077    });
 2078
 2079    _ = editor.update(cx, |editor, window, cx| {
 2080        // test cursor between line_start and indent_start
 2081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2082            s.select_display_ranges([
 2083                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2084            ]);
 2085        });
 2086
 2087        // cursor should move to line_start
 2088        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2089        assert_eq!(
 2090            display_ranges(editor, cx),
 2091            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2092        );
 2093
 2094        // cursor should move to indent_start
 2095        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2096        assert_eq!(
 2097            display_ranges(editor, cx),
 2098            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2099        );
 2100
 2101        // cursor should move to back to line_start
 2102        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2103        assert_eq!(
 2104            display_ranges(editor, cx),
 2105            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2106        );
 2107    });
 2108}
 2109
 2110#[gpui::test]
 2111fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2112    init_test(cx, |_| {});
 2113
 2114    let editor = cx.add_window(|window, cx| {
 2115        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2116        build_editor(buffer, window, cx)
 2117    });
 2118    _ = editor.update(cx, |editor, window, cx| {
 2119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2120            s.select_display_ranges([
 2121                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2122                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2123            ])
 2124        });
 2125        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2126        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2127
 2128        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2129        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2130
 2131        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2132        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2133
 2134        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2135        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2136
 2137        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2138        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2139
 2140        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2141        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2142
 2143        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2144        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2145
 2146        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2147        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2148
 2149        editor.move_right(&MoveRight, window, cx);
 2150        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2151        assert_selection_ranges(
 2152            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2153            editor,
 2154            cx,
 2155        );
 2156
 2157        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2158        assert_selection_ranges(
 2159            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2160            editor,
 2161            cx,
 2162        );
 2163
 2164        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2165        assert_selection_ranges(
 2166            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2167            editor,
 2168            cx,
 2169        );
 2170    });
 2171}
 2172
 2173#[gpui::test]
 2174fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2175    init_test(cx, |_| {});
 2176
 2177    let editor = cx.add_window(|window, cx| {
 2178        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2179        build_editor(buffer, window, cx)
 2180    });
 2181
 2182    _ = editor.update(cx, |editor, window, cx| {
 2183        editor.set_wrap_width(Some(140.0.into()), cx);
 2184        assert_eq!(
 2185            editor.display_text(cx),
 2186            "use one::{\n    two::three::\n    four::five\n};"
 2187        );
 2188
 2189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2190            s.select_display_ranges([
 2191                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2192            ]);
 2193        });
 2194
 2195        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2196        assert_eq!(
 2197            display_ranges(editor, cx),
 2198            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2199        );
 2200
 2201        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2202        assert_eq!(
 2203            display_ranges(editor, cx),
 2204            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2205        );
 2206
 2207        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2208        assert_eq!(
 2209            display_ranges(editor, cx),
 2210            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2211        );
 2212
 2213        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2214        assert_eq!(
 2215            display_ranges(editor, cx),
 2216            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2217        );
 2218
 2219        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2220        assert_eq!(
 2221            display_ranges(editor, cx),
 2222            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2223        );
 2224
 2225        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2226        assert_eq!(
 2227            display_ranges(editor, cx),
 2228            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2229        );
 2230    });
 2231}
 2232
 2233#[gpui::test]
 2234async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2235    init_test(cx, |_| {});
 2236    let mut cx = EditorTestContext::new(cx).await;
 2237
 2238    let line_height = cx.update_editor(|editor, window, cx| {
 2239        editor
 2240            .style(cx)
 2241            .text
 2242            .line_height_in_pixels(window.rem_size())
 2243    });
 2244    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2245
 2246    // The third line only contains a single space so we can later assert that the
 2247    // editor's paragraph movement considers a non-blank line as a paragraph
 2248    // boundary.
 2249    cx.set_state(&"ˇone\ntwo\n \nthree\nfourˇ\nfive\n\nsix");
 2250
 2251    cx.update_editor(|editor, window, cx| {
 2252        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2253    });
 2254    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\nˇ\nsix");
 2255
 2256    cx.update_editor(|editor, window, cx| {
 2257        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2258    });
 2259    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsixˇ");
 2260
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2263    });
 2264    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n\nsixˇ");
 2265
 2266    cx.update_editor(|editor, window, cx| {
 2267        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2268    });
 2269    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsix");
 2270
 2271    cx.update_editor(|editor, window, cx| {
 2272        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2273    });
 2274
 2275    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\n\nsix");
 2276
 2277    cx.update_editor(|editor, window, cx| {
 2278        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2279    });
 2280    cx.assert_editor_state(&"ˇone\ntwo\n \nthree\nfour\nfive\n\nsix");
 2281}
 2282
 2283#[gpui::test]
 2284async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2285    init_test(cx, |_| {});
 2286    let mut cx = EditorTestContext::new(cx).await;
 2287    let line_height = cx.update_editor(|editor, window, cx| {
 2288        editor
 2289            .style(cx)
 2290            .text
 2291            .line_height_in_pixels(window.rem_size())
 2292    });
 2293    let window = cx.window;
 2294    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2295
 2296    cx.set_state(
 2297        r#"ˇone
 2298        two
 2299        three
 2300        four
 2301        five
 2302        six
 2303        seven
 2304        eight
 2305        nine
 2306        ten
 2307        "#,
 2308    );
 2309
 2310    cx.update_editor(|editor, window, cx| {
 2311        assert_eq!(
 2312            editor.snapshot(window, cx).scroll_position(),
 2313            gpui::Point::new(0., 0.)
 2314        );
 2315        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2316        assert_eq!(
 2317            editor.snapshot(window, cx).scroll_position(),
 2318            gpui::Point::new(0., 3.)
 2319        );
 2320        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2321        assert_eq!(
 2322            editor.snapshot(window, cx).scroll_position(),
 2323            gpui::Point::new(0., 6.)
 2324        );
 2325        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2326        assert_eq!(
 2327            editor.snapshot(window, cx).scroll_position(),
 2328            gpui::Point::new(0., 3.)
 2329        );
 2330
 2331        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2332        assert_eq!(
 2333            editor.snapshot(window, cx).scroll_position(),
 2334            gpui::Point::new(0., 1.)
 2335        );
 2336        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2337        assert_eq!(
 2338            editor.snapshot(window, cx).scroll_position(),
 2339            gpui::Point::new(0., 3.)
 2340        );
 2341    });
 2342}
 2343
 2344#[gpui::test]
 2345async fn test_autoscroll(cx: &mut TestAppContext) {
 2346    init_test(cx, |_| {});
 2347    let mut cx = EditorTestContext::new(cx).await;
 2348
 2349    let line_height = cx.update_editor(|editor, window, cx| {
 2350        editor.set_vertical_scroll_margin(2, cx);
 2351        editor
 2352            .style(cx)
 2353            .text
 2354            .line_height_in_pixels(window.rem_size())
 2355    });
 2356    let window = cx.window;
 2357    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2358
 2359    cx.set_state(
 2360        r#"ˇone
 2361            two
 2362            three
 2363            four
 2364            five
 2365            six
 2366            seven
 2367            eight
 2368            nine
 2369            ten
 2370        "#,
 2371    );
 2372    cx.update_editor(|editor, window, cx| {
 2373        assert_eq!(
 2374            editor.snapshot(window, cx).scroll_position(),
 2375            gpui::Point::new(0., 0.0)
 2376        );
 2377    });
 2378
 2379    // Add a cursor below the visible area. Since both cursors cannot fit
 2380    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2381    // allows the vertical scroll margin below that cursor.
 2382    cx.update_editor(|editor, window, cx| {
 2383        editor.change_selections(Default::default(), window, cx, |selections| {
 2384            selections.select_ranges([
 2385                Point::new(0, 0)..Point::new(0, 0),
 2386                Point::new(6, 0)..Point::new(6, 0),
 2387            ]);
 2388        })
 2389    });
 2390    cx.update_editor(|editor, window, cx| {
 2391        assert_eq!(
 2392            editor.snapshot(window, cx).scroll_position(),
 2393            gpui::Point::new(0., 3.0)
 2394        );
 2395    });
 2396
 2397    // Move down. The editor cursor scrolls down to track the newest cursor.
 2398    cx.update_editor(|editor, window, cx| {
 2399        editor.move_down(&Default::default(), window, cx);
 2400    });
 2401    cx.update_editor(|editor, window, cx| {
 2402        assert_eq!(
 2403            editor.snapshot(window, cx).scroll_position(),
 2404            gpui::Point::new(0., 4.0)
 2405        );
 2406    });
 2407
 2408    // Add a cursor above the visible area. Since both cursors fit on screen,
 2409    // the editor scrolls to show both.
 2410    cx.update_editor(|editor, window, cx| {
 2411        editor.change_selections(Default::default(), window, cx, |selections| {
 2412            selections.select_ranges([
 2413                Point::new(1, 0)..Point::new(1, 0),
 2414                Point::new(6, 0)..Point::new(6, 0),
 2415            ]);
 2416        })
 2417    });
 2418    cx.update_editor(|editor, window, cx| {
 2419        assert_eq!(
 2420            editor.snapshot(window, cx).scroll_position(),
 2421            gpui::Point::new(0., 1.0)
 2422        );
 2423    });
 2424}
 2425
 2426#[gpui::test]
 2427async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2428    init_test(cx, |_| {});
 2429    let mut cx = EditorTestContext::new(cx).await;
 2430
 2431    let line_height = cx.update_editor(|editor, window, cx| {
 2432        editor
 2433            .style(cx)
 2434            .text
 2435            .line_height_in_pixels(window.rem_size())
 2436    });
 2437    let window = cx.window;
 2438    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2439    cx.set_state(
 2440        &r#"
 2441        ˇone
 2442        two
 2443        threeˇ
 2444        four
 2445        five
 2446        six
 2447        seven
 2448        eight
 2449        nine
 2450        ten
 2451        "#
 2452        .unindent(),
 2453    );
 2454
 2455    cx.update_editor(|editor, window, cx| {
 2456        editor.move_page_down(&MovePageDown::default(), window, cx)
 2457    });
 2458    cx.assert_editor_state(
 2459        &r#"
 2460        one
 2461        two
 2462        three
 2463        ˇfour
 2464        five
 2465        sixˇ
 2466        seven
 2467        eight
 2468        nine
 2469        ten
 2470        "#
 2471        .unindent(),
 2472    );
 2473
 2474    cx.update_editor(|editor, window, cx| {
 2475        editor.move_page_down(&MovePageDown::default(), window, cx)
 2476    });
 2477    cx.assert_editor_state(
 2478        &r#"
 2479        one
 2480        two
 2481        three
 2482        four
 2483        five
 2484        six
 2485        ˇseven
 2486        eight
 2487        nineˇ
 2488        ten
 2489        "#
 2490        .unindent(),
 2491    );
 2492
 2493    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2494    cx.assert_editor_state(
 2495        &r#"
 2496        one
 2497        two
 2498        three
 2499        ˇfour
 2500        five
 2501        sixˇ
 2502        seven
 2503        eight
 2504        nine
 2505        ten
 2506        "#
 2507        .unindent(),
 2508    );
 2509
 2510    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2511    cx.assert_editor_state(
 2512        &r#"
 2513        ˇone
 2514        two
 2515        threeˇ
 2516        four
 2517        five
 2518        six
 2519        seven
 2520        eight
 2521        nine
 2522        ten
 2523        "#
 2524        .unindent(),
 2525    );
 2526
 2527    // Test select collapsing
 2528    cx.update_editor(|editor, window, cx| {
 2529        editor.move_page_down(&MovePageDown::default(), window, cx);
 2530        editor.move_page_down(&MovePageDown::default(), window, cx);
 2531        editor.move_page_down(&MovePageDown::default(), window, cx);
 2532    });
 2533    cx.assert_editor_state(
 2534        &r#"
 2535        one
 2536        two
 2537        three
 2538        four
 2539        five
 2540        six
 2541        seven
 2542        eight
 2543        nine
 2544        ˇten
 2545        ˇ"#
 2546        .unindent(),
 2547    );
 2548}
 2549
 2550#[gpui::test]
 2551async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2552    init_test(cx, |_| {});
 2553    let mut cx = EditorTestContext::new(cx).await;
 2554    cx.set_state("one «two threeˇ» four");
 2555    cx.update_editor(|editor, window, cx| {
 2556        editor.delete_to_beginning_of_line(
 2557            &DeleteToBeginningOfLine {
 2558                stop_at_indent: false,
 2559            },
 2560            window,
 2561            cx,
 2562        );
 2563        assert_eq!(editor.text(cx), " four");
 2564    });
 2565}
 2566
 2567#[gpui::test]
 2568async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2569    init_test(cx, |_| {});
 2570
 2571    let mut cx = EditorTestContext::new(cx).await;
 2572
 2573    // For an empty selection, the preceding word fragment is deleted.
 2574    // For non-empty selections, only selected characters are deleted.
 2575    cx.set_state("onˇe two t«hreˇ»e four");
 2576    cx.update_editor(|editor, window, cx| {
 2577        editor.delete_to_previous_word_start(
 2578            &DeleteToPreviousWordStart {
 2579                ignore_newlines: false,
 2580                ignore_brackets: false,
 2581            },
 2582            window,
 2583            cx,
 2584        );
 2585    });
 2586    cx.assert_editor_state("ˇe two tˇe four");
 2587
 2588    cx.set_state("e tˇwo te «fˇ»our");
 2589    cx.update_editor(|editor, window, cx| {
 2590        editor.delete_to_next_word_end(
 2591            &DeleteToNextWordEnd {
 2592                ignore_newlines: false,
 2593                ignore_brackets: false,
 2594            },
 2595            window,
 2596            cx,
 2597        );
 2598    });
 2599    cx.assert_editor_state("e tˇ te ˇour");
 2600}
 2601
 2602#[gpui::test]
 2603async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2604    init_test(cx, |_| {});
 2605
 2606    let mut cx = EditorTestContext::new(cx).await;
 2607
 2608    cx.set_state("here is some text    ˇwith a space");
 2609    cx.update_editor(|editor, window, cx| {
 2610        editor.delete_to_previous_word_start(
 2611            &DeleteToPreviousWordStart {
 2612                ignore_newlines: false,
 2613                ignore_brackets: true,
 2614            },
 2615            window,
 2616            cx,
 2617        );
 2618    });
 2619    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2620    cx.assert_editor_state("here is some textˇwith a space");
 2621
 2622    cx.set_state("here is some text    ˇwith a space");
 2623    cx.update_editor(|editor, window, cx| {
 2624        editor.delete_to_previous_word_start(
 2625            &DeleteToPreviousWordStart {
 2626                ignore_newlines: false,
 2627                ignore_brackets: false,
 2628            },
 2629            window,
 2630            cx,
 2631        );
 2632    });
 2633    cx.assert_editor_state("here is some textˇwith a space");
 2634
 2635    cx.set_state("here is some textˇ    with a space");
 2636    cx.update_editor(|editor, window, cx| {
 2637        editor.delete_to_next_word_end(
 2638            &DeleteToNextWordEnd {
 2639                ignore_newlines: false,
 2640                ignore_brackets: true,
 2641            },
 2642            window,
 2643            cx,
 2644        );
 2645    });
 2646    // Same happens in the other direction.
 2647    cx.assert_editor_state("here is some textˇwith a space");
 2648
 2649    cx.set_state("here is some textˇ    with a space");
 2650    cx.update_editor(|editor, window, cx| {
 2651        editor.delete_to_next_word_end(
 2652            &DeleteToNextWordEnd {
 2653                ignore_newlines: false,
 2654                ignore_brackets: false,
 2655            },
 2656            window,
 2657            cx,
 2658        );
 2659    });
 2660    cx.assert_editor_state("here is some textˇwith a space");
 2661
 2662    cx.set_state("here is some textˇ    with a space");
 2663    cx.update_editor(|editor, window, cx| {
 2664        editor.delete_to_next_word_end(
 2665            &DeleteToNextWordEnd {
 2666                ignore_newlines: true,
 2667                ignore_brackets: false,
 2668            },
 2669            window,
 2670            cx,
 2671        );
 2672    });
 2673    cx.assert_editor_state("here is some textˇwith a space");
 2674    cx.update_editor(|editor, window, cx| {
 2675        editor.delete_to_previous_word_start(
 2676            &DeleteToPreviousWordStart {
 2677                ignore_newlines: true,
 2678                ignore_brackets: false,
 2679            },
 2680            window,
 2681            cx,
 2682        );
 2683    });
 2684    cx.assert_editor_state("here is some ˇwith a space");
 2685    cx.update_editor(|editor, window, cx| {
 2686        editor.delete_to_previous_word_start(
 2687            &DeleteToPreviousWordStart {
 2688                ignore_newlines: true,
 2689                ignore_brackets: false,
 2690            },
 2691            window,
 2692            cx,
 2693        );
 2694    });
 2695    // Single whitespaces are removed with the word behind them.
 2696    cx.assert_editor_state("here is ˇwith a space");
 2697    cx.update_editor(|editor, window, cx| {
 2698        editor.delete_to_previous_word_start(
 2699            &DeleteToPreviousWordStart {
 2700                ignore_newlines: true,
 2701                ignore_brackets: false,
 2702            },
 2703            window,
 2704            cx,
 2705        );
 2706    });
 2707    cx.assert_editor_state("here ˇwith a space");
 2708    cx.update_editor(|editor, window, cx| {
 2709        editor.delete_to_previous_word_start(
 2710            &DeleteToPreviousWordStart {
 2711                ignore_newlines: true,
 2712                ignore_brackets: false,
 2713            },
 2714            window,
 2715            cx,
 2716        );
 2717    });
 2718    cx.assert_editor_state("ˇwith a space");
 2719    cx.update_editor(|editor, window, cx| {
 2720        editor.delete_to_previous_word_start(
 2721            &DeleteToPreviousWordStart {
 2722                ignore_newlines: true,
 2723                ignore_brackets: false,
 2724            },
 2725            window,
 2726            cx,
 2727        );
 2728    });
 2729    cx.assert_editor_state("ˇwith a space");
 2730    cx.update_editor(|editor, window, cx| {
 2731        editor.delete_to_next_word_end(
 2732            &DeleteToNextWordEnd {
 2733                ignore_newlines: true,
 2734                ignore_brackets: false,
 2735            },
 2736            window,
 2737            cx,
 2738        );
 2739    });
 2740    // Same happens in the other direction.
 2741    cx.assert_editor_state("ˇ a space");
 2742    cx.update_editor(|editor, window, cx| {
 2743        editor.delete_to_next_word_end(
 2744            &DeleteToNextWordEnd {
 2745                ignore_newlines: true,
 2746                ignore_brackets: false,
 2747            },
 2748            window,
 2749            cx,
 2750        );
 2751    });
 2752    cx.assert_editor_state("ˇ space");
 2753    cx.update_editor(|editor, window, cx| {
 2754        editor.delete_to_next_word_end(
 2755            &DeleteToNextWordEnd {
 2756                ignore_newlines: true,
 2757                ignore_brackets: false,
 2758            },
 2759            window,
 2760            cx,
 2761        );
 2762    });
 2763    cx.assert_editor_state("ˇ");
 2764    cx.update_editor(|editor, window, cx| {
 2765        editor.delete_to_next_word_end(
 2766            &DeleteToNextWordEnd {
 2767                ignore_newlines: true,
 2768                ignore_brackets: false,
 2769            },
 2770            window,
 2771            cx,
 2772        );
 2773    });
 2774    cx.assert_editor_state("ˇ");
 2775    cx.update_editor(|editor, window, cx| {
 2776        editor.delete_to_previous_word_start(
 2777            &DeleteToPreviousWordStart {
 2778                ignore_newlines: true,
 2779                ignore_brackets: false,
 2780            },
 2781            window,
 2782            cx,
 2783        );
 2784    });
 2785    cx.assert_editor_state("ˇ");
 2786}
 2787
 2788#[gpui::test]
 2789async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2790    init_test(cx, |_| {});
 2791
 2792    let language = Arc::new(
 2793        Language::new(
 2794            LanguageConfig {
 2795                brackets: BracketPairConfig {
 2796                    pairs: vec![
 2797                        BracketPair {
 2798                            start: "\"".to_string(),
 2799                            end: "\"".to_string(),
 2800                            close: true,
 2801                            surround: true,
 2802                            newline: false,
 2803                        },
 2804                        BracketPair {
 2805                            start: "(".to_string(),
 2806                            end: ")".to_string(),
 2807                            close: true,
 2808                            surround: true,
 2809                            newline: true,
 2810                        },
 2811                    ],
 2812                    ..BracketPairConfig::default()
 2813                },
 2814                ..LanguageConfig::default()
 2815            },
 2816            Some(tree_sitter_rust::LANGUAGE.into()),
 2817        )
 2818        .with_brackets_query(
 2819            r#"
 2820                ("(" @open ")" @close)
 2821                ("\"" @open "\"" @close)
 2822            "#,
 2823        )
 2824        .unwrap(),
 2825    );
 2826
 2827    let mut cx = EditorTestContext::new(cx).await;
 2828    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2829
 2830    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2831    cx.update_editor(|editor, window, cx| {
 2832        editor.delete_to_previous_word_start(
 2833            &DeleteToPreviousWordStart {
 2834                ignore_newlines: true,
 2835                ignore_brackets: false,
 2836            },
 2837            window,
 2838            cx,
 2839        );
 2840    });
 2841    // Deletion stops before brackets if asked to not ignore them.
 2842    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2843    cx.update_editor(|editor, window, cx| {
 2844        editor.delete_to_previous_word_start(
 2845            &DeleteToPreviousWordStart {
 2846                ignore_newlines: true,
 2847                ignore_brackets: false,
 2848            },
 2849            window,
 2850            cx,
 2851        );
 2852    });
 2853    // Deletion has to remove a single bracket and then stop again.
 2854    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2855
 2856    cx.update_editor(|editor, window, cx| {
 2857        editor.delete_to_previous_word_start(
 2858            &DeleteToPreviousWordStart {
 2859                ignore_newlines: true,
 2860                ignore_brackets: false,
 2861            },
 2862            window,
 2863            cx,
 2864        );
 2865    });
 2866    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2867
 2868    cx.update_editor(|editor, window, cx| {
 2869        editor.delete_to_previous_word_start(
 2870            &DeleteToPreviousWordStart {
 2871                ignore_newlines: true,
 2872                ignore_brackets: false,
 2873            },
 2874            window,
 2875            cx,
 2876        );
 2877    });
 2878    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2879
 2880    cx.update_editor(|editor, window, cx| {
 2881        editor.delete_to_previous_word_start(
 2882            &DeleteToPreviousWordStart {
 2883                ignore_newlines: true,
 2884                ignore_brackets: false,
 2885            },
 2886            window,
 2887            cx,
 2888        );
 2889    });
 2890    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2891
 2892    cx.update_editor(|editor, window, cx| {
 2893        editor.delete_to_next_word_end(
 2894            &DeleteToNextWordEnd {
 2895                ignore_newlines: true,
 2896                ignore_brackets: false,
 2897            },
 2898            window,
 2899            cx,
 2900        );
 2901    });
 2902    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2903    cx.assert_editor_state(r#"ˇ");"#);
 2904
 2905    cx.update_editor(|editor, window, cx| {
 2906        editor.delete_to_next_word_end(
 2907            &DeleteToNextWordEnd {
 2908                ignore_newlines: true,
 2909                ignore_brackets: false,
 2910            },
 2911            window,
 2912            cx,
 2913        );
 2914    });
 2915    cx.assert_editor_state(r#"ˇ"#);
 2916
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_next_word_end(
 2919            &DeleteToNextWordEnd {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    cx.assert_editor_state(r#"ˇ"#);
 2928
 2929    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: true,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2941}
 2942
 2943#[gpui::test]
 2944fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2945    init_test(cx, |_| {});
 2946
 2947    let editor = cx.add_window(|window, cx| {
 2948        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2949        build_editor(buffer, window, cx)
 2950    });
 2951    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2952        ignore_newlines: false,
 2953        ignore_brackets: false,
 2954    };
 2955    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2956        ignore_newlines: true,
 2957        ignore_brackets: false,
 2958    };
 2959
 2960    _ = editor.update(cx, |editor, window, cx| {
 2961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2962            s.select_display_ranges([
 2963                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2964            ])
 2965        });
 2966        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2967        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2968        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2969        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2970        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2971        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2972        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2973        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2974        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2975        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2976        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2977        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2978    });
 2979}
 2980
 2981#[gpui::test]
 2982fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 2983    init_test(cx, |_| {});
 2984
 2985    let editor = cx.add_window(|window, cx| {
 2986        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 2987        build_editor(buffer, window, cx)
 2988    });
 2989    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 2990        ignore_newlines: false,
 2991        ignore_brackets: false,
 2992    };
 2993    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 2994        ignore_newlines: true,
 2995        ignore_brackets: false,
 2996    };
 2997
 2998    _ = editor.update(cx, |editor, window, cx| {
 2999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3000            s.select_display_ranges([
 3001                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3002            ])
 3003        });
 3004        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3005        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3006        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3007        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3008        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3009        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3010        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3011        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3012        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3013        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3014        editor.delete_to_previous_subword_start(
 3015            &del_to_prev_sub_word_start_ignore_newlines,
 3016            window,
 3017            cx,
 3018        );
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3020    });
 3021}
 3022
 3023#[gpui::test]
 3024fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3025    init_test(cx, |_| {});
 3026
 3027    let editor = cx.add_window(|window, cx| {
 3028        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3029        build_editor(buffer, window, cx)
 3030    });
 3031    let del_to_next_word_end = DeleteToNextWordEnd {
 3032        ignore_newlines: false,
 3033        ignore_brackets: false,
 3034    };
 3035    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3036        ignore_newlines: true,
 3037        ignore_brackets: false,
 3038    };
 3039
 3040    _ = editor.update(cx, |editor, window, cx| {
 3041        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3042            s.select_display_ranges([
 3043                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3044            ])
 3045        });
 3046        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3047        assert_eq!(
 3048            editor.buffer.read(cx).read(cx).text(),
 3049            "one\n   two\nthree\n   four"
 3050        );
 3051        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3052        assert_eq!(
 3053            editor.buffer.read(cx).read(cx).text(),
 3054            "\n   two\nthree\n   four"
 3055        );
 3056        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3057        assert_eq!(
 3058            editor.buffer.read(cx).read(cx).text(),
 3059            "two\nthree\n   four"
 3060        );
 3061        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3062        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3063        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3064        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3065        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3066        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3067        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3068        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3069    });
 3070}
 3071
 3072#[gpui::test]
 3073fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3074    init_test(cx, |_| {});
 3075
 3076    let editor = cx.add_window(|window, cx| {
 3077        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3078        build_editor(buffer, window, cx)
 3079    });
 3080    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3081        ignore_newlines: false,
 3082        ignore_brackets: false,
 3083    };
 3084    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3085        ignore_newlines: true,
 3086        ignore_brackets: false,
 3087    };
 3088
 3089    _ = editor.update(cx, |editor, window, cx| {
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_display_ranges([
 3092                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3093            ])
 3094        });
 3095        // Delete "\n" (empty line)
 3096        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3097        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3098        // Delete "foo" (subword boundary)
 3099        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3100        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3101        // Delete "Bar"
 3102        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3103        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3104        // Delete "\n   " (newline + leading whitespace)
 3105        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3106        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3107        // Delete "baz" (subword boundary)
 3108        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3109        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3110        // With ignore_newlines, delete "Qux"
 3111        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3113    });
 3114}
 3115
 3116#[gpui::test]
 3117fn test_newline(cx: &mut TestAppContext) {
 3118    init_test(cx, |_| {});
 3119
 3120    let editor = cx.add_window(|window, cx| {
 3121        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3122        build_editor(buffer, window, cx)
 3123    });
 3124
 3125    _ = editor.update(cx, |editor, window, cx| {
 3126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3127            s.select_display_ranges([
 3128                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3129                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3130                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3131            ])
 3132        });
 3133
 3134        editor.newline(&Newline, window, cx);
 3135        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3136    });
 3137}
 3138
 3139#[gpui::test]
 3140async fn test_newline_yaml(cx: &mut TestAppContext) {
 3141    init_test(cx, |_| {});
 3142
 3143    let mut cx = EditorTestContext::new(cx).await;
 3144    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3145    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3146
 3147    // Object (between 2 fields)
 3148    cx.set_state(indoc! {"
 3149    test:ˇ
 3150    hello: bye"});
 3151    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3152    cx.assert_editor_state(indoc! {"
 3153    test:
 3154        ˇ
 3155    hello: bye"});
 3156
 3157    // Object (first and single line)
 3158    cx.set_state(indoc! {"
 3159    test:ˇ"});
 3160    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3161    cx.assert_editor_state(indoc! {"
 3162    test:
 3163        ˇ"});
 3164
 3165    // Array with objects (after first element)
 3166    cx.set_state(indoc! {"
 3167    test:
 3168        - foo: barˇ"});
 3169    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3170    cx.assert_editor_state(indoc! {"
 3171    test:
 3172        - foo: bar
 3173        ˇ"});
 3174
 3175    // Array with objects and comment
 3176    cx.set_state(indoc! {"
 3177    test:
 3178        - foo: bar
 3179        - bar: # testˇ"});
 3180    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3181    cx.assert_editor_state(indoc! {"
 3182    test:
 3183        - foo: bar
 3184        - bar: # test
 3185            ˇ"});
 3186
 3187    // Array with objects (after second element)
 3188    cx.set_state(indoc! {"
 3189    test:
 3190        - foo: bar
 3191        - bar: fooˇ"});
 3192    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3193    cx.assert_editor_state(indoc! {"
 3194    test:
 3195        - foo: bar
 3196        - bar: foo
 3197        ˇ"});
 3198
 3199    // Array with strings (after first element)
 3200    cx.set_state(indoc! {"
 3201    test:
 3202        - fooˇ"});
 3203    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3204    cx.assert_editor_state(indoc! {"
 3205    test:
 3206        - foo
 3207        ˇ"});
 3208}
 3209
 3210#[gpui::test]
 3211fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3212    init_test(cx, |_| {});
 3213
 3214    let editor = cx.add_window(|window, cx| {
 3215        let buffer = MultiBuffer::build_simple(
 3216            "
 3217                a
 3218                b(
 3219                    X
 3220                )
 3221                c(
 3222                    X
 3223                )
 3224            "
 3225            .unindent()
 3226            .as_str(),
 3227            cx,
 3228        );
 3229        let mut editor = build_editor(buffer, window, cx);
 3230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3231            s.select_ranges([
 3232                Point::new(2, 4)..Point::new(2, 5),
 3233                Point::new(5, 4)..Point::new(5, 5),
 3234            ])
 3235        });
 3236        editor
 3237    });
 3238
 3239    _ = editor.update(cx, |editor, window, cx| {
 3240        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3241        editor.buffer.update(cx, |buffer, cx| {
 3242            buffer.edit(
 3243                [
 3244                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3245                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3246                ],
 3247                None,
 3248                cx,
 3249            );
 3250            assert_eq!(
 3251                buffer.read(cx).text(),
 3252                "
 3253                    a
 3254                    b()
 3255                    c()
 3256                "
 3257                .unindent()
 3258            );
 3259        });
 3260        assert_eq!(
 3261            editor.selections.ranges(&editor.display_snapshot(cx)),
 3262            &[
 3263                Point::new(1, 2)..Point::new(1, 2),
 3264                Point::new(2, 2)..Point::new(2, 2),
 3265            ],
 3266        );
 3267
 3268        editor.newline(&Newline, window, cx);
 3269        assert_eq!(
 3270            editor.text(cx),
 3271            "
 3272                a
 3273                b(
 3274                )
 3275                c(
 3276                )
 3277            "
 3278            .unindent()
 3279        );
 3280
 3281        // The selections are moved after the inserted newlines
 3282        assert_eq!(
 3283            editor.selections.ranges(&editor.display_snapshot(cx)),
 3284            &[
 3285                Point::new(2, 0)..Point::new(2, 0),
 3286                Point::new(4, 0)..Point::new(4, 0),
 3287            ],
 3288        );
 3289    });
 3290}
 3291
 3292#[gpui::test]
 3293async fn test_newline_above(cx: &mut TestAppContext) {
 3294    init_test(cx, |settings| {
 3295        settings.defaults.tab_size = NonZeroU32::new(4)
 3296    });
 3297
 3298    let language = Arc::new(
 3299        Language::new(
 3300            LanguageConfig::default(),
 3301            Some(tree_sitter_rust::LANGUAGE.into()),
 3302        )
 3303        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3304        .unwrap(),
 3305    );
 3306
 3307    let mut cx = EditorTestContext::new(cx).await;
 3308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3309    cx.set_state(indoc! {"
 3310        const a: ˇA = (
 3311 3312                «const_functionˇ»(ˇ),
 3313                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3314 3315        ˇ);ˇ
 3316    "});
 3317
 3318    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        ˇ
 3321        const a: A = (
 3322            ˇ
 3323            (
 3324                ˇ
 3325                ˇ
 3326                const_function(),
 3327                ˇ
 3328                ˇ
 3329                ˇ
 3330                ˇ
 3331                something_else,
 3332                ˇ
 3333            )
 3334            ˇ
 3335            ˇ
 3336        );
 3337    "});
 3338}
 3339
 3340#[gpui::test]
 3341async fn test_newline_below(cx: &mut TestAppContext) {
 3342    init_test(cx, |settings| {
 3343        settings.defaults.tab_size = NonZeroU32::new(4)
 3344    });
 3345
 3346    let language = Arc::new(
 3347        Language::new(
 3348            LanguageConfig::default(),
 3349            Some(tree_sitter_rust::LANGUAGE.into()),
 3350        )
 3351        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3352        .unwrap(),
 3353    );
 3354
 3355    let mut cx = EditorTestContext::new(cx).await;
 3356    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3357    cx.set_state(indoc! {"
 3358        const a: ˇA = (
 3359 3360                «const_functionˇ»(ˇ),
 3361                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3362 3363        ˇ);ˇ
 3364    "});
 3365
 3366    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: A = (
 3369            ˇ
 3370            (
 3371                ˇ
 3372                const_function(),
 3373                ˇ
 3374                ˇ
 3375                something_else,
 3376                ˇ
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380            )
 3381            ˇ
 3382        );
 3383        ˇ
 3384        ˇ
 3385    "});
 3386}
 3387
 3388#[gpui::test]
 3389fn test_newline_below_multibuffer(cx: &mut TestAppContext) {
 3390    init_test(cx, |_| {});
 3391
 3392    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3393    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3394    let multibuffer = cx.new(|cx| {
 3395        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3396        multibuffer.push_excerpts(
 3397            buffer_1.clone(),
 3398            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3399            cx,
 3400        );
 3401        multibuffer.push_excerpts(
 3402            buffer_2.clone(),
 3403            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3404            cx,
 3405        );
 3406        multibuffer
 3407    });
 3408
 3409    cx.add_window(|window, cx| {
 3410        let mut editor = build_editor(multibuffer, window, cx);
 3411
 3412        assert_eq!(
 3413            editor.text(cx),
 3414            indoc! {"
 3415                aaa
 3416                bbb
 3417                ccc
 3418                ddd
 3419                eee
 3420                fff"}
 3421        );
 3422
 3423        // Cursor on the last line of the first excerpt.
 3424        // The newline should be inserted within the first excerpt (buffer_1),
 3425        // not in the second excerpt (buffer_2).
 3426        select_ranges(
 3427            &mut editor,
 3428            indoc! {"
 3429                aaa
 3430                bbb
 3431                cˇcc
 3432                ddd
 3433                eee
 3434                fff"},
 3435            window,
 3436            cx,
 3437        );
 3438        editor.newline_below(&NewlineBelow, window, cx);
 3439        assert_text_with_selections(
 3440            &mut editor,
 3441            indoc! {"
 3442                aaa
 3443                bbb
 3444                ccc
 3445                ˇ
 3446                ddd
 3447                eee
 3448                fff"},
 3449            cx,
 3450        );
 3451        buffer_1.read_with(cx, |buffer, _| {
 3452            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 3453        });
 3454        buffer_2.read_with(cx, |buffer, _| {
 3455            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3456        });
 3457
 3458        editor
 3459    });
 3460}
 3461
 3462#[gpui::test]
 3463fn test_newline_below_multibuffer_middle_of_excerpt(cx: &mut TestAppContext) {
 3464    init_test(cx, |_| {});
 3465
 3466    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3467    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3468    let multibuffer = cx.new(|cx| {
 3469        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3470        multibuffer.push_excerpts(
 3471            buffer_1.clone(),
 3472            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3473            cx,
 3474        );
 3475        multibuffer.push_excerpts(
 3476            buffer_2.clone(),
 3477            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3478            cx,
 3479        );
 3480        multibuffer
 3481    });
 3482
 3483    cx.add_window(|window, cx| {
 3484        let mut editor = build_editor(multibuffer, window, cx);
 3485
 3486        // Cursor in the middle of the first excerpt.
 3487        select_ranges(
 3488            &mut editor,
 3489            indoc! {"
 3490                aˇaa
 3491                bbb
 3492                ccc
 3493                ddd
 3494                eee
 3495                fff"},
 3496            window,
 3497            cx,
 3498        );
 3499        editor.newline_below(&NewlineBelow, window, cx);
 3500        assert_text_with_selections(
 3501            &mut editor,
 3502            indoc! {"
 3503                aaa
 3504                ˇ
 3505                bbb
 3506                ccc
 3507                ddd
 3508                eee
 3509                fff"},
 3510            cx,
 3511        );
 3512        buffer_1.read_with(cx, |buffer, _| {
 3513            assert_eq!(buffer.text(), "aaa\n\nbbb\nccc");
 3514        });
 3515        buffer_2.read_with(cx, |buffer, _| {
 3516            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3517        });
 3518
 3519        editor
 3520    });
 3521}
 3522
 3523#[gpui::test]
 3524fn test_newline_below_multibuffer_last_line_of_last_excerpt(cx: &mut TestAppContext) {
 3525    init_test(cx, |_| {});
 3526
 3527    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3528    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3529    let multibuffer = cx.new(|cx| {
 3530        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3531        multibuffer.push_excerpts(
 3532            buffer_1.clone(),
 3533            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3534            cx,
 3535        );
 3536        multibuffer.push_excerpts(
 3537            buffer_2.clone(),
 3538            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3539            cx,
 3540        );
 3541        multibuffer
 3542    });
 3543
 3544    cx.add_window(|window, cx| {
 3545        let mut editor = build_editor(multibuffer, window, cx);
 3546
 3547        // Cursor on the last line of the last excerpt.
 3548        select_ranges(
 3549            &mut editor,
 3550            indoc! {"
 3551                aaa
 3552                bbb
 3553                ccc
 3554                ddd
 3555                eee
 3556                fˇff"},
 3557            window,
 3558            cx,
 3559        );
 3560        editor.newline_below(&NewlineBelow, window, cx);
 3561        assert_text_with_selections(
 3562            &mut editor,
 3563            indoc! {"
 3564                aaa
 3565                bbb
 3566                ccc
 3567                ddd
 3568                eee
 3569                fff
 3570                ˇ"},
 3571            cx,
 3572        );
 3573        buffer_1.read_with(cx, |buffer, _| {
 3574            assert_eq!(buffer.text(), "aaa\nbbb\nccc");
 3575        });
 3576        buffer_2.read_with(cx, |buffer, _| {
 3577            assert_eq!(buffer.text(), "ddd\neee\nfff\n");
 3578        });
 3579
 3580        editor
 3581    });
 3582}
 3583
 3584#[gpui::test]
 3585fn test_newline_below_multibuffer_multiple_cursors(cx: &mut TestAppContext) {
 3586    init_test(cx, |_| {});
 3587
 3588    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3589    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3590    let multibuffer = cx.new(|cx| {
 3591        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3592        multibuffer.push_excerpts(
 3593            buffer_1.clone(),
 3594            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3595            cx,
 3596        );
 3597        multibuffer.push_excerpts(
 3598            buffer_2.clone(),
 3599            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 3))],
 3600            cx,
 3601        );
 3602        multibuffer
 3603    });
 3604
 3605    cx.add_window(|window, cx| {
 3606        let mut editor = build_editor(multibuffer, window, cx);
 3607
 3608        // Cursors on the last line of the first excerpt and the first line
 3609        // of the second excerpt. Each newline should go into its respective buffer.
 3610        select_ranges(
 3611            &mut editor,
 3612            indoc! {"
 3613                aaa
 3614                bbb
 3615                cˇcc
 3616                dˇdd
 3617                eee
 3618                fff"},
 3619            window,
 3620            cx,
 3621        );
 3622        editor.newline_below(&NewlineBelow, window, cx);
 3623        assert_text_with_selections(
 3624            &mut editor,
 3625            indoc! {"
 3626                aaa
 3627                bbb
 3628                ccc
 3629                ˇ
 3630                ddd
 3631                ˇ
 3632                eee
 3633                fff"},
 3634            cx,
 3635        );
 3636        buffer_1.read_with(cx, |buffer, _| {
 3637            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 3638        });
 3639        buffer_2.read_with(cx, |buffer, _| {
 3640            assert_eq!(buffer.text(), "ddd\n\neee\nfff");
 3641        });
 3642
 3643        editor
 3644    });
 3645}
 3646
 3647#[gpui::test]
 3648async fn test_newline_comments(cx: &mut TestAppContext) {
 3649    init_test(cx, |settings| {
 3650        settings.defaults.tab_size = NonZeroU32::new(4)
 3651    });
 3652
 3653    let language = Arc::new(Language::new(
 3654        LanguageConfig {
 3655            line_comments: vec!["// ".into()],
 3656            ..LanguageConfig::default()
 3657        },
 3658        None,
 3659    ));
 3660    {
 3661        let mut cx = EditorTestContext::new(cx).await;
 3662        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3663        cx.set_state(indoc! {"
 3664        // Fooˇ
 3665    "});
 3666
 3667        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3668        cx.assert_editor_state(indoc! {"
 3669        // Foo
 3670        // ˇ
 3671    "});
 3672        // Ensure that we add comment prefix when existing line contains space
 3673        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3674        cx.assert_editor_state(
 3675            indoc! {"
 3676        // Foo
 3677        //s
 3678        // ˇ
 3679    "}
 3680            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3681            .as_str(),
 3682        );
 3683        // Ensure that we add comment prefix when existing line does not contain space
 3684        cx.set_state(indoc! {"
 3685        // Foo
 3686        //ˇ
 3687    "});
 3688        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3689        cx.assert_editor_state(indoc! {"
 3690        // Foo
 3691        //
 3692        // ˇ
 3693    "});
 3694        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3695        cx.set_state(indoc! {"
 3696        ˇ// Foo
 3697    "});
 3698        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3699        cx.assert_editor_state(indoc! {"
 3700
 3701        ˇ// Foo
 3702    "});
 3703    }
 3704    // Ensure that comment continuations can be disabled.
 3705    update_test_language_settings(cx, |settings| {
 3706        settings.defaults.extend_comment_on_newline = Some(false);
 3707    });
 3708    let mut cx = EditorTestContext::new(cx).await;
 3709    cx.set_state(indoc! {"
 3710        // Fooˇ
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        // Foo
 3715        ˇ
 3716    "});
 3717}
 3718
 3719#[gpui::test]
 3720async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3721    init_test(cx, |settings| {
 3722        settings.defaults.tab_size = NonZeroU32::new(4)
 3723    });
 3724
 3725    let language = Arc::new(Language::new(
 3726        LanguageConfig {
 3727            line_comments: vec!["// ".into(), "/// ".into()],
 3728            ..LanguageConfig::default()
 3729        },
 3730        None,
 3731    ));
 3732    {
 3733        let mut cx = EditorTestContext::new(cx).await;
 3734        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3735        cx.set_state(indoc! {"
 3736        //ˇ
 3737    "});
 3738        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3739        cx.assert_editor_state(indoc! {"
 3740        //
 3741        // ˇ
 3742    "});
 3743
 3744        cx.set_state(indoc! {"
 3745        ///ˇ
 3746    "});
 3747        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3748        cx.assert_editor_state(indoc! {"
 3749        ///
 3750        /// ˇ
 3751    "});
 3752    }
 3753}
 3754
 3755#[gpui::test]
 3756async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3757    init_test(cx, |settings| {
 3758        settings.defaults.tab_size = NonZeroU32::new(4)
 3759    });
 3760
 3761    let language = Arc::new(
 3762        Language::new(
 3763            LanguageConfig {
 3764                documentation_comment: Some(language::BlockCommentConfig {
 3765                    start: "/**".into(),
 3766                    end: "*/".into(),
 3767                    prefix: "* ".into(),
 3768                    tab_size: 1,
 3769                }),
 3770
 3771                ..LanguageConfig::default()
 3772            },
 3773            Some(tree_sitter_rust::LANGUAGE.into()),
 3774        )
 3775        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3776        .unwrap(),
 3777    );
 3778
 3779    {
 3780        let mut cx = EditorTestContext::new(cx).await;
 3781        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3782        cx.set_state(indoc! {"
 3783        /**ˇ
 3784    "});
 3785
 3786        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3787        cx.assert_editor_state(indoc! {"
 3788        /**
 3789         * ˇ
 3790    "});
 3791        // Ensure that if cursor is before the comment start,
 3792        // we do not actually insert a comment prefix.
 3793        cx.set_state(indoc! {"
 3794        ˇ/**
 3795    "});
 3796        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3797        cx.assert_editor_state(indoc! {"
 3798
 3799        ˇ/**
 3800    "});
 3801        // Ensure that if cursor is between it doesn't add comment prefix.
 3802        cx.set_state(indoc! {"
 3803        /*ˇ*
 3804    "});
 3805        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3806        cx.assert_editor_state(indoc! {"
 3807        /*
 3808        ˇ*
 3809    "});
 3810        // Ensure that if suffix exists on same line after cursor it adds new line.
 3811        cx.set_state(indoc! {"
 3812        /**ˇ*/
 3813    "});
 3814        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3815        cx.assert_editor_state(indoc! {"
 3816        /**
 3817         * ˇ
 3818         */
 3819    "});
 3820        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3821        cx.set_state(indoc! {"
 3822        /**ˇ */
 3823    "});
 3824        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3825        cx.assert_editor_state(indoc! {"
 3826        /**
 3827         * ˇ
 3828         */
 3829    "});
 3830        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3831        cx.set_state(indoc! {"
 3832        /** ˇ*/
 3833    "});
 3834        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3835        cx.assert_editor_state(
 3836            indoc! {"
 3837        /**s
 3838         * ˇ
 3839         */
 3840    "}
 3841            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3842            .as_str(),
 3843        );
 3844        // Ensure that delimiter space is preserved when newline on already
 3845        // spaced delimiter.
 3846        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3847        cx.assert_editor_state(
 3848            indoc! {"
 3849        /**s
 3850         *s
 3851         * ˇ
 3852         */
 3853    "}
 3854            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3855            .as_str(),
 3856        );
 3857        // Ensure that delimiter space is preserved when space is not
 3858        // on existing delimiter.
 3859        cx.set_state(indoc! {"
 3860        /**
 3861 3862         */
 3863    "});
 3864        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3865        cx.assert_editor_state(indoc! {"
 3866        /**
 3867         *
 3868         * ˇ
 3869         */
 3870    "});
 3871        // Ensure that if suffix exists on same line after cursor it
 3872        // doesn't add extra new line if prefix is not on same line.
 3873        cx.set_state(indoc! {"
 3874        /**
 3875        ˇ*/
 3876    "});
 3877        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3878        cx.assert_editor_state(indoc! {"
 3879        /**
 3880
 3881        ˇ*/
 3882    "});
 3883        // Ensure that it detects suffix after existing prefix.
 3884        cx.set_state(indoc! {"
 3885        /**ˇ/
 3886    "});
 3887        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3888        cx.assert_editor_state(indoc! {"
 3889        /**
 3890        ˇ/
 3891    "});
 3892        // Ensure that if suffix exists on same line before
 3893        // cursor it does not add comment prefix.
 3894        cx.set_state(indoc! {"
 3895        /** */ˇ
 3896    "});
 3897        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3898        cx.assert_editor_state(indoc! {"
 3899        /** */
 3900        ˇ
 3901    "});
 3902        // Ensure that if suffix exists on same line before
 3903        // cursor it does not add comment prefix.
 3904        cx.set_state(indoc! {"
 3905        /**
 3906         *
 3907         */ˇ
 3908    "});
 3909        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3910        cx.assert_editor_state(indoc! {"
 3911        /**
 3912         *
 3913         */
 3914         ˇ
 3915    "});
 3916
 3917        // Ensure that inline comment followed by code
 3918        // doesn't add comment prefix on newline
 3919        cx.set_state(indoc! {"
 3920        /** */ textˇ
 3921    "});
 3922        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3923        cx.assert_editor_state(indoc! {"
 3924        /** */ text
 3925        ˇ
 3926    "});
 3927
 3928        // Ensure that text after comment end tag
 3929        // doesn't add comment prefix on newline
 3930        cx.set_state(indoc! {"
 3931        /**
 3932         *
 3933         */ˇtext
 3934    "});
 3935        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3936        cx.assert_editor_state(indoc! {"
 3937        /**
 3938         *
 3939         */
 3940         ˇtext
 3941    "});
 3942
 3943        // Ensure if not comment block it doesn't
 3944        // add comment prefix on newline
 3945        cx.set_state(indoc! {"
 3946        * textˇ
 3947    "});
 3948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3949        cx.assert_editor_state(indoc! {"
 3950        * text
 3951        ˇ
 3952    "});
 3953    }
 3954    // Ensure that comment continuations can be disabled.
 3955    update_test_language_settings(cx, |settings| {
 3956        settings.defaults.extend_comment_on_newline = Some(false);
 3957    });
 3958    let mut cx = EditorTestContext::new(cx).await;
 3959    cx.set_state(indoc! {"
 3960        /**ˇ
 3961    "});
 3962    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3963    cx.assert_editor_state(indoc! {"
 3964        /**
 3965        ˇ
 3966    "});
 3967}
 3968
 3969#[gpui::test]
 3970async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3971    init_test(cx, |settings| {
 3972        settings.defaults.tab_size = NonZeroU32::new(4)
 3973    });
 3974
 3975    let lua_language = Arc::new(Language::new(
 3976        LanguageConfig {
 3977            line_comments: vec!["--".into()],
 3978            block_comment: Some(language::BlockCommentConfig {
 3979                start: "--[[".into(),
 3980                prefix: "".into(),
 3981                end: "]]".into(),
 3982                tab_size: 0,
 3983            }),
 3984            ..LanguageConfig::default()
 3985        },
 3986        None,
 3987    ));
 3988
 3989    let mut cx = EditorTestContext::new(cx).await;
 3990    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3991
 3992    // Line with line comment should extend
 3993    cx.set_state(indoc! {"
 3994        --ˇ
 3995    "});
 3996    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3997    cx.assert_editor_state(indoc! {"
 3998        --
 3999        --ˇ
 4000    "});
 4001
 4002    // Line with block comment that matches line comment should not extend
 4003    cx.set_state(indoc! {"
 4004        --[[ˇ
 4005    "});
 4006    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4007    cx.assert_editor_state(indoc! {"
 4008        --[[
 4009        ˇ
 4010    "});
 4011}
 4012
 4013#[gpui::test]
 4014fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 4015    init_test(cx, |_| {});
 4016
 4017    let editor = cx.add_window(|window, cx| {
 4018        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 4019        let mut editor = build_editor(buffer, window, cx);
 4020        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4021            s.select_ranges([
 4022                MultiBufferOffset(3)..MultiBufferOffset(4),
 4023                MultiBufferOffset(11)..MultiBufferOffset(12),
 4024                MultiBufferOffset(19)..MultiBufferOffset(20),
 4025            ])
 4026        });
 4027        editor
 4028    });
 4029
 4030    _ = editor.update(cx, |editor, window, cx| {
 4031        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 4032        editor.buffer.update(cx, |buffer, cx| {
 4033            buffer.edit(
 4034                [
 4035                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 4036                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 4037                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 4038                ],
 4039                None,
 4040                cx,
 4041            );
 4042            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 4043        });
 4044        assert_eq!(
 4045            editor.selections.ranges(&editor.display_snapshot(cx)),
 4046            &[
 4047                MultiBufferOffset(2)..MultiBufferOffset(2),
 4048                MultiBufferOffset(7)..MultiBufferOffset(7),
 4049                MultiBufferOffset(12)..MultiBufferOffset(12)
 4050            ],
 4051        );
 4052
 4053        editor.insert("Z", window, cx);
 4054        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 4055
 4056        // The selections are moved after the inserted characters
 4057        assert_eq!(
 4058            editor.selections.ranges(&editor.display_snapshot(cx)),
 4059            &[
 4060                MultiBufferOffset(3)..MultiBufferOffset(3),
 4061                MultiBufferOffset(9)..MultiBufferOffset(9),
 4062                MultiBufferOffset(15)..MultiBufferOffset(15)
 4063            ],
 4064        );
 4065    });
 4066}
 4067
 4068#[gpui::test]
 4069async fn test_tab(cx: &mut TestAppContext) {
 4070    init_test(cx, |settings| {
 4071        settings.defaults.tab_size = NonZeroU32::new(3)
 4072    });
 4073
 4074    let mut cx = EditorTestContext::new(cx).await;
 4075    cx.set_state(indoc! {"
 4076        ˇabˇc
 4077        ˇ🏀ˇ🏀ˇefg
 4078 4079    "});
 4080    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082           ˇab ˇc
 4083           ˇ🏀  ˇ🏀  ˇefg
 4084        d  ˇ
 4085    "});
 4086
 4087    cx.set_state(indoc! {"
 4088        a
 4089        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4090    "});
 4091    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4092    cx.assert_editor_state(indoc! {"
 4093        a
 4094           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4095    "});
 4096}
 4097
 4098#[gpui::test]
 4099async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 4100    init_test(cx, |_| {});
 4101
 4102    let mut cx = EditorTestContext::new(cx).await;
 4103    let language = Arc::new(
 4104        Language::new(
 4105            LanguageConfig::default(),
 4106            Some(tree_sitter_rust::LANGUAGE.into()),
 4107        )
 4108        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 4109        .unwrap(),
 4110    );
 4111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4112
 4113    // test when all cursors are not at suggested indent
 4114    // then simply move to their suggested indent location
 4115    cx.set_state(indoc! {"
 4116        const a: B = (
 4117            c(
 4118        ˇ
 4119        ˇ    )
 4120        );
 4121    "});
 4122    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4123    cx.assert_editor_state(indoc! {"
 4124        const a: B = (
 4125            c(
 4126                ˇ
 4127            ˇ)
 4128        );
 4129    "});
 4130
 4131    // test cursor already at suggested indent not moving when
 4132    // other cursors are yet to reach their suggested indents
 4133    cx.set_state(indoc! {"
 4134        ˇ
 4135        const a: B = (
 4136            c(
 4137                d(
 4138        ˇ
 4139                )
 4140        ˇ
 4141        ˇ    )
 4142        );
 4143    "});
 4144    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4145    cx.assert_editor_state(indoc! {"
 4146        ˇ
 4147        const a: B = (
 4148            c(
 4149                d(
 4150                    ˇ
 4151                )
 4152                ˇ
 4153            ˇ)
 4154        );
 4155    "});
 4156    // test when all cursors are at suggested indent then tab is inserted
 4157    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4158    cx.assert_editor_state(indoc! {"
 4159            ˇ
 4160        const a: B = (
 4161            c(
 4162                d(
 4163                        ˇ
 4164                )
 4165                    ˇ
 4166                ˇ)
 4167        );
 4168    "});
 4169
 4170    // test when current indent is less than suggested indent,
 4171    // we adjust line to match suggested indent and move cursor to it
 4172    //
 4173    // when no other cursor is at word boundary, all of them should move
 4174    cx.set_state(indoc! {"
 4175        const a: B = (
 4176            c(
 4177                d(
 4178        ˇ
 4179        ˇ   )
 4180        ˇ   )
 4181        );
 4182    "});
 4183    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4184    cx.assert_editor_state(indoc! {"
 4185        const a: B = (
 4186            c(
 4187                d(
 4188                    ˇ
 4189                ˇ)
 4190            ˇ)
 4191        );
 4192    "});
 4193
 4194    // test when current indent is less than suggested indent,
 4195    // we adjust line to match suggested indent and move cursor to it
 4196    //
 4197    // when some other cursor is at word boundary, it should not move
 4198    cx.set_state(indoc! {"
 4199        const a: B = (
 4200            c(
 4201                d(
 4202        ˇ
 4203        ˇ   )
 4204           ˇ)
 4205        );
 4206    "});
 4207    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4208    cx.assert_editor_state(indoc! {"
 4209        const a: B = (
 4210            c(
 4211                d(
 4212                    ˇ
 4213                ˇ)
 4214            ˇ)
 4215        );
 4216    "});
 4217
 4218    // test when current indent is more than suggested indent,
 4219    // we just move cursor to current indent instead of suggested indent
 4220    //
 4221    // when no other cursor is at word boundary, all of them should move
 4222    cx.set_state(indoc! {"
 4223        const a: B = (
 4224            c(
 4225                d(
 4226        ˇ
 4227        ˇ                )
 4228        ˇ   )
 4229        );
 4230    "});
 4231    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4232    cx.assert_editor_state(indoc! {"
 4233        const a: B = (
 4234            c(
 4235                d(
 4236                    ˇ
 4237                        ˇ)
 4238            ˇ)
 4239        );
 4240    "});
 4241    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4242    cx.assert_editor_state(indoc! {"
 4243        const a: B = (
 4244            c(
 4245                d(
 4246                        ˇ
 4247                            ˇ)
 4248                ˇ)
 4249        );
 4250    "});
 4251
 4252    // test when current indent is more than suggested indent,
 4253    // we just move cursor to current indent instead of suggested indent
 4254    //
 4255    // when some other cursor is at word boundary, it doesn't move
 4256    cx.set_state(indoc! {"
 4257        const a: B = (
 4258            c(
 4259                d(
 4260        ˇ
 4261        ˇ                )
 4262            ˇ)
 4263        );
 4264    "});
 4265    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4266    cx.assert_editor_state(indoc! {"
 4267        const a: B = (
 4268            c(
 4269                d(
 4270                    ˇ
 4271                        ˇ)
 4272            ˇ)
 4273        );
 4274    "});
 4275
 4276    // handle auto-indent when there are multiple cursors on the same line
 4277    cx.set_state(indoc! {"
 4278        const a: B = (
 4279            c(
 4280        ˇ    ˇ
 4281        ˇ    )
 4282        );
 4283    "});
 4284    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4285    cx.assert_editor_state(indoc! {"
 4286        const a: B = (
 4287            c(
 4288                ˇ
 4289            ˇ)
 4290        );
 4291    "});
 4292}
 4293
 4294#[gpui::test]
 4295async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4296    init_test(cx, |settings| {
 4297        settings.defaults.tab_size = NonZeroU32::new(3)
 4298    });
 4299
 4300    let mut cx = EditorTestContext::new(cx).await;
 4301    cx.set_state(indoc! {"
 4302         ˇ
 4303        \t ˇ
 4304        \t  ˇ
 4305        \t   ˇ
 4306         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4307    "});
 4308
 4309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4310    cx.assert_editor_state(indoc! {"
 4311           ˇ
 4312        \t   ˇ
 4313        \t   ˇ
 4314        \t      ˇ
 4315         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4316    "});
 4317}
 4318
 4319#[gpui::test]
 4320async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4321    init_test(cx, |settings| {
 4322        settings.defaults.tab_size = NonZeroU32::new(4)
 4323    });
 4324
 4325    let language = Arc::new(
 4326        Language::new(
 4327            LanguageConfig::default(),
 4328            Some(tree_sitter_rust::LANGUAGE.into()),
 4329        )
 4330        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4331        .unwrap(),
 4332    );
 4333
 4334    let mut cx = EditorTestContext::new(cx).await;
 4335    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4336    cx.set_state(indoc! {"
 4337        fn a() {
 4338            if b {
 4339        \t ˇc
 4340            }
 4341        }
 4342    "});
 4343
 4344    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4345    cx.assert_editor_state(indoc! {"
 4346        fn a() {
 4347            if b {
 4348                ˇc
 4349            }
 4350        }
 4351    "});
 4352}
 4353
 4354#[gpui::test]
 4355async fn test_indent_outdent(cx: &mut TestAppContext) {
 4356    init_test(cx, |settings| {
 4357        settings.defaults.tab_size = NonZeroU32::new(4);
 4358    });
 4359
 4360    let mut cx = EditorTestContext::new(cx).await;
 4361
 4362    cx.set_state(indoc! {"
 4363          «oneˇ» «twoˇ»
 4364        three
 4365         four
 4366    "});
 4367    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4368    cx.assert_editor_state(indoc! {"
 4369            «oneˇ» «twoˇ»
 4370        three
 4371         four
 4372    "});
 4373
 4374    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4375    cx.assert_editor_state(indoc! {"
 4376        «oneˇ» «twoˇ»
 4377        three
 4378         four
 4379    "});
 4380
 4381    // select across line ending
 4382    cx.set_state(indoc! {"
 4383        one two
 4384        t«hree
 4385        ˇ» four
 4386    "});
 4387    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4388    cx.assert_editor_state(indoc! {"
 4389        one two
 4390            t«hree
 4391        ˇ» four
 4392    "});
 4393
 4394    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4395    cx.assert_editor_state(indoc! {"
 4396        one two
 4397        t«hree
 4398        ˇ» four
 4399    "});
 4400
 4401    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4402    cx.set_state(indoc! {"
 4403        one two
 4404        ˇthree
 4405            four
 4406    "});
 4407    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4408    cx.assert_editor_state(indoc! {"
 4409        one two
 4410            ˇthree
 4411            four
 4412    "});
 4413
 4414    cx.set_state(indoc! {"
 4415        one two
 4416        ˇ    three
 4417            four
 4418    "});
 4419    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4420    cx.assert_editor_state(indoc! {"
 4421        one two
 4422        ˇthree
 4423            four
 4424    "});
 4425}
 4426
 4427#[gpui::test]
 4428async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4429    // This is a regression test for issue #33761
 4430    init_test(cx, |_| {});
 4431
 4432    let mut cx = EditorTestContext::new(cx).await;
 4433    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4434    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4435
 4436    cx.set_state(
 4437        r#"ˇ#     ingress:
 4438ˇ#         api:
 4439ˇ#             enabled: false
 4440ˇ#             pathType: Prefix
 4441ˇ#           console:
 4442ˇ#               enabled: false
 4443ˇ#               pathType: Prefix
 4444"#,
 4445    );
 4446
 4447    // Press tab to indent all lines
 4448    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4449
 4450    cx.assert_editor_state(
 4451        r#"    ˇ#     ingress:
 4452    ˇ#         api:
 4453    ˇ#             enabled: false
 4454    ˇ#             pathType: Prefix
 4455    ˇ#           console:
 4456    ˇ#               enabled: false
 4457    ˇ#               pathType: Prefix
 4458"#,
 4459    );
 4460}
 4461
 4462#[gpui::test]
 4463async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4464    // This is a test to make sure our fix for issue #33761 didn't break anything
 4465    init_test(cx, |_| {});
 4466
 4467    let mut cx = EditorTestContext::new(cx).await;
 4468    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4469    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4470
 4471    cx.set_state(
 4472        r#"ˇingress:
 4473ˇ  api:
 4474ˇ    enabled: false
 4475ˇ    pathType: Prefix
 4476"#,
 4477    );
 4478
 4479    // Press tab to indent all lines
 4480    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4481
 4482    cx.assert_editor_state(
 4483        r#"ˇingress:
 4484    ˇapi:
 4485        ˇenabled: false
 4486        ˇpathType: Prefix
 4487"#,
 4488    );
 4489}
 4490
 4491#[gpui::test]
 4492async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4493    init_test(cx, |settings| {
 4494        settings.defaults.hard_tabs = Some(true);
 4495    });
 4496
 4497    let mut cx = EditorTestContext::new(cx).await;
 4498
 4499    // select two ranges on one line
 4500    cx.set_state(indoc! {"
 4501        «oneˇ» «twoˇ»
 4502        three
 4503        four
 4504    "});
 4505    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4506    cx.assert_editor_state(indoc! {"
 4507        \t«oneˇ» «twoˇ»
 4508        three
 4509        four
 4510    "});
 4511    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4512    cx.assert_editor_state(indoc! {"
 4513        \t\t«oneˇ» «twoˇ»
 4514        three
 4515        four
 4516    "});
 4517    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4518    cx.assert_editor_state(indoc! {"
 4519        \t«oneˇ» «twoˇ»
 4520        three
 4521        four
 4522    "});
 4523    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4524    cx.assert_editor_state(indoc! {"
 4525        «oneˇ» «twoˇ»
 4526        three
 4527        four
 4528    "});
 4529
 4530    // select across a line ending
 4531    cx.set_state(indoc! {"
 4532        one two
 4533        t«hree
 4534        ˇ»four
 4535    "});
 4536    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4537    cx.assert_editor_state(indoc! {"
 4538        one two
 4539        \tt«hree
 4540        ˇ»four
 4541    "});
 4542    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4543    cx.assert_editor_state(indoc! {"
 4544        one two
 4545        \t\tt«hree
 4546        ˇ»four
 4547    "});
 4548    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4549    cx.assert_editor_state(indoc! {"
 4550        one two
 4551        \tt«hree
 4552        ˇ»four
 4553    "});
 4554    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4555    cx.assert_editor_state(indoc! {"
 4556        one two
 4557        t«hree
 4558        ˇ»four
 4559    "});
 4560
 4561    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4562    cx.set_state(indoc! {"
 4563        one two
 4564        ˇthree
 4565        four
 4566    "});
 4567    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4568    cx.assert_editor_state(indoc! {"
 4569        one two
 4570        ˇthree
 4571        four
 4572    "});
 4573    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4574    cx.assert_editor_state(indoc! {"
 4575        one two
 4576        \tˇthree
 4577        four
 4578    "});
 4579    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4580    cx.assert_editor_state(indoc! {"
 4581        one two
 4582        ˇthree
 4583        four
 4584    "});
 4585}
 4586
 4587#[gpui::test]
 4588fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4589    init_test(cx, |settings| {
 4590        settings.languages.0.extend([
 4591            (
 4592                "TOML".into(),
 4593                LanguageSettingsContent {
 4594                    tab_size: NonZeroU32::new(2),
 4595                    ..Default::default()
 4596                },
 4597            ),
 4598            (
 4599                "Rust".into(),
 4600                LanguageSettingsContent {
 4601                    tab_size: NonZeroU32::new(4),
 4602                    ..Default::default()
 4603                },
 4604            ),
 4605        ]);
 4606    });
 4607
 4608    let toml_language = Arc::new(Language::new(
 4609        LanguageConfig {
 4610            name: "TOML".into(),
 4611            ..Default::default()
 4612        },
 4613        None,
 4614    ));
 4615    let rust_language = Arc::new(Language::new(
 4616        LanguageConfig {
 4617            name: "Rust".into(),
 4618            ..Default::default()
 4619        },
 4620        None,
 4621    ));
 4622
 4623    let toml_buffer =
 4624        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4625    let rust_buffer =
 4626        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4627    let multibuffer = cx.new(|cx| {
 4628        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4629        multibuffer.push_excerpts(
 4630            toml_buffer.clone(),
 4631            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4632            cx,
 4633        );
 4634        multibuffer.push_excerpts(
 4635            rust_buffer.clone(),
 4636            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4637            cx,
 4638        );
 4639        multibuffer
 4640    });
 4641
 4642    cx.add_window(|window, cx| {
 4643        let mut editor = build_editor(multibuffer, window, cx);
 4644
 4645        assert_eq!(
 4646            editor.text(cx),
 4647            indoc! {"
 4648                a = 1
 4649                b = 2
 4650
 4651                const c: usize = 3;
 4652            "}
 4653        );
 4654
 4655        select_ranges(
 4656            &mut editor,
 4657            indoc! {"
 4658                «aˇ» = 1
 4659                b = 2
 4660
 4661                «const c:ˇ» usize = 3;
 4662            "},
 4663            window,
 4664            cx,
 4665        );
 4666
 4667        editor.tab(&Tab, window, cx);
 4668        assert_text_with_selections(
 4669            &mut editor,
 4670            indoc! {"
 4671                  «aˇ» = 1
 4672                b = 2
 4673
 4674                    «const c:ˇ» usize = 3;
 4675            "},
 4676            cx,
 4677        );
 4678        editor.backtab(&Backtab, window, cx);
 4679        assert_text_with_selections(
 4680            &mut editor,
 4681            indoc! {"
 4682                «aˇ» = 1
 4683                b = 2
 4684
 4685                «const c:ˇ» usize = 3;
 4686            "},
 4687            cx,
 4688        );
 4689
 4690        editor
 4691    });
 4692}
 4693
 4694#[gpui::test]
 4695async fn test_backspace(cx: &mut TestAppContext) {
 4696    init_test(cx, |_| {});
 4697
 4698    let mut cx = EditorTestContext::new(cx).await;
 4699
 4700    // Basic backspace
 4701    cx.set_state(indoc! {"
 4702        onˇe two three
 4703        fou«rˇ» five six
 4704        seven «ˇeight nine
 4705        »ten
 4706    "});
 4707    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4708    cx.assert_editor_state(indoc! {"
 4709        oˇe two three
 4710        fouˇ five six
 4711        seven ˇten
 4712    "});
 4713
 4714    // Test backspace inside and around indents
 4715    cx.set_state(indoc! {"
 4716        zero
 4717            ˇone
 4718                ˇtwo
 4719            ˇ ˇ ˇ  three
 4720        ˇ  ˇ  four
 4721    "});
 4722    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4723    cx.assert_editor_state(indoc! {"
 4724        zero
 4725        ˇone
 4726            ˇtwo
 4727        ˇ  threeˇ  four
 4728    "});
 4729}
 4730
 4731#[gpui::test]
 4732async fn test_delete(cx: &mut TestAppContext) {
 4733    init_test(cx, |_| {});
 4734
 4735    let mut cx = EditorTestContext::new(cx).await;
 4736    cx.set_state(indoc! {"
 4737        onˇe two three
 4738        fou«rˇ» five six
 4739        seven «ˇeight nine
 4740        »ten
 4741    "});
 4742    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4743    cx.assert_editor_state(indoc! {"
 4744        onˇ two three
 4745        fouˇ five six
 4746        seven ˇten
 4747    "});
 4748}
 4749
 4750#[gpui::test]
 4751fn test_delete_line(cx: &mut TestAppContext) {
 4752    init_test(cx, |_| {});
 4753
 4754    let editor = cx.add_window(|window, cx| {
 4755        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4756        build_editor(buffer, window, cx)
 4757    });
 4758    _ = editor.update(cx, |editor, window, cx| {
 4759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4760            s.select_display_ranges([
 4761                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4762                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4763                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4764            ])
 4765        });
 4766        editor.delete_line(&DeleteLine, window, cx);
 4767        assert_eq!(editor.display_text(cx), "ghi");
 4768        assert_eq!(
 4769            display_ranges(editor, cx),
 4770            vec![
 4771                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4772                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4773            ]
 4774        );
 4775    });
 4776
 4777    let editor = cx.add_window(|window, cx| {
 4778        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4779        build_editor(buffer, window, cx)
 4780    });
 4781    _ = editor.update(cx, |editor, window, cx| {
 4782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4783            s.select_display_ranges([
 4784                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4785            ])
 4786        });
 4787        editor.delete_line(&DeleteLine, window, cx);
 4788        assert_eq!(editor.display_text(cx), "ghi\n");
 4789        assert_eq!(
 4790            display_ranges(editor, cx),
 4791            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4792        );
 4793    });
 4794
 4795    let editor = cx.add_window(|window, cx| {
 4796        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4797        build_editor(buffer, window, cx)
 4798    });
 4799    _ = editor.update(cx, |editor, window, cx| {
 4800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4801            s.select_display_ranges([
 4802                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4803            ])
 4804        });
 4805        editor.delete_line(&DeleteLine, window, cx);
 4806        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4807        assert_eq!(
 4808            display_ranges(editor, cx),
 4809            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4810        );
 4811    });
 4812}
 4813
 4814#[gpui::test]
 4815fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4816    init_test(cx, |_| {});
 4817
 4818    cx.add_window(|window, cx| {
 4819        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4820        let mut editor = build_editor(buffer.clone(), window, cx);
 4821        let buffer = buffer.read(cx).as_singleton().unwrap();
 4822
 4823        assert_eq!(
 4824            editor
 4825                .selections
 4826                .ranges::<Point>(&editor.display_snapshot(cx)),
 4827            &[Point::new(0, 0)..Point::new(0, 0)]
 4828        );
 4829
 4830        // When on single line, replace newline at end by space
 4831        editor.join_lines(&JoinLines, window, cx);
 4832        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4833        assert_eq!(
 4834            editor
 4835                .selections
 4836                .ranges::<Point>(&editor.display_snapshot(cx)),
 4837            &[Point::new(0, 3)..Point::new(0, 3)]
 4838        );
 4839
 4840        // When multiple lines are selected, remove newlines that are spanned by the selection
 4841        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4842            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4843        });
 4844        editor.join_lines(&JoinLines, window, cx);
 4845        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4846        assert_eq!(
 4847            editor
 4848                .selections
 4849                .ranges::<Point>(&editor.display_snapshot(cx)),
 4850            &[Point::new(0, 11)..Point::new(0, 11)]
 4851        );
 4852
 4853        // Undo should be transactional
 4854        editor.undo(&Undo, window, cx);
 4855        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4856        assert_eq!(
 4857            editor
 4858                .selections
 4859                .ranges::<Point>(&editor.display_snapshot(cx)),
 4860            &[Point::new(0, 5)..Point::new(2, 2)]
 4861        );
 4862
 4863        // When joining an empty line don't insert a space
 4864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4865            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4866        });
 4867        editor.join_lines(&JoinLines, window, cx);
 4868        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4869        assert_eq!(
 4870            editor
 4871                .selections
 4872                .ranges::<Point>(&editor.display_snapshot(cx)),
 4873            [Point::new(2, 3)..Point::new(2, 3)]
 4874        );
 4875
 4876        // We can remove trailing newlines
 4877        editor.join_lines(&JoinLines, window, cx);
 4878        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4879        assert_eq!(
 4880            editor
 4881                .selections
 4882                .ranges::<Point>(&editor.display_snapshot(cx)),
 4883            [Point::new(2, 3)..Point::new(2, 3)]
 4884        );
 4885
 4886        // We don't blow up on the last line
 4887        editor.join_lines(&JoinLines, window, cx);
 4888        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4889        assert_eq!(
 4890            editor
 4891                .selections
 4892                .ranges::<Point>(&editor.display_snapshot(cx)),
 4893            [Point::new(2, 3)..Point::new(2, 3)]
 4894        );
 4895
 4896        // reset to test indentation
 4897        editor.buffer.update(cx, |buffer, cx| {
 4898            buffer.edit(
 4899                [
 4900                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4901                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4902                ],
 4903                None,
 4904                cx,
 4905            )
 4906        });
 4907
 4908        // We remove any leading spaces
 4909        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4911            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4912        });
 4913        editor.join_lines(&JoinLines, window, cx);
 4914        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4915
 4916        // We don't insert a space for a line containing only spaces
 4917        editor.join_lines(&JoinLines, window, cx);
 4918        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4919
 4920        // We ignore any leading tabs
 4921        editor.join_lines(&JoinLines, window, cx);
 4922        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4923
 4924        editor
 4925    });
 4926}
 4927
 4928#[gpui::test]
 4929fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4930    init_test(cx, |_| {});
 4931
 4932    cx.add_window(|window, cx| {
 4933        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4934        let mut editor = build_editor(buffer.clone(), window, cx);
 4935        let buffer = buffer.read(cx).as_singleton().unwrap();
 4936
 4937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4938            s.select_ranges([
 4939                Point::new(0, 2)..Point::new(1, 1),
 4940                Point::new(1, 2)..Point::new(1, 2),
 4941                Point::new(3, 1)..Point::new(3, 2),
 4942            ])
 4943        });
 4944
 4945        editor.join_lines(&JoinLines, window, cx);
 4946        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4947
 4948        assert_eq!(
 4949            editor
 4950                .selections
 4951                .ranges::<Point>(&editor.display_snapshot(cx)),
 4952            [
 4953                Point::new(0, 7)..Point::new(0, 7),
 4954                Point::new(1, 3)..Point::new(1, 3)
 4955            ]
 4956        );
 4957        editor
 4958    });
 4959}
 4960
 4961#[gpui::test]
 4962async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4963    init_test(cx, |_| {});
 4964
 4965    let mut cx = EditorTestContext::new(cx).await;
 4966
 4967    let diff_base = r#"
 4968        Line 0
 4969        Line 1
 4970        Line 2
 4971        Line 3
 4972        "#
 4973    .unindent();
 4974
 4975    cx.set_state(
 4976        &r#"
 4977        ˇLine 0
 4978        Line 1
 4979        Line 2
 4980        Line 3
 4981        "#
 4982        .unindent(),
 4983    );
 4984
 4985    cx.set_head_text(&diff_base);
 4986    executor.run_until_parked();
 4987
 4988    // Join lines
 4989    cx.update_editor(|editor, window, cx| {
 4990        editor.join_lines(&JoinLines, window, cx);
 4991    });
 4992    executor.run_until_parked();
 4993
 4994    cx.assert_editor_state(
 4995        &r#"
 4996        Line 0ˇ Line 1
 4997        Line 2
 4998        Line 3
 4999        "#
 5000        .unindent(),
 5001    );
 5002    // Join again
 5003    cx.update_editor(|editor, window, cx| {
 5004        editor.join_lines(&JoinLines, window, cx);
 5005    });
 5006    executor.run_until_parked();
 5007
 5008    cx.assert_editor_state(
 5009        &r#"
 5010        Line 0 Line 1ˇ Line 2
 5011        Line 3
 5012        "#
 5013        .unindent(),
 5014    );
 5015}
 5016
 5017#[gpui::test]
 5018async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) {
 5019    init_test(cx, |_| {});
 5020
 5021    {
 5022        let language = Arc::new(Language::new(
 5023            LanguageConfig {
 5024                line_comments: vec!["// ".into(), "/// ".into()],
 5025                documentation_comment: Some(BlockCommentConfig {
 5026                    start: "/*".into(),
 5027                    end: "*/".into(),
 5028                    prefix: "* ".into(),
 5029                    tab_size: 1,
 5030                }),
 5031                ..LanguageConfig::default()
 5032            },
 5033            None,
 5034        ));
 5035
 5036        let mut cx = EditorTestContext::new(cx).await;
 5037        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5038
 5039        // Strips the comment prefix (with trailing space) from the joined-in line.
 5040        cx.set_state(indoc! {"
 5041            // ˇfoo
 5042            // bar
 5043        "});
 5044        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5045        cx.assert_editor_state(indoc! {"
 5046            // fooˇ bar
 5047        "});
 5048
 5049        // Strips the longer doc-comment prefix when both `//` and `///` match.
 5050        cx.set_state(indoc! {"
 5051            /// ˇfoo
 5052            /// bar
 5053        "});
 5054        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5055        cx.assert_editor_state(indoc! {"
 5056            /// fooˇ bar
 5057        "});
 5058
 5059        // Does not strip when the second line is a regular line (no comment prefix).
 5060        cx.set_state(indoc! {"
 5061            // ˇfoo
 5062            bar
 5063        "});
 5064        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5065        cx.assert_editor_state(indoc! {"
 5066            // fooˇ bar
 5067        "});
 5068
 5069        // No-whitespace join also strips the comment prefix.
 5070        cx.set_state(indoc! {"
 5071            // ˇfoo
 5072            // bar
 5073        "});
 5074        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5075        cx.assert_editor_state(indoc! {"
 5076            // fooˇbar
 5077        "});
 5078
 5079        // Strips even when the joined-in line is just the bare prefix (no trailing space).
 5080        cx.set_state(indoc! {"
 5081            // ˇfoo
 5082            //
 5083        "});
 5084        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5085        cx.assert_editor_state(indoc! {"
 5086            // fooˇ
 5087        "});
 5088
 5089        // Mixed line comment prefix types: the longer matching prefix is stripped.
 5090        cx.set_state(indoc! {"
 5091            // ˇfoo
 5092            /// bar
 5093        "});
 5094        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5095        cx.assert_editor_state(indoc! {"
 5096            // fooˇ bar
 5097        "});
 5098
 5099        // Strips block comment body prefix (`* `) from the joined-in line.
 5100        cx.set_state(indoc! {"
 5101             * ˇfoo
 5102             * bar
 5103        "});
 5104        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5105        cx.assert_editor_state(indoc! {"
 5106             * fooˇ bar
 5107        "});
 5108
 5109        // Strips bare block comment body prefix (`*` without trailing space).
 5110        cx.set_state(indoc! {"
 5111             * ˇfoo
 5112             *
 5113        "});
 5114        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5115        cx.assert_editor_state(indoc! {"
 5116             * fooˇ
 5117        "});
 5118    }
 5119
 5120    {
 5121        let markdown_language = Arc::new(Language::new(
 5122            LanguageConfig {
 5123                unordered_list: vec!["- ".into(), "* ".into(), "+ ".into()],
 5124                ..LanguageConfig::default()
 5125            },
 5126            None,
 5127        ));
 5128
 5129        let mut cx = EditorTestContext::new(cx).await;
 5130        cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
 5131
 5132        // Strips the `- ` list marker from the joined-in line.
 5133        cx.set_state(indoc! {"
 5134            - ˇfoo
 5135            - bar
 5136        "});
 5137        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5138        cx.assert_editor_state(indoc! {"
 5139            - fooˇ bar
 5140        "});
 5141
 5142        // Strips the `* ` list marker from the joined-in line.
 5143        cx.set_state(indoc! {"
 5144            * ˇfoo
 5145            * bar
 5146        "});
 5147        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5148        cx.assert_editor_state(indoc! {"
 5149            * fooˇ bar
 5150        "});
 5151
 5152        // Strips the `+ ` list marker from the joined-in line.
 5153        cx.set_state(indoc! {"
 5154            + ˇfoo
 5155            + bar
 5156        "});
 5157        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5158        cx.assert_editor_state(indoc! {"
 5159            + fooˇ bar
 5160        "});
 5161
 5162        // No-whitespace join also strips the list marker.
 5163        cx.set_state(indoc! {"
 5164            - ˇfoo
 5165            - bar
 5166        "});
 5167        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5168        cx.assert_editor_state(indoc! {"
 5169            - fooˇbar
 5170        "});
 5171    }
 5172}
 5173
 5174#[gpui::test]
 5175async fn test_custom_newlines_cause_no_false_positive_diffs(
 5176    executor: BackgroundExecutor,
 5177    cx: &mut TestAppContext,
 5178) {
 5179    init_test(cx, |_| {});
 5180    let mut cx = EditorTestContext::new(cx).await;
 5181    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 5182    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 5183    executor.run_until_parked();
 5184
 5185    cx.update_editor(|editor, window, cx| {
 5186        let snapshot = editor.snapshot(window, cx);
 5187        assert_eq!(
 5188            snapshot
 5189                .buffer_snapshot()
 5190                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 5191                .collect::<Vec<_>>(),
 5192            Vec::new(),
 5193            "Should not have any diffs for files with custom newlines"
 5194        );
 5195    });
 5196}
 5197
 5198#[gpui::test]
 5199async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 5200    init_test(cx, |_| {});
 5201
 5202    let mut cx = EditorTestContext::new(cx).await;
 5203
 5204    // Test sort_lines_case_insensitive()
 5205    cx.set_state(indoc! {"
 5206        «z
 5207        y
 5208        x
 5209        Z
 5210        Y
 5211        Xˇ»
 5212    "});
 5213    cx.update_editor(|e, window, cx| {
 5214        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 5215    });
 5216    cx.assert_editor_state(indoc! {"
 5217        «x
 5218        X
 5219        y
 5220        Y
 5221        z
 5222        Zˇ»
 5223    "});
 5224
 5225    // Test sort_lines_by_length()
 5226    //
 5227    // Demonstrates:
 5228    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 5229    // - sort is stable
 5230    cx.set_state(indoc! {"
 5231        «123
 5232        æ
 5233        12
 5234 5235        1
 5236        æˇ»
 5237    "});
 5238    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 5239    cx.assert_editor_state(indoc! {"
 5240        «æ
 5241 5242        1
 5243        æ
 5244        12
 5245        123ˇ»
 5246    "});
 5247
 5248    // Test reverse_lines()
 5249    cx.set_state(indoc! {"
 5250        «5
 5251        4
 5252        3
 5253        2
 5254        1ˇ»
 5255    "});
 5256    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 5257    cx.assert_editor_state(indoc! {"
 5258        «1
 5259        2
 5260        3
 5261        4
 5262        5ˇ»
 5263    "});
 5264
 5265    // Skip testing shuffle_line()
 5266
 5267    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 5268    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 5269
 5270    // Don't manipulate when cursor is on single line, but expand the selection
 5271    cx.set_state(indoc! {"
 5272        ddˇdd
 5273        ccc
 5274        bb
 5275        a
 5276    "});
 5277    cx.update_editor(|e, window, cx| {
 5278        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5279    });
 5280    cx.assert_editor_state(indoc! {"
 5281        «ddddˇ»
 5282        ccc
 5283        bb
 5284        a
 5285    "});
 5286
 5287    // Basic manipulate case
 5288    // Start selection moves to column 0
 5289    // End of selection shrinks to fit shorter line
 5290    cx.set_state(indoc! {"
 5291        dd«d
 5292        ccc
 5293        bb
 5294        aaaaaˇ»
 5295    "});
 5296    cx.update_editor(|e, window, cx| {
 5297        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5298    });
 5299    cx.assert_editor_state(indoc! {"
 5300        «aaaaa
 5301        bb
 5302        ccc
 5303        dddˇ»
 5304    "});
 5305
 5306    // Manipulate case with newlines
 5307    cx.set_state(indoc! {"
 5308        dd«d
 5309        ccc
 5310
 5311        bb
 5312        aaaaa
 5313
 5314        ˇ»
 5315    "});
 5316    cx.update_editor(|e, window, cx| {
 5317        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5318    });
 5319    cx.assert_editor_state(indoc! {"
 5320        «
 5321
 5322        aaaaa
 5323        bb
 5324        ccc
 5325        dddˇ»
 5326
 5327    "});
 5328
 5329    // Adding new line
 5330    cx.set_state(indoc! {"
 5331        aa«a
 5332        bbˇ»b
 5333    "});
 5334    cx.update_editor(|e, window, cx| {
 5335        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 5336    });
 5337    cx.assert_editor_state(indoc! {"
 5338        «aaa
 5339        bbb
 5340        added_lineˇ»
 5341    "});
 5342
 5343    // Removing line
 5344    cx.set_state(indoc! {"
 5345        aa«a
 5346        bbbˇ»
 5347    "});
 5348    cx.update_editor(|e, window, cx| {
 5349        e.manipulate_immutable_lines(window, cx, |lines| {
 5350            lines.pop();
 5351        })
 5352    });
 5353    cx.assert_editor_state(indoc! {"
 5354        «aaaˇ»
 5355    "});
 5356
 5357    // Removing all lines
 5358    cx.set_state(indoc! {"
 5359        aa«a
 5360        bbbˇ»
 5361    "});
 5362    cx.update_editor(|e, window, cx| {
 5363        e.manipulate_immutable_lines(window, cx, |lines| {
 5364            lines.drain(..);
 5365        })
 5366    });
 5367    cx.assert_editor_state(indoc! {"
 5368        ˇ
 5369    "});
 5370}
 5371
 5372#[gpui::test]
 5373async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5374    init_test(cx, |_| {});
 5375
 5376    let mut cx = EditorTestContext::new(cx).await;
 5377
 5378    // Consider continuous selection as single selection
 5379    cx.set_state(indoc! {"
 5380        Aaa«aa
 5381        cˇ»c«c
 5382        bb
 5383        aaaˇ»aa
 5384    "});
 5385    cx.update_editor(|e, window, cx| {
 5386        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5387    });
 5388    cx.assert_editor_state(indoc! {"
 5389        «Aaaaa
 5390        ccc
 5391        bb
 5392        aaaaaˇ»
 5393    "});
 5394
 5395    cx.set_state(indoc! {"
 5396        Aaa«aa
 5397        cˇ»c«c
 5398        bb
 5399        aaaˇ»aa
 5400    "});
 5401    cx.update_editor(|e, window, cx| {
 5402        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5403    });
 5404    cx.assert_editor_state(indoc! {"
 5405        «Aaaaa
 5406        ccc
 5407        bbˇ»
 5408    "});
 5409
 5410    // Consider non continuous selection as distinct dedup operations
 5411    cx.set_state(indoc! {"
 5412        «aaaaa
 5413        bb
 5414        aaaaa
 5415        aaaaaˇ»
 5416
 5417        aaa«aaˇ»
 5418    "});
 5419    cx.update_editor(|e, window, cx| {
 5420        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5421    });
 5422    cx.assert_editor_state(indoc! {"
 5423        «aaaaa
 5424        bbˇ»
 5425
 5426        «aaaaaˇ»
 5427    "});
 5428}
 5429
 5430#[gpui::test]
 5431async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5432    init_test(cx, |_| {});
 5433
 5434    let mut cx = EditorTestContext::new(cx).await;
 5435
 5436    cx.set_state(indoc! {"
 5437        «Aaa
 5438        aAa
 5439        Aaaˇ»
 5440    "});
 5441    cx.update_editor(|e, window, cx| {
 5442        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5443    });
 5444    cx.assert_editor_state(indoc! {"
 5445        «Aaa
 5446        aAaˇ»
 5447    "});
 5448
 5449    cx.set_state(indoc! {"
 5450        «Aaa
 5451        aAa
 5452        aaAˇ»
 5453    "});
 5454    cx.update_editor(|e, window, cx| {
 5455        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5456    });
 5457    cx.assert_editor_state(indoc! {"
 5458        «Aaaˇ»
 5459    "});
 5460}
 5461
 5462#[gpui::test]
 5463async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5464    init_test(cx, |_| {});
 5465
 5466    let mut cx = EditorTestContext::new(cx).await;
 5467
 5468    let js_language = Arc::new(Language::new(
 5469        LanguageConfig {
 5470            name: "JavaScript".into(),
 5471            wrap_characters: Some(language::WrapCharactersConfig {
 5472                start_prefix: "<".into(),
 5473                start_suffix: ">".into(),
 5474                end_prefix: "</".into(),
 5475                end_suffix: ">".into(),
 5476            }),
 5477            ..LanguageConfig::default()
 5478        },
 5479        None,
 5480    ));
 5481
 5482    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5483
 5484    cx.set_state(indoc! {"
 5485        «testˇ»
 5486    "});
 5487    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5488    cx.assert_editor_state(indoc! {"
 5489        <«ˇ»>test</«ˇ»>
 5490    "});
 5491
 5492    cx.set_state(indoc! {"
 5493        «test
 5494         testˇ»
 5495    "});
 5496    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5497    cx.assert_editor_state(indoc! {"
 5498        <«ˇ»>test
 5499         test</«ˇ»>
 5500    "});
 5501
 5502    cx.set_state(indoc! {"
 5503        teˇst
 5504    "});
 5505    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5506    cx.assert_editor_state(indoc! {"
 5507        te<«ˇ»></«ˇ»>st
 5508    "});
 5509}
 5510
 5511#[gpui::test]
 5512async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5513    init_test(cx, |_| {});
 5514
 5515    let mut cx = EditorTestContext::new(cx).await;
 5516
 5517    let js_language = Arc::new(Language::new(
 5518        LanguageConfig {
 5519            name: "JavaScript".into(),
 5520            wrap_characters: Some(language::WrapCharactersConfig {
 5521                start_prefix: "<".into(),
 5522                start_suffix: ">".into(),
 5523                end_prefix: "</".into(),
 5524                end_suffix: ">".into(),
 5525            }),
 5526            ..LanguageConfig::default()
 5527        },
 5528        None,
 5529    ));
 5530
 5531    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5532
 5533    cx.set_state(indoc! {"
 5534        «testˇ»
 5535        «testˇ» «testˇ»
 5536        «testˇ»
 5537    "});
 5538    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5539    cx.assert_editor_state(indoc! {"
 5540        <«ˇ»>test</«ˇ»>
 5541        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5542        <«ˇ»>test</«ˇ»>
 5543    "});
 5544
 5545    cx.set_state(indoc! {"
 5546        «test
 5547         testˇ»
 5548        «test
 5549         testˇ»
 5550    "});
 5551    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5552    cx.assert_editor_state(indoc! {"
 5553        <«ˇ»>test
 5554         test</«ˇ»>
 5555        <«ˇ»>test
 5556         test</«ˇ»>
 5557    "});
 5558}
 5559
 5560#[gpui::test]
 5561async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5562    init_test(cx, |_| {});
 5563
 5564    let mut cx = EditorTestContext::new(cx).await;
 5565
 5566    let plaintext_language = Arc::new(Language::new(
 5567        LanguageConfig {
 5568            name: "Plain Text".into(),
 5569            ..LanguageConfig::default()
 5570        },
 5571        None,
 5572    ));
 5573
 5574    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5575
 5576    cx.set_state(indoc! {"
 5577        «testˇ»
 5578    "});
 5579    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5580    cx.assert_editor_state(indoc! {"
 5581      «testˇ»
 5582    "});
 5583}
 5584
 5585#[gpui::test]
 5586async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5587    init_test(cx, |_| {});
 5588
 5589    let mut cx = EditorTestContext::new(cx).await;
 5590
 5591    // Manipulate with multiple selections on a single line
 5592    cx.set_state(indoc! {"
 5593        dd«dd
 5594        cˇ»c«c
 5595        bb
 5596        aaaˇ»aa
 5597    "});
 5598    cx.update_editor(|e, window, cx| {
 5599        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5600    });
 5601    cx.assert_editor_state(indoc! {"
 5602        «aaaaa
 5603        bb
 5604        ccc
 5605        ddddˇ»
 5606    "});
 5607
 5608    // Manipulate with multiple disjoin selections
 5609    cx.set_state(indoc! {"
 5610 5611        4
 5612        3
 5613        2
 5614        1ˇ»
 5615
 5616        dd«dd
 5617        ccc
 5618        bb
 5619        aaaˇ»aa
 5620    "});
 5621    cx.update_editor(|e, window, cx| {
 5622        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5623    });
 5624    cx.assert_editor_state(indoc! {"
 5625        «1
 5626        2
 5627        3
 5628        4
 5629        5ˇ»
 5630
 5631        «aaaaa
 5632        bb
 5633        ccc
 5634        ddddˇ»
 5635    "});
 5636
 5637    // Adding lines on each selection
 5638    cx.set_state(indoc! {"
 5639 5640        1ˇ»
 5641
 5642        bb«bb
 5643        aaaˇ»aa
 5644    "});
 5645    cx.update_editor(|e, window, cx| {
 5646        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5647    });
 5648    cx.assert_editor_state(indoc! {"
 5649        «2
 5650        1
 5651        added lineˇ»
 5652
 5653        «bbbb
 5654        aaaaa
 5655        added lineˇ»
 5656    "});
 5657
 5658    // Removing lines on each selection
 5659    cx.set_state(indoc! {"
 5660 5661        1ˇ»
 5662
 5663        bb«bb
 5664        aaaˇ»aa
 5665    "});
 5666    cx.update_editor(|e, window, cx| {
 5667        e.manipulate_immutable_lines(window, cx, |lines| {
 5668            lines.pop();
 5669        })
 5670    });
 5671    cx.assert_editor_state(indoc! {"
 5672        «2ˇ»
 5673
 5674        «bbbbˇ»
 5675    "});
 5676}
 5677
 5678#[gpui::test]
 5679async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5680    init_test(cx, |settings| {
 5681        settings.defaults.tab_size = NonZeroU32::new(3)
 5682    });
 5683
 5684    let mut cx = EditorTestContext::new(cx).await;
 5685
 5686    // MULTI SELECTION
 5687    // Ln.1 "«" tests empty lines
 5688    // Ln.9 tests just leading whitespace
 5689    cx.set_state(indoc! {"
 5690        «
 5691        abc                 // No indentationˇ»
 5692        «\tabc              // 1 tabˇ»
 5693        \t\tabc «      ˇ»   // 2 tabs
 5694        \t ab«c             // Tab followed by space
 5695         \tabc              // Space followed by tab (3 spaces should be the result)
 5696        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5697           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5698        \t
 5699        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5700    "});
 5701    cx.update_editor(|e, window, cx| {
 5702        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5703    });
 5704    cx.assert_editor_state(
 5705        indoc! {"
 5706            «
 5707            abc                 // No indentation
 5708               abc              // 1 tab
 5709                  abc          // 2 tabs
 5710                abc             // Tab followed by space
 5711               abc              // Space followed by tab (3 spaces should be the result)
 5712                           abc   // Mixed indentation (tab conversion depends on the column)
 5713               abc         // Already space indented
 5714               ·
 5715               abc\tdef          // Only the leading tab is manipulatedˇ»
 5716        "}
 5717        .replace("·", "")
 5718        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5719    );
 5720
 5721    // Test on just a few lines, the others should remain unchanged
 5722    // Only lines (3, 5, 10, 11) should change
 5723    cx.set_state(
 5724        indoc! {"
 5725            ·
 5726            abc                 // No indentation
 5727            \tabcˇ               // 1 tab
 5728            \t\tabc             // 2 tabs
 5729            \t abcˇ              // Tab followed by space
 5730             \tabc              // Space followed by tab (3 spaces should be the result)
 5731            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5732               abc              // Already space indented
 5733            «\t
 5734            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5735        "}
 5736        .replace("·", "")
 5737        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5738    );
 5739    cx.update_editor(|e, window, cx| {
 5740        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5741    });
 5742    cx.assert_editor_state(
 5743        indoc! {"
 5744            ·
 5745            abc                 // No indentation
 5746            «   abc               // 1 tabˇ»
 5747            \t\tabc             // 2 tabs
 5748            «    abc              // Tab followed by spaceˇ»
 5749             \tabc              // Space followed by tab (3 spaces should be the result)
 5750            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5751               abc              // Already space indented
 5752            «   ·
 5753               abc\tdef          // Only the leading tab is manipulatedˇ»
 5754        "}
 5755        .replace("·", "")
 5756        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5757    );
 5758
 5759    // SINGLE SELECTION
 5760    // Ln.1 "«" tests empty lines
 5761    // Ln.9 tests just leading whitespace
 5762    cx.set_state(indoc! {"
 5763        «
 5764        abc                 // No indentation
 5765        \tabc               // 1 tab
 5766        \t\tabc             // 2 tabs
 5767        \t abc              // Tab followed by space
 5768         \tabc              // Space followed by tab (3 spaces should be the result)
 5769        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5770           abc              // Already space indented
 5771        \t
 5772        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5773    "});
 5774    cx.update_editor(|e, window, cx| {
 5775        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5776    });
 5777    cx.assert_editor_state(
 5778        indoc! {"
 5779            «
 5780            abc                 // No indentation
 5781               abc               // 1 tab
 5782                  abc             // 2 tabs
 5783                abc              // Tab followed by space
 5784               abc              // Space followed by tab (3 spaces should be the result)
 5785                           abc   // Mixed indentation (tab conversion depends on the column)
 5786               abc              // Already space indented
 5787               ·
 5788               abc\tdef          // Only the leading tab is manipulatedˇ»
 5789        "}
 5790        .replace("·", "")
 5791        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5792    );
 5793}
 5794
 5795#[gpui::test]
 5796async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5797    init_test(cx, |settings| {
 5798        settings.defaults.tab_size = NonZeroU32::new(3)
 5799    });
 5800
 5801    let mut cx = EditorTestContext::new(cx).await;
 5802
 5803    // MULTI SELECTION
 5804    // Ln.1 "«" tests empty lines
 5805    // Ln.11 tests just leading whitespace
 5806    cx.set_state(indoc! {"
 5807        «
 5808        abˇ»ˇc                 // No indentation
 5809         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5810          abc  «             // 2 spaces (< 3 so dont convert)
 5811           abc              // 3 spaces (convert)
 5812             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5813        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5814        «\t abc              // Tab followed by space
 5815         \tabc              // Space followed by tab (should be consumed due to tab)
 5816        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5817           \tˇ»  «\t
 5818           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5819    "});
 5820    cx.update_editor(|e, window, cx| {
 5821        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5822    });
 5823    cx.assert_editor_state(indoc! {"
 5824        «
 5825        abc                 // No indentation
 5826         abc                // 1 space (< 3 so dont convert)
 5827          abc               // 2 spaces (< 3 so dont convert)
 5828        \tabc              // 3 spaces (convert)
 5829        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5830        \t\t\tabc           // Already tab indented
 5831        \t abc              // Tab followed by space
 5832        \tabc              // Space followed by tab (should be consumed due to tab)
 5833        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5834        \t\t\t
 5835        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5836    "});
 5837
 5838    // Test on just a few lines, the other should remain unchanged
 5839    // Only lines (4, 8, 11, 12) should change
 5840    cx.set_state(
 5841        indoc! {"
 5842            ·
 5843            abc                 // No indentation
 5844             abc                // 1 space (< 3 so dont convert)
 5845              abc               // 2 spaces (< 3 so dont convert)
 5846            «   abc              // 3 spaces (convert)ˇ»
 5847                 abc            // 5 spaces (1 tab + 2 spaces)
 5848            \t\t\tabc           // Already tab indented
 5849            \t abc              // Tab followed by space
 5850             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5851               \t\t  \tabc      // Mixed indentation
 5852            \t \t  \t   \tabc   // Mixed indentation
 5853               \t  \tˇ
 5854            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5855        "}
 5856        .replace("·", "")
 5857        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5858    );
 5859    cx.update_editor(|e, window, cx| {
 5860        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5861    });
 5862    cx.assert_editor_state(
 5863        indoc! {"
 5864            ·
 5865            abc                 // No indentation
 5866             abc                // 1 space (< 3 so dont convert)
 5867              abc               // 2 spaces (< 3 so dont convert)
 5868            «\tabc              // 3 spaces (convert)ˇ»
 5869                 abc            // 5 spaces (1 tab + 2 spaces)
 5870            \t\t\tabc           // Already tab indented
 5871            \t abc              // Tab followed by space
 5872            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5873               \t\t  \tabc      // Mixed indentation
 5874            \t \t  \t   \tabc   // Mixed indentation
 5875            «\t\t\t
 5876            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5877        "}
 5878        .replace("·", "")
 5879        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5880    );
 5881
 5882    // SINGLE SELECTION
 5883    // Ln.1 "«" tests empty lines
 5884    // Ln.11 tests just leading whitespace
 5885    cx.set_state(indoc! {"
 5886        «
 5887        abc                 // No indentation
 5888         abc                // 1 space (< 3 so dont convert)
 5889          abc               // 2 spaces (< 3 so dont convert)
 5890           abc              // 3 spaces (convert)
 5891             abc            // 5 spaces (1 tab + 2 spaces)
 5892        \t\t\tabc           // Already tab indented
 5893        \t abc              // Tab followed by space
 5894         \tabc              // Space followed by tab (should be consumed due to tab)
 5895        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5896           \t  \t
 5897           abc   \t         // Only the leading spaces should be convertedˇ»
 5898    "});
 5899    cx.update_editor(|e, window, cx| {
 5900        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5901    });
 5902    cx.assert_editor_state(indoc! {"
 5903        «
 5904        abc                 // No indentation
 5905         abc                // 1 space (< 3 so dont convert)
 5906          abc               // 2 spaces (< 3 so dont convert)
 5907        \tabc              // 3 spaces (convert)
 5908        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5909        \t\t\tabc           // Already tab indented
 5910        \t abc              // Tab followed by space
 5911        \tabc              // Space followed by tab (should be consumed due to tab)
 5912        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5913        \t\t\t
 5914        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5915    "});
 5916}
 5917
 5918#[gpui::test]
 5919async fn test_toggle_case(cx: &mut TestAppContext) {
 5920    init_test(cx, |_| {});
 5921
 5922    let mut cx = EditorTestContext::new(cx).await;
 5923
 5924    // If all lower case -> upper case
 5925    cx.set_state(indoc! {"
 5926        «hello worldˇ»
 5927    "});
 5928    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5929    cx.assert_editor_state(indoc! {"
 5930        «HELLO WORLDˇ»
 5931    "});
 5932
 5933    // If all upper case -> lower case
 5934    cx.set_state(indoc! {"
 5935        «HELLO WORLDˇ»
 5936    "});
 5937    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5938    cx.assert_editor_state(indoc! {"
 5939        «hello worldˇ»
 5940    "});
 5941
 5942    // If any upper case characters are identified -> lower case
 5943    // This matches JetBrains IDEs
 5944    cx.set_state(indoc! {"
 5945        «hEllo worldˇ»
 5946    "});
 5947    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5948    cx.assert_editor_state(indoc! {"
 5949        «hello worldˇ»
 5950    "});
 5951}
 5952
 5953#[gpui::test]
 5954async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5955    init_test(cx, |_| {});
 5956
 5957    let mut cx = EditorTestContext::new(cx).await;
 5958
 5959    cx.set_state(indoc! {"
 5960        «implement-windows-supportˇ»
 5961    "});
 5962    cx.update_editor(|e, window, cx| {
 5963        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5964    });
 5965    cx.assert_editor_state(indoc! {"
 5966        «Implement windows supportˇ»
 5967    "});
 5968}
 5969
 5970#[gpui::test]
 5971async fn test_manipulate_text(cx: &mut TestAppContext) {
 5972    init_test(cx, |_| {});
 5973
 5974    let mut cx = EditorTestContext::new(cx).await;
 5975
 5976    // Test convert_to_upper_case()
 5977    cx.set_state(indoc! {"
 5978        «hello worldˇ»
 5979    "});
 5980    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5981    cx.assert_editor_state(indoc! {"
 5982        «HELLO WORLDˇ»
 5983    "});
 5984
 5985    // Test convert_to_lower_case()
 5986    cx.set_state(indoc! {"
 5987        «HELLO WORLDˇ»
 5988    "});
 5989    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5990    cx.assert_editor_state(indoc! {"
 5991        «hello worldˇ»
 5992    "});
 5993
 5994    // Test multiple line, single selection case
 5995    cx.set_state(indoc! {"
 5996        «The quick brown
 5997        fox jumps over
 5998        the lazy dogˇ»
 5999    "});
 6000    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6001    cx.assert_editor_state(indoc! {"
 6002        «The Quick Brown
 6003        Fox Jumps Over
 6004        The Lazy Dogˇ»
 6005    "});
 6006
 6007    // Test multiple line, single selection case
 6008    cx.set_state(indoc! {"
 6009        «The quick brown
 6010        fox jumps over
 6011        the lazy dogˇ»
 6012    "});
 6013    cx.update_editor(|e, window, cx| {
 6014        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 6015    });
 6016    cx.assert_editor_state(indoc! {"
 6017        «TheQuickBrown
 6018        FoxJumpsOver
 6019        TheLazyDogˇ»
 6020    "});
 6021
 6022    // From here on out, test more complex cases of manipulate_text()
 6023
 6024    // Test no selection case - should affect words cursors are in
 6025    // Cursor at beginning, middle, and end of word
 6026    cx.set_state(indoc! {"
 6027        ˇhello big beauˇtiful worldˇ
 6028    "});
 6029    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6030    cx.assert_editor_state(indoc! {"
 6031        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 6032    "});
 6033
 6034    // Test multiple selections on a single line and across multiple lines
 6035    cx.set_state(indoc! {"
 6036        «Theˇ» quick «brown
 6037        foxˇ» jumps «overˇ»
 6038        the «lazyˇ» dog
 6039    "});
 6040    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6041    cx.assert_editor_state(indoc! {"
 6042        «THEˇ» quick «BROWN
 6043        FOXˇ» jumps «OVERˇ»
 6044        the «LAZYˇ» dog
 6045    "});
 6046
 6047    // Test case where text length grows
 6048    cx.set_state(indoc! {"
 6049        «tschüߡ»
 6050    "});
 6051    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6052    cx.assert_editor_state(indoc! {"
 6053        «TSCHÜSSˇ»
 6054    "});
 6055
 6056    // Test to make sure we don't crash when text shrinks
 6057    cx.set_state(indoc! {"
 6058        aaa_bbbˇ
 6059    "});
 6060    cx.update_editor(|e, window, cx| {
 6061        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6062    });
 6063    cx.assert_editor_state(indoc! {"
 6064        «aaaBbbˇ»
 6065    "});
 6066
 6067    // Test to make sure we all aware of the fact that each word can grow and shrink
 6068    // Final selections should be aware of this fact
 6069    cx.set_state(indoc! {"
 6070        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 6071    "});
 6072    cx.update_editor(|e, window, cx| {
 6073        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6074    });
 6075    cx.assert_editor_state(indoc! {"
 6076        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 6077    "});
 6078
 6079    cx.set_state(indoc! {"
 6080        «hElLo, WoRld!ˇ»
 6081    "});
 6082    cx.update_editor(|e, window, cx| {
 6083        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 6084    });
 6085    cx.assert_editor_state(indoc! {"
 6086        «HeLlO, wOrLD!ˇ»
 6087    "});
 6088
 6089    // Test selections with `line_mode() = true`.
 6090    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 6091    cx.set_state(indoc! {"
 6092        «The quick brown
 6093        fox jumps over
 6094        tˇ»he lazy dog
 6095    "});
 6096    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6097    cx.assert_editor_state(indoc! {"
 6098        «THE QUICK BROWN
 6099        FOX JUMPS OVER
 6100        THE LAZY DOGˇ»
 6101    "});
 6102}
 6103
 6104#[gpui::test]
 6105fn test_duplicate_line(cx: &mut TestAppContext) {
 6106    init_test(cx, |_| {});
 6107
 6108    let editor = cx.add_window(|window, cx| {
 6109        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6110        build_editor(buffer, window, cx)
 6111    });
 6112    _ = editor.update(cx, |editor, window, cx| {
 6113        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6114            s.select_display_ranges([
 6115                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6116                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6117                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6118                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6119            ])
 6120        });
 6121        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6122        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6123        assert_eq!(
 6124            display_ranges(editor, cx),
 6125            vec![
 6126                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 6127                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 6128                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6129                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 6130            ]
 6131        );
 6132    });
 6133
 6134    let editor = cx.add_window(|window, cx| {
 6135        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6136        build_editor(buffer, window, cx)
 6137    });
 6138    _ = editor.update(cx, |editor, window, cx| {
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_display_ranges([
 6141                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6142                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6143            ])
 6144        });
 6145        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6146        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6147        assert_eq!(
 6148            display_ranges(editor, cx),
 6149            vec![
 6150                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 6151                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 6152            ]
 6153        );
 6154    });
 6155
 6156    // With `duplicate_line_up` the selections move to the duplicated lines,
 6157    // which are inserted above the original lines
 6158    let editor = cx.add_window(|window, cx| {
 6159        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6160        build_editor(buffer, window, cx)
 6161    });
 6162    _ = editor.update(cx, |editor, window, cx| {
 6163        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6164            s.select_display_ranges([
 6165                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6166                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6167                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6168                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6169            ])
 6170        });
 6171        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6172        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6173        assert_eq!(
 6174            display_ranges(editor, cx),
 6175            vec![
 6176                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6177                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6178                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6179                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6180            ]
 6181        );
 6182    });
 6183
 6184    let editor = cx.add_window(|window, cx| {
 6185        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6186        build_editor(buffer, window, cx)
 6187    });
 6188    _ = editor.update(cx, |editor, window, cx| {
 6189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6190            s.select_display_ranges([
 6191                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6192                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6193            ])
 6194        });
 6195        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6196        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6197        assert_eq!(
 6198            display_ranges(editor, cx),
 6199            vec![
 6200                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6201                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6202            ]
 6203        );
 6204    });
 6205
 6206    let editor = cx.add_window(|window, cx| {
 6207        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6208        build_editor(buffer, window, cx)
 6209    });
 6210    _ = editor.update(cx, |editor, window, cx| {
 6211        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6212            s.select_display_ranges([
 6213                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6214                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6215            ])
 6216        });
 6217        editor.duplicate_selection(&DuplicateSelection, window, cx);
 6218        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 6219        assert_eq!(
 6220            display_ranges(editor, cx),
 6221            vec![
 6222                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6223                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 6224            ]
 6225        );
 6226    });
 6227}
 6228
 6229#[gpui::test]
 6230async fn test_rotate_selections(cx: &mut TestAppContext) {
 6231    init_test(cx, |_| {});
 6232
 6233    let mut cx = EditorTestContext::new(cx).await;
 6234
 6235    // Rotate text selections (horizontal)
 6236    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6237    cx.update_editor(|e, window, cx| {
 6238        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6239    });
 6240    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 6241    cx.update_editor(|e, window, cx| {
 6242        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6243    });
 6244    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6245
 6246    // Rotate text selections (vertical)
 6247    cx.set_state(indoc! {"
 6248        x=«1ˇ»
 6249        y=«2ˇ»
 6250        z=«3ˇ»
 6251    "});
 6252    cx.update_editor(|e, window, cx| {
 6253        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6254    });
 6255    cx.assert_editor_state(indoc! {"
 6256        x=«3ˇ»
 6257        y=«1ˇ»
 6258        z=«2ˇ»
 6259    "});
 6260    cx.update_editor(|e, window, cx| {
 6261        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6262    });
 6263    cx.assert_editor_state(indoc! {"
 6264        x=«1ˇ»
 6265        y=«2ˇ»
 6266        z=«3ˇ»
 6267    "});
 6268
 6269    // Rotate text selections (vertical, different lengths)
 6270    cx.set_state(indoc! {"
 6271        x=\"«ˇ»\"
 6272        y=\"«aˇ»\"
 6273        z=\"«aaˇ»\"
 6274    "});
 6275    cx.update_editor(|e, window, cx| {
 6276        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6277    });
 6278    cx.assert_editor_state(indoc! {"
 6279        x=\"«aaˇ»\"
 6280        y=\"«ˇ»\"
 6281        z=\"«aˇ»\"
 6282    "});
 6283    cx.update_editor(|e, window, cx| {
 6284        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6285    });
 6286    cx.assert_editor_state(indoc! {"
 6287        x=\"«ˇ»\"
 6288        y=\"«aˇ»\"
 6289        z=\"«aaˇ»\"
 6290    "});
 6291
 6292    // Rotate whole lines (cursor positions preserved)
 6293    cx.set_state(indoc! {"
 6294        ˇline123
 6295        liˇne23
 6296        line3ˇ
 6297    "});
 6298    cx.update_editor(|e, window, cx| {
 6299        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6300    });
 6301    cx.assert_editor_state(indoc! {"
 6302        line3ˇ
 6303        ˇline123
 6304        liˇne23
 6305    "});
 6306    cx.update_editor(|e, window, cx| {
 6307        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6308    });
 6309    cx.assert_editor_state(indoc! {"
 6310        ˇline123
 6311        liˇne23
 6312        line3ˇ
 6313    "});
 6314
 6315    // Rotate whole lines, multiple cursors per line (positions preserved)
 6316    cx.set_state(indoc! {"
 6317        ˇliˇne123
 6318        ˇline23
 6319        ˇline3
 6320    "});
 6321    cx.update_editor(|e, window, cx| {
 6322        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6323    });
 6324    cx.assert_editor_state(indoc! {"
 6325        ˇline3
 6326        ˇliˇne123
 6327        ˇline23
 6328    "});
 6329    cx.update_editor(|e, window, cx| {
 6330        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6331    });
 6332    cx.assert_editor_state(indoc! {"
 6333        ˇliˇne123
 6334        ˇline23
 6335        ˇline3
 6336    "});
 6337}
 6338
 6339#[gpui::test]
 6340fn test_move_line_up_down(cx: &mut TestAppContext) {
 6341    init_test(cx, |_| {});
 6342
 6343    let editor = cx.add_window(|window, cx| {
 6344        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6345        build_editor(buffer, window, cx)
 6346    });
 6347    _ = editor.update(cx, |editor, window, cx| {
 6348        editor.fold_creases(
 6349            vec![
 6350                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6351                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6352                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6353            ],
 6354            true,
 6355            window,
 6356            cx,
 6357        );
 6358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6359            s.select_display_ranges([
 6360                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6361                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6362                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6363                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 6364            ])
 6365        });
 6366        assert_eq!(
 6367            editor.display_text(cx),
 6368            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6369        );
 6370
 6371        editor.move_line_up(&MoveLineUp, window, cx);
 6372        assert_eq!(
 6373            editor.display_text(cx),
 6374            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6375        );
 6376        assert_eq!(
 6377            display_ranges(editor, cx),
 6378            vec![
 6379                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6380                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6381                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6382                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6383            ]
 6384        );
 6385    });
 6386
 6387    _ = editor.update(cx, |editor, window, cx| {
 6388        editor.move_line_down(&MoveLineDown, window, cx);
 6389        assert_eq!(
 6390            editor.display_text(cx),
 6391            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6392        );
 6393        assert_eq!(
 6394            display_ranges(editor, cx),
 6395            vec![
 6396                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6397                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6398                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6399                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6400            ]
 6401        );
 6402    });
 6403
 6404    _ = editor.update(cx, |editor, window, cx| {
 6405        editor.move_line_down(&MoveLineDown, window, cx);
 6406        assert_eq!(
 6407            editor.display_text(cx),
 6408            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6409        );
 6410        assert_eq!(
 6411            display_ranges(editor, cx),
 6412            vec![
 6413                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6414                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6415                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6416                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6417            ]
 6418        );
 6419    });
 6420
 6421    _ = editor.update(cx, |editor, window, cx| {
 6422        editor.move_line_up(&MoveLineUp, window, cx);
 6423        assert_eq!(
 6424            editor.display_text(cx),
 6425            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6426        );
 6427        assert_eq!(
 6428            display_ranges(editor, cx),
 6429            vec![
 6430                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6431                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6432                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6433                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6434            ]
 6435        );
 6436    });
 6437}
 6438
 6439#[gpui::test]
 6440fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6441    init_test(cx, |_| {});
 6442    let editor = cx.add_window(|window, cx| {
 6443        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6444        build_editor(buffer, window, cx)
 6445    });
 6446    _ = editor.update(cx, |editor, window, cx| {
 6447        editor.fold_creases(
 6448            vec![Crease::simple(
 6449                Point::new(6, 4)..Point::new(7, 4),
 6450                FoldPlaceholder::test(),
 6451            )],
 6452            true,
 6453            window,
 6454            cx,
 6455        );
 6456        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6457            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6458        });
 6459        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6460        editor.move_line_up(&MoveLineUp, window, cx);
 6461        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6462        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6463    });
 6464}
 6465
 6466#[gpui::test]
 6467fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6468    init_test(cx, |_| {});
 6469
 6470    let editor = cx.add_window(|window, cx| {
 6471        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6472        build_editor(buffer, window, cx)
 6473    });
 6474    _ = editor.update(cx, |editor, window, cx| {
 6475        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6476        editor.insert_blocks(
 6477            [BlockProperties {
 6478                style: BlockStyle::Fixed,
 6479                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6480                height: Some(1),
 6481                render: Arc::new(|_| div().into_any()),
 6482                priority: 0,
 6483            }],
 6484            Some(Autoscroll::fit()),
 6485            cx,
 6486        );
 6487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6488            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6489        });
 6490        editor.move_line_down(&MoveLineDown, window, cx);
 6491    });
 6492}
 6493
 6494#[gpui::test]
 6495async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6496    init_test(cx, |_| {});
 6497
 6498    let mut cx = EditorTestContext::new(cx).await;
 6499    cx.set_state(
 6500        &"
 6501            ˇzero
 6502            one
 6503            two
 6504            three
 6505            four
 6506            five
 6507        "
 6508        .unindent(),
 6509    );
 6510
 6511    // Create a four-line block that replaces three lines of text.
 6512    cx.update_editor(|editor, window, cx| {
 6513        let snapshot = editor.snapshot(window, cx);
 6514        let snapshot = &snapshot.buffer_snapshot();
 6515        let placement = BlockPlacement::Replace(
 6516            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6517        );
 6518        editor.insert_blocks(
 6519            [BlockProperties {
 6520                placement,
 6521                height: Some(4),
 6522                style: BlockStyle::Sticky,
 6523                render: Arc::new(|_| gpui::div().into_any_element()),
 6524                priority: 0,
 6525            }],
 6526            None,
 6527            cx,
 6528        );
 6529    });
 6530
 6531    // Move down so that the cursor touches the block.
 6532    cx.update_editor(|editor, window, cx| {
 6533        editor.move_down(&Default::default(), window, cx);
 6534    });
 6535    cx.assert_editor_state(
 6536        &"
 6537            zero
 6538            «one
 6539            two
 6540            threeˇ»
 6541            four
 6542            five
 6543        "
 6544        .unindent(),
 6545    );
 6546
 6547    // Move down past the block.
 6548    cx.update_editor(|editor, window, cx| {
 6549        editor.move_down(&Default::default(), window, cx);
 6550    });
 6551    cx.assert_editor_state(
 6552        &"
 6553            zero
 6554            one
 6555            two
 6556            three
 6557            ˇfour
 6558            five
 6559        "
 6560        .unindent(),
 6561    );
 6562}
 6563
 6564#[gpui::test]
 6565fn test_transpose(cx: &mut TestAppContext) {
 6566    init_test(cx, |_| {});
 6567
 6568    _ = cx.add_window(|window, cx| {
 6569        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6570        editor.set_style(EditorStyle::default(), window, cx);
 6571        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6572            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6573        });
 6574        editor.transpose(&Default::default(), window, cx);
 6575        assert_eq!(editor.text(cx), "bac");
 6576        assert_eq!(
 6577            editor.selections.ranges(&editor.display_snapshot(cx)),
 6578            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6579        );
 6580
 6581        editor.transpose(&Default::default(), window, cx);
 6582        assert_eq!(editor.text(cx), "bca");
 6583        assert_eq!(
 6584            editor.selections.ranges(&editor.display_snapshot(cx)),
 6585            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6586        );
 6587
 6588        editor.transpose(&Default::default(), window, cx);
 6589        assert_eq!(editor.text(cx), "bac");
 6590        assert_eq!(
 6591            editor.selections.ranges(&editor.display_snapshot(cx)),
 6592            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6593        );
 6594
 6595        editor
 6596    });
 6597
 6598    _ = cx.add_window(|window, cx| {
 6599        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6600        editor.set_style(EditorStyle::default(), window, cx);
 6601        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6602            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6603        });
 6604        editor.transpose(&Default::default(), window, cx);
 6605        assert_eq!(editor.text(cx), "acb\nde");
 6606        assert_eq!(
 6607            editor.selections.ranges(&editor.display_snapshot(cx)),
 6608            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6609        );
 6610
 6611        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6612            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6613        });
 6614        editor.transpose(&Default::default(), window, cx);
 6615        assert_eq!(editor.text(cx), "acbd\ne");
 6616        assert_eq!(
 6617            editor.selections.ranges(&editor.display_snapshot(cx)),
 6618            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6619        );
 6620
 6621        editor.transpose(&Default::default(), window, cx);
 6622        assert_eq!(editor.text(cx), "acbde\n");
 6623        assert_eq!(
 6624            editor.selections.ranges(&editor.display_snapshot(cx)),
 6625            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6626        );
 6627
 6628        editor.transpose(&Default::default(), window, cx);
 6629        assert_eq!(editor.text(cx), "acbd\ne");
 6630        assert_eq!(
 6631            editor.selections.ranges(&editor.display_snapshot(cx)),
 6632            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6633        );
 6634
 6635        editor
 6636    });
 6637
 6638    _ = cx.add_window(|window, cx| {
 6639        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6640        editor.set_style(EditorStyle::default(), window, cx);
 6641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6642            s.select_ranges([
 6643                MultiBufferOffset(1)..MultiBufferOffset(1),
 6644                MultiBufferOffset(2)..MultiBufferOffset(2),
 6645                MultiBufferOffset(4)..MultiBufferOffset(4),
 6646            ])
 6647        });
 6648        editor.transpose(&Default::default(), window, cx);
 6649        assert_eq!(editor.text(cx), "bacd\ne");
 6650        assert_eq!(
 6651            editor.selections.ranges(&editor.display_snapshot(cx)),
 6652            [
 6653                MultiBufferOffset(2)..MultiBufferOffset(2),
 6654                MultiBufferOffset(3)..MultiBufferOffset(3),
 6655                MultiBufferOffset(5)..MultiBufferOffset(5)
 6656            ]
 6657        );
 6658
 6659        editor.transpose(&Default::default(), window, cx);
 6660        assert_eq!(editor.text(cx), "bcade\n");
 6661        assert_eq!(
 6662            editor.selections.ranges(&editor.display_snapshot(cx)),
 6663            [
 6664                MultiBufferOffset(3)..MultiBufferOffset(3),
 6665                MultiBufferOffset(4)..MultiBufferOffset(4),
 6666                MultiBufferOffset(6)..MultiBufferOffset(6)
 6667            ]
 6668        );
 6669
 6670        editor.transpose(&Default::default(), window, cx);
 6671        assert_eq!(editor.text(cx), "bcda\ne");
 6672        assert_eq!(
 6673            editor.selections.ranges(&editor.display_snapshot(cx)),
 6674            [
 6675                MultiBufferOffset(4)..MultiBufferOffset(4),
 6676                MultiBufferOffset(6)..MultiBufferOffset(6)
 6677            ]
 6678        );
 6679
 6680        editor.transpose(&Default::default(), window, cx);
 6681        assert_eq!(editor.text(cx), "bcade\n");
 6682        assert_eq!(
 6683            editor.selections.ranges(&editor.display_snapshot(cx)),
 6684            [
 6685                MultiBufferOffset(4)..MultiBufferOffset(4),
 6686                MultiBufferOffset(6)..MultiBufferOffset(6)
 6687            ]
 6688        );
 6689
 6690        editor.transpose(&Default::default(), window, cx);
 6691        assert_eq!(editor.text(cx), "bcaed\n");
 6692        assert_eq!(
 6693            editor.selections.ranges(&editor.display_snapshot(cx)),
 6694            [
 6695                MultiBufferOffset(5)..MultiBufferOffset(5),
 6696                MultiBufferOffset(6)..MultiBufferOffset(6)
 6697            ]
 6698        );
 6699
 6700        editor
 6701    });
 6702
 6703    _ = cx.add_window(|window, cx| {
 6704        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6705        editor.set_style(EditorStyle::default(), window, cx);
 6706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6707            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6708        });
 6709        editor.transpose(&Default::default(), window, cx);
 6710        assert_eq!(editor.text(cx), "🏀🍐✋");
 6711        assert_eq!(
 6712            editor.selections.ranges(&editor.display_snapshot(cx)),
 6713            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6714        );
 6715
 6716        editor.transpose(&Default::default(), window, cx);
 6717        assert_eq!(editor.text(cx), "🏀✋🍐");
 6718        assert_eq!(
 6719            editor.selections.ranges(&editor.display_snapshot(cx)),
 6720            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6721        );
 6722
 6723        editor.transpose(&Default::default(), window, cx);
 6724        assert_eq!(editor.text(cx), "🏀🍐✋");
 6725        assert_eq!(
 6726            editor.selections.ranges(&editor.display_snapshot(cx)),
 6727            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6728        );
 6729
 6730        editor
 6731    });
 6732}
 6733
 6734#[gpui::test]
 6735async fn test_rewrap(cx: &mut TestAppContext) {
 6736    init_test(cx, |settings| {
 6737        settings.languages.0.extend([
 6738            (
 6739                "Markdown".into(),
 6740                LanguageSettingsContent {
 6741                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6742                    preferred_line_length: Some(40),
 6743                    ..Default::default()
 6744                },
 6745            ),
 6746            (
 6747                "Plain Text".into(),
 6748                LanguageSettingsContent {
 6749                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6750                    preferred_line_length: Some(40),
 6751                    ..Default::default()
 6752                },
 6753            ),
 6754            (
 6755                "C++".into(),
 6756                LanguageSettingsContent {
 6757                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6758                    preferred_line_length: Some(40),
 6759                    ..Default::default()
 6760                },
 6761            ),
 6762            (
 6763                "Python".into(),
 6764                LanguageSettingsContent {
 6765                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6766                    preferred_line_length: Some(40),
 6767                    ..Default::default()
 6768                },
 6769            ),
 6770            (
 6771                "Rust".into(),
 6772                LanguageSettingsContent {
 6773                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6774                    preferred_line_length: Some(40),
 6775                    ..Default::default()
 6776                },
 6777            ),
 6778        ])
 6779    });
 6780
 6781    let mut cx = EditorTestContext::new(cx).await;
 6782
 6783    let cpp_language = Arc::new(Language::new(
 6784        LanguageConfig {
 6785            name: "C++".into(),
 6786            line_comments: vec!["// ".into()],
 6787            ..LanguageConfig::default()
 6788        },
 6789        None,
 6790    ));
 6791    let python_language = Arc::new(Language::new(
 6792        LanguageConfig {
 6793            name: "Python".into(),
 6794            line_comments: vec!["# ".into()],
 6795            ..LanguageConfig::default()
 6796        },
 6797        None,
 6798    ));
 6799    let markdown_language = Arc::new(Language::new(
 6800        LanguageConfig {
 6801            name: "Markdown".into(),
 6802            rewrap_prefixes: vec![
 6803                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6804                regex::Regex::new("[-*+]\\s+").unwrap(),
 6805            ],
 6806            ..LanguageConfig::default()
 6807        },
 6808        None,
 6809    ));
 6810    let rust_language = Arc::new(
 6811        Language::new(
 6812            LanguageConfig {
 6813                name: "Rust".into(),
 6814                line_comments: vec!["// ".into(), "/// ".into()],
 6815                ..LanguageConfig::default()
 6816            },
 6817            Some(tree_sitter_rust::LANGUAGE.into()),
 6818        )
 6819        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6820        .unwrap(),
 6821    );
 6822
 6823    let plaintext_language = Arc::new(Language::new(
 6824        LanguageConfig {
 6825            name: "Plain Text".into(),
 6826            ..LanguageConfig::default()
 6827        },
 6828        None,
 6829    ));
 6830
 6831    // Test basic rewrapping of a long line with a cursor
 6832    assert_rewrap(
 6833        indoc! {"
 6834            // ˇThis is a long comment that needs to be wrapped.
 6835        "},
 6836        indoc! {"
 6837            // ˇThis is a long comment that needs to
 6838            // be wrapped.
 6839        "},
 6840        cpp_language.clone(),
 6841        &mut cx,
 6842    );
 6843
 6844    // Test rewrapping a full selection
 6845    assert_rewrap(
 6846        indoc! {"
 6847            «// This selected long comment needs to be wrapped.ˇ»"
 6848        },
 6849        indoc! {"
 6850            «// This selected long comment needs to
 6851            // be wrapped.ˇ»"
 6852        },
 6853        cpp_language.clone(),
 6854        &mut cx,
 6855    );
 6856
 6857    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6858    assert_rewrap(
 6859        indoc! {"
 6860            // ˇThis is the first line.
 6861            // Thisˇ is the second line.
 6862            // This is the thirdˇ line, all part of one paragraph.
 6863         "},
 6864        indoc! {"
 6865            // ˇThis is the first line. Thisˇ is the
 6866            // second line. This is the thirdˇ line,
 6867            // all part of one paragraph.
 6868         "},
 6869        cpp_language.clone(),
 6870        &mut cx,
 6871    );
 6872
 6873    // Test multiple cursors in different paragraphs trigger separate rewraps
 6874    assert_rewrap(
 6875        indoc! {"
 6876            // ˇThis is the first paragraph, first line.
 6877            // ˇThis is the first paragraph, second line.
 6878
 6879            // ˇThis is the second paragraph, first line.
 6880            // ˇThis is the second paragraph, second line.
 6881        "},
 6882        indoc! {"
 6883            // ˇThis is the first paragraph, first
 6884            // line. ˇThis is the first paragraph,
 6885            // second line.
 6886
 6887            // ˇThis is the second paragraph, first
 6888            // line. ˇThis is the second paragraph,
 6889            // second line.
 6890        "},
 6891        cpp_language.clone(),
 6892        &mut cx,
 6893    );
 6894
 6895    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6896    assert_rewrap(
 6897        indoc! {"
 6898            «// A regular long long comment to be wrapped.
 6899            /// A documentation long comment to be wrapped.ˇ»
 6900          "},
 6901        indoc! {"
 6902            «// A regular long long comment to be
 6903            // wrapped.
 6904            /// A documentation long comment to be
 6905            /// wrapped.ˇ»
 6906          "},
 6907        rust_language.clone(),
 6908        &mut cx,
 6909    );
 6910
 6911    // Test that change in indentation level trigger seperate rewraps
 6912    assert_rewrap(
 6913        indoc! {"
 6914            fn foo() {
 6915                «// This is a long comment at the base indent.
 6916                    // This is a long comment at the next indent.ˇ»
 6917            }
 6918        "},
 6919        indoc! {"
 6920            fn foo() {
 6921                «// This is a long comment at the
 6922                // base indent.
 6923                    // This is a long comment at the
 6924                    // next indent.ˇ»
 6925            }
 6926        "},
 6927        rust_language.clone(),
 6928        &mut cx,
 6929    );
 6930
 6931    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6932    assert_rewrap(
 6933        indoc! {"
 6934            # ˇThis is a long comment using a pound sign.
 6935        "},
 6936        indoc! {"
 6937            # ˇThis is a long comment using a pound
 6938            # sign.
 6939        "},
 6940        python_language,
 6941        &mut cx,
 6942    );
 6943
 6944    // Test rewrapping only affects comments, not code even when selected
 6945    assert_rewrap(
 6946        indoc! {"
 6947            «/// This doc comment is long and should be wrapped.
 6948            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6949        "},
 6950        indoc! {"
 6951            «/// This doc comment is long and should
 6952            /// be wrapped.
 6953            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6954        "},
 6955        rust_language.clone(),
 6956        &mut cx,
 6957    );
 6958
 6959    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6960    assert_rewrap(
 6961        indoc! {"
 6962            # Header
 6963
 6964            A long long long line of markdown text to wrap.ˇ
 6965         "},
 6966        indoc! {"
 6967            # Header
 6968
 6969            A long long long line of markdown text
 6970            to wrap.ˇ
 6971         "},
 6972        markdown_language.clone(),
 6973        &mut cx,
 6974    );
 6975
 6976    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6977    assert_rewrap(
 6978        indoc! {"
 6979            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6980            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6981            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6982        "},
 6983        indoc! {"
 6984            «1. This is a numbered list item that is
 6985               very long and needs to be wrapped
 6986               properly.
 6987            2. This is a numbered list item that is
 6988               very long and needs to be wrapped
 6989               properly.
 6990            - This is an unordered list item that is
 6991              also very long and should not merge
 6992              with the numbered item.ˇ»
 6993        "},
 6994        markdown_language.clone(),
 6995        &mut cx,
 6996    );
 6997
 6998    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6999    assert_rewrap(
 7000        indoc! {"
 7001            «1. This is a numbered list item that is
 7002            very long and needs to be wrapped
 7003            properly.
 7004            2. This is a numbered list item that is
 7005            very long and needs to be wrapped
 7006            properly.
 7007            - This is an unordered list item that is
 7008            also very long and should not merge with
 7009            the numbered item.ˇ»
 7010        "},
 7011        indoc! {"
 7012            «1. This is a numbered list item that is
 7013               very long and needs to be wrapped
 7014               properly.
 7015            2. This is a numbered list item that is
 7016               very long and needs to be wrapped
 7017               properly.
 7018            - This is an unordered list item that is
 7019              also very long and should not merge
 7020              with the numbered item.ˇ»
 7021        "},
 7022        markdown_language.clone(),
 7023        &mut cx,
 7024    );
 7025
 7026    // Test that rewrapping maintain indents even when they already exists.
 7027    assert_rewrap(
 7028        indoc! {"
 7029            «1. This is a numbered list
 7030               item that is very long and needs to be wrapped properly.
 7031            2. This is a numbered list
 7032               item that is very long and needs to be wrapped properly.
 7033            - This is an unordered list item that is also very long and
 7034              should not merge with the numbered item.ˇ»
 7035        "},
 7036        indoc! {"
 7037            «1. This is a numbered list item that is
 7038               very long and needs to be wrapped
 7039               properly.
 7040            2. This is a numbered list item that is
 7041               very long and needs to be wrapped
 7042               properly.
 7043            - This is an unordered list item that is
 7044              also very long and should not merge
 7045              with the numbered item.ˇ»
 7046        "},
 7047        markdown_language,
 7048        &mut cx,
 7049    );
 7050
 7051    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 7052    assert_rewrap(
 7053        indoc! {"
 7054            ˇThis is a very long line of plain text that will be wrapped.
 7055        "},
 7056        indoc! {"
 7057            ˇThis is a very long line of plain text
 7058            that will be wrapped.
 7059        "},
 7060        plaintext_language.clone(),
 7061        &mut cx,
 7062    );
 7063
 7064    // Test that non-commented code acts as a paragraph boundary within a selection
 7065    assert_rewrap(
 7066        indoc! {"
 7067               «// This is the first long comment block to be wrapped.
 7068               fn my_func(a: u32);
 7069               // This is the second long comment block to be wrapped.ˇ»
 7070           "},
 7071        indoc! {"
 7072               «// This is the first long comment block
 7073               // to be wrapped.
 7074               fn my_func(a: u32);
 7075               // This is the second long comment block
 7076               // to be wrapped.ˇ»
 7077           "},
 7078        rust_language,
 7079        &mut cx,
 7080    );
 7081
 7082    // Test rewrapping multiple selections, including ones with blank lines or tabs
 7083    assert_rewrap(
 7084        indoc! {"
 7085            «ˇThis is a very long line that will be wrapped.
 7086
 7087            This is another paragraph in the same selection.»
 7088
 7089            «\tThis is a very long indented line that will be wrapped.ˇ»
 7090         "},
 7091        indoc! {"
 7092            «ˇThis is a very long line that will be
 7093            wrapped.
 7094
 7095            This is another paragraph in the same
 7096            selection.»
 7097
 7098            «\tThis is a very long indented line
 7099            \tthat will be wrapped.ˇ»
 7100         "},
 7101        plaintext_language,
 7102        &mut cx,
 7103    );
 7104
 7105    // Test that an empty comment line acts as a paragraph boundary
 7106    assert_rewrap(
 7107        indoc! {"
 7108            // ˇThis is a long comment that will be wrapped.
 7109            //
 7110            // And this is another long comment that will also be wrapped.ˇ
 7111         "},
 7112        indoc! {"
 7113            // ˇThis is a long comment that will be
 7114            // wrapped.
 7115            //
 7116            // And this is another long comment that
 7117            // will also be wrapped.ˇ
 7118         "},
 7119        cpp_language,
 7120        &mut cx,
 7121    );
 7122
 7123    #[track_caller]
 7124    fn assert_rewrap(
 7125        unwrapped_text: &str,
 7126        wrapped_text: &str,
 7127        language: Arc<Language>,
 7128        cx: &mut EditorTestContext,
 7129    ) {
 7130        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7131        cx.set_state(unwrapped_text);
 7132        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7133        cx.assert_editor_state(wrapped_text);
 7134    }
 7135}
 7136
 7137#[gpui::test]
 7138async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 7139    init_test(cx, |settings| {
 7140        settings.languages.0.extend([(
 7141            "Rust".into(),
 7142            LanguageSettingsContent {
 7143                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7144                preferred_line_length: Some(40),
 7145                ..Default::default()
 7146            },
 7147        )])
 7148    });
 7149
 7150    let mut cx = EditorTestContext::new(cx).await;
 7151
 7152    let rust_lang = Arc::new(
 7153        Language::new(
 7154            LanguageConfig {
 7155                name: "Rust".into(),
 7156                line_comments: vec!["// ".into()],
 7157                block_comment: Some(BlockCommentConfig {
 7158                    start: "/*".into(),
 7159                    end: "*/".into(),
 7160                    prefix: "* ".into(),
 7161                    tab_size: 1,
 7162                }),
 7163                documentation_comment: Some(BlockCommentConfig {
 7164                    start: "/**".into(),
 7165                    end: "*/".into(),
 7166                    prefix: "* ".into(),
 7167                    tab_size: 1,
 7168                }),
 7169
 7170                ..LanguageConfig::default()
 7171            },
 7172            Some(tree_sitter_rust::LANGUAGE.into()),
 7173        )
 7174        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 7175        .unwrap(),
 7176    );
 7177
 7178    // regular block comment
 7179    assert_rewrap(
 7180        indoc! {"
 7181            /*
 7182             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7183             */
 7184            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7185        "},
 7186        indoc! {"
 7187            /*
 7188             *ˇ Lorem ipsum dolor sit amet,
 7189             * consectetur adipiscing elit.
 7190             */
 7191            /*
 7192             *ˇ Lorem ipsum dolor sit amet,
 7193             * consectetur adipiscing elit.
 7194             */
 7195        "},
 7196        rust_lang.clone(),
 7197        &mut cx,
 7198    );
 7199
 7200    // indent is respected
 7201    assert_rewrap(
 7202        indoc! {"
 7203            {}
 7204                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7205        "},
 7206        indoc! {"
 7207            {}
 7208                /*
 7209                 *ˇ Lorem ipsum dolor sit amet,
 7210                 * consectetur adipiscing elit.
 7211                 */
 7212        "},
 7213        rust_lang.clone(),
 7214        &mut cx,
 7215    );
 7216
 7217    // short block comments with inline delimiters
 7218    assert_rewrap(
 7219        indoc! {"
 7220            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7221            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7222             */
 7223            /*
 7224             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7225        "},
 7226        indoc! {"
 7227            /*
 7228             *ˇ Lorem ipsum dolor sit amet,
 7229             * consectetur adipiscing elit.
 7230             */
 7231            /*
 7232             *ˇ Lorem ipsum dolor sit amet,
 7233             * consectetur adipiscing elit.
 7234             */
 7235            /*
 7236             *ˇ Lorem ipsum dolor sit amet,
 7237             * consectetur adipiscing elit.
 7238             */
 7239        "},
 7240        rust_lang.clone(),
 7241        &mut cx,
 7242    );
 7243
 7244    // multiline block comment with inline start/end delimiters
 7245    assert_rewrap(
 7246        indoc! {"
 7247            /*ˇ Lorem ipsum dolor sit amet,
 7248             * consectetur adipiscing elit. */
 7249        "},
 7250        indoc! {"
 7251            /*
 7252             *ˇ Lorem ipsum dolor sit amet,
 7253             * consectetur adipiscing elit.
 7254             */
 7255        "},
 7256        rust_lang.clone(),
 7257        &mut cx,
 7258    );
 7259
 7260    // block comment rewrap still respects paragraph bounds
 7261    assert_rewrap(
 7262        indoc! {"
 7263            /*
 7264             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7265             *
 7266             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7267             */
 7268        "},
 7269        indoc! {"
 7270            /*
 7271             *ˇ Lorem ipsum dolor sit amet,
 7272             * consectetur adipiscing elit.
 7273             *
 7274             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7275             */
 7276        "},
 7277        rust_lang.clone(),
 7278        &mut cx,
 7279    );
 7280
 7281    // documentation comments
 7282    assert_rewrap(
 7283        indoc! {"
 7284            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7285            /**
 7286             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7287             */
 7288        "},
 7289        indoc! {"
 7290            /**
 7291             *ˇ Lorem ipsum dolor sit amet,
 7292             * consectetur adipiscing elit.
 7293             */
 7294            /**
 7295             *ˇ Lorem ipsum dolor sit amet,
 7296             * consectetur adipiscing elit.
 7297             */
 7298        "},
 7299        rust_lang.clone(),
 7300        &mut cx,
 7301    );
 7302
 7303    // different, adjacent comments
 7304    assert_rewrap(
 7305        indoc! {"
 7306            /**
 7307             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7308             */
 7309            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7310            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7311        "},
 7312        indoc! {"
 7313            /**
 7314             *ˇ Lorem ipsum dolor sit amet,
 7315             * consectetur adipiscing elit.
 7316             */
 7317            /*
 7318             *ˇ Lorem ipsum dolor sit amet,
 7319             * consectetur adipiscing elit.
 7320             */
 7321            //ˇ Lorem ipsum dolor sit amet,
 7322            // consectetur adipiscing elit.
 7323        "},
 7324        rust_lang.clone(),
 7325        &mut cx,
 7326    );
 7327
 7328    // selection w/ single short block comment
 7329    assert_rewrap(
 7330        indoc! {"
 7331            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7332        "},
 7333        indoc! {"
 7334            «/*
 7335             * Lorem ipsum dolor sit amet,
 7336             * consectetur adipiscing elit.
 7337             */ˇ»
 7338        "},
 7339        rust_lang.clone(),
 7340        &mut cx,
 7341    );
 7342
 7343    // rewrapping a single comment w/ abutting comments
 7344    assert_rewrap(
 7345        indoc! {"
 7346            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7347            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7348        "},
 7349        indoc! {"
 7350            /*
 7351             * ˇLorem ipsum dolor sit amet,
 7352             * consectetur adipiscing elit.
 7353             */
 7354            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7355        "},
 7356        rust_lang.clone(),
 7357        &mut cx,
 7358    );
 7359
 7360    // selection w/ non-abutting short block comments
 7361    assert_rewrap(
 7362        indoc! {"
 7363            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7364
 7365            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7366        "},
 7367        indoc! {"
 7368            «/*
 7369             * Lorem ipsum dolor sit amet,
 7370             * consectetur adipiscing elit.
 7371             */
 7372
 7373            /*
 7374             * Lorem ipsum dolor sit amet,
 7375             * consectetur adipiscing elit.
 7376             */ˇ»
 7377        "},
 7378        rust_lang.clone(),
 7379        &mut cx,
 7380    );
 7381
 7382    // selection of multiline block comments
 7383    assert_rewrap(
 7384        indoc! {"
 7385            «/* Lorem ipsum dolor sit amet,
 7386             * consectetur adipiscing elit. */ˇ»
 7387        "},
 7388        indoc! {"
 7389            «/*
 7390             * Lorem ipsum dolor sit amet,
 7391             * consectetur adipiscing elit.
 7392             */ˇ»
 7393        "},
 7394        rust_lang.clone(),
 7395        &mut cx,
 7396    );
 7397
 7398    // partial selection of multiline block comments
 7399    assert_rewrap(
 7400        indoc! {"
 7401            «/* Lorem ipsum dolor sit amet,ˇ»
 7402             * consectetur adipiscing elit. */
 7403            /* Lorem ipsum dolor sit amet,
 7404             «* consectetur adipiscing elit. */ˇ»
 7405        "},
 7406        indoc! {"
 7407            «/*
 7408             * Lorem ipsum dolor sit amet,ˇ»
 7409             * consectetur adipiscing elit. */
 7410            /* Lorem ipsum dolor sit amet,
 7411             «* consectetur adipiscing elit.
 7412             */ˇ»
 7413        "},
 7414        rust_lang.clone(),
 7415        &mut cx,
 7416    );
 7417
 7418    // selection w/ abutting short block comments
 7419    // TODO: should not be combined; should rewrap as 2 comments
 7420    assert_rewrap(
 7421        indoc! {"
 7422            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7423            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7424        "},
 7425        // desired behavior:
 7426        // indoc! {"
 7427        //     «/*
 7428        //      * Lorem ipsum dolor sit amet,
 7429        //      * consectetur adipiscing elit.
 7430        //      */
 7431        //     /*
 7432        //      * Lorem ipsum dolor sit amet,
 7433        //      * consectetur adipiscing elit.
 7434        //      */ˇ»
 7435        // "},
 7436        // actual behaviour:
 7437        indoc! {"
 7438            «/*
 7439             * Lorem ipsum dolor sit amet,
 7440             * consectetur adipiscing elit. Lorem
 7441             * ipsum dolor sit amet, consectetur
 7442             * adipiscing elit.
 7443             */ˇ»
 7444        "},
 7445        rust_lang.clone(),
 7446        &mut cx,
 7447    );
 7448
 7449    // TODO: same as above, but with delimiters on separate line
 7450    // assert_rewrap(
 7451    //     indoc! {"
 7452    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7453    //          */
 7454    //         /*
 7455    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7456    //     "},
 7457    //     // desired:
 7458    //     // indoc! {"
 7459    //     //     «/*
 7460    //     //      * Lorem ipsum dolor sit amet,
 7461    //     //      * consectetur adipiscing elit.
 7462    //     //      */
 7463    //     //     /*
 7464    //     //      * Lorem ipsum dolor sit amet,
 7465    //     //      * consectetur adipiscing elit.
 7466    //     //      */ˇ»
 7467    //     // "},
 7468    //     // actual: (but with trailing w/s on the empty lines)
 7469    //     indoc! {"
 7470    //         «/*
 7471    //          * Lorem ipsum dolor sit amet,
 7472    //          * consectetur adipiscing elit.
 7473    //          *
 7474    //          */
 7475    //         /*
 7476    //          *
 7477    //          * Lorem ipsum dolor sit amet,
 7478    //          * consectetur adipiscing elit.
 7479    //          */ˇ»
 7480    //     "},
 7481    //     rust_lang.clone(),
 7482    //     &mut cx,
 7483    // );
 7484
 7485    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7486    assert_rewrap(
 7487        indoc! {"
 7488            /*
 7489             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7490             */
 7491            /*
 7492             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7493            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7494        "},
 7495        // desired:
 7496        // indoc! {"
 7497        //     /*
 7498        //      *ˇ Lorem ipsum dolor sit amet,
 7499        //      * consectetur adipiscing elit.
 7500        //      */
 7501        //     /*
 7502        //      *ˇ Lorem ipsum dolor sit amet,
 7503        //      * consectetur adipiscing elit.
 7504        //      */
 7505        //     /*
 7506        //      *ˇ Lorem ipsum dolor sit amet
 7507        //      */ /* consectetur adipiscing elit. */
 7508        // "},
 7509        // actual:
 7510        indoc! {"
 7511            /*
 7512             //ˇ Lorem ipsum dolor sit amet,
 7513             // consectetur adipiscing elit.
 7514             */
 7515            /*
 7516             * //ˇ Lorem ipsum dolor sit amet,
 7517             * consectetur adipiscing elit.
 7518             */
 7519            /*
 7520             *ˇ Lorem ipsum dolor sit amet */ /*
 7521             * consectetur adipiscing elit.
 7522             */
 7523        "},
 7524        rust_lang,
 7525        &mut cx,
 7526    );
 7527
 7528    #[track_caller]
 7529    fn assert_rewrap(
 7530        unwrapped_text: &str,
 7531        wrapped_text: &str,
 7532        language: Arc<Language>,
 7533        cx: &mut EditorTestContext,
 7534    ) {
 7535        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7536        cx.set_state(unwrapped_text);
 7537        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7538        cx.assert_editor_state(wrapped_text);
 7539    }
 7540}
 7541
 7542#[gpui::test]
 7543async fn test_hard_wrap(cx: &mut TestAppContext) {
 7544    init_test(cx, |_| {});
 7545    let mut cx = EditorTestContext::new(cx).await;
 7546
 7547    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7548    cx.update_editor(|editor, _, cx| {
 7549        editor.set_hard_wrap(Some(14), cx);
 7550    });
 7551
 7552    cx.set_state(indoc!(
 7553        "
 7554        one two three ˇ
 7555        "
 7556    ));
 7557    cx.simulate_input("four");
 7558    cx.run_until_parked();
 7559
 7560    cx.assert_editor_state(indoc!(
 7561        "
 7562        one two three
 7563        fourˇ
 7564        "
 7565    ));
 7566
 7567    cx.update_editor(|editor, window, cx| {
 7568        editor.newline(&Default::default(), window, cx);
 7569    });
 7570    cx.run_until_parked();
 7571    cx.assert_editor_state(indoc!(
 7572        "
 7573        one two three
 7574        four
 7575        ˇ
 7576        "
 7577    ));
 7578
 7579    cx.simulate_input("five");
 7580    cx.run_until_parked();
 7581    cx.assert_editor_state(indoc!(
 7582        "
 7583        one two three
 7584        four
 7585        fiveˇ
 7586        "
 7587    ));
 7588
 7589    cx.update_editor(|editor, window, cx| {
 7590        editor.newline(&Default::default(), window, cx);
 7591    });
 7592    cx.run_until_parked();
 7593    cx.simulate_input("# ");
 7594    cx.run_until_parked();
 7595    cx.assert_editor_state(indoc!(
 7596        "
 7597        one two three
 7598        four
 7599        five
 7600        # ˇ
 7601        "
 7602    ));
 7603
 7604    cx.update_editor(|editor, window, cx| {
 7605        editor.newline(&Default::default(), window, cx);
 7606    });
 7607    cx.run_until_parked();
 7608    cx.assert_editor_state(indoc!(
 7609        "
 7610        one two three
 7611        four
 7612        five
 7613        #\x20
 7614 7615        "
 7616    ));
 7617
 7618    cx.simulate_input(" 6");
 7619    cx.run_until_parked();
 7620    cx.assert_editor_state(indoc!(
 7621        "
 7622        one two three
 7623        four
 7624        five
 7625        #
 7626        # 6ˇ
 7627        "
 7628    ));
 7629}
 7630
 7631#[gpui::test]
 7632async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7633    init_test(cx, |_| {});
 7634
 7635    let mut cx = EditorTestContext::new(cx).await;
 7636
 7637    cx.set_state(indoc! {"The quick brownˇ"});
 7638    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7639    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7640
 7641    cx.set_state(indoc! {"The emacs foxˇ"});
 7642    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7643    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7644
 7645    cx.set_state(indoc! {"
 7646        The quick« brownˇ»
 7647        fox jumps overˇ
 7648        the lazy dog"});
 7649    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7650    cx.assert_editor_state(indoc! {"
 7651        The quickˇ
 7652        ˇthe lazy dog"});
 7653
 7654    cx.set_state(indoc! {"
 7655        The quick« brownˇ»
 7656        fox jumps overˇ
 7657        the lazy dog"});
 7658    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7659    cx.assert_editor_state(indoc! {"
 7660        The quickˇ
 7661        fox jumps overˇthe lazy dog"});
 7662
 7663    cx.set_state(indoc! {"
 7664        The quick« brownˇ»
 7665        fox jumps overˇ
 7666        the lazy dog"});
 7667    cx.update_editor(|e, window, cx| {
 7668        e.cut_to_end_of_line(
 7669            &CutToEndOfLine {
 7670                stop_at_newlines: true,
 7671            },
 7672            window,
 7673            cx,
 7674        )
 7675    });
 7676    cx.assert_editor_state(indoc! {"
 7677        The quickˇ
 7678        fox jumps overˇ
 7679        the lazy dog"});
 7680
 7681    cx.set_state(indoc! {"
 7682        The quick« brownˇ»
 7683        fox jumps overˇ
 7684        the lazy dog"});
 7685    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7686    cx.assert_editor_state(indoc! {"
 7687        The quickˇ
 7688        fox jumps overˇthe lazy dog"});
 7689}
 7690
 7691#[gpui::test]
 7692async fn test_clipboard(cx: &mut TestAppContext) {
 7693    init_test(cx, |_| {});
 7694
 7695    let mut cx = EditorTestContext::new(cx).await;
 7696
 7697    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7698    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7699    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7700
 7701    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7702    cx.set_state("two ˇfour ˇsix ˇ");
 7703    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7704    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7705
 7706    // Paste again but with only two cursors. Since the number of cursors doesn't
 7707    // match the number of slices in the clipboard, the entire clipboard text
 7708    // is pasted at each cursor.
 7709    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7710    cx.update_editor(|e, window, cx| {
 7711        e.handle_input("( ", window, cx);
 7712        e.paste(&Paste, window, cx);
 7713        e.handle_input(") ", window, cx);
 7714    });
 7715    cx.assert_editor_state(
 7716        &([
 7717            "( one✅ ",
 7718            "three ",
 7719            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7720            "three ",
 7721            "five ) ˇ",
 7722        ]
 7723        .join("\n")),
 7724    );
 7725
 7726    // Cut with three selections, one of which is full-line.
 7727    cx.set_state(indoc! {"
 7728        1«2ˇ»3
 7729        4ˇ567
 7730        «8ˇ»9"});
 7731    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7732    cx.assert_editor_state(indoc! {"
 7733        1ˇ3
 7734        ˇ9"});
 7735
 7736    // Paste with three selections, noticing how the copied selection that was full-line
 7737    // gets inserted before the second cursor.
 7738    cx.set_state(indoc! {"
 7739        1ˇ3
 7740 7741        «oˇ»ne"});
 7742    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7743    cx.assert_editor_state(indoc! {"
 7744        12ˇ3
 7745        4567
 7746 7747        8ˇne"});
 7748
 7749    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7750    cx.set_state(indoc! {"
 7751        The quick brown
 7752        fox juˇmps over
 7753        the lazy dog"});
 7754    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7755    assert_eq!(
 7756        cx.read_from_clipboard()
 7757            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7758        Some("fox jumps over\n".to_string())
 7759    );
 7760
 7761    // Paste with three selections, noticing how the copied full-line selection is inserted
 7762    // before the empty selections but replaces the selection that is non-empty.
 7763    cx.set_state(indoc! {"
 7764        Tˇhe quick brown
 7765        «foˇ»x jumps over
 7766        tˇhe lazy dog"});
 7767    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7768    cx.assert_editor_state(indoc! {"
 7769        fox jumps over
 7770        Tˇhe quick brown
 7771        fox jumps over
 7772        ˇx jumps over
 7773        fox jumps over
 7774        tˇhe lazy dog"});
 7775}
 7776
 7777#[gpui::test]
 7778async fn test_copy_trim(cx: &mut TestAppContext) {
 7779    init_test(cx, |_| {});
 7780
 7781    let mut cx = EditorTestContext::new(cx).await;
 7782    cx.set_state(
 7783        r#"            «for selection in selections.iter() {
 7784            let mut start = selection.start;
 7785            let mut end = selection.end;
 7786            let is_entire_line = selection.is_empty();
 7787            if is_entire_line {
 7788                start = Point::new(start.row, 0);ˇ»
 7789                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7790            }
 7791        "#,
 7792    );
 7793    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7794    assert_eq!(
 7795        cx.read_from_clipboard()
 7796            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7797        Some(
 7798            "for selection in selections.iter() {
 7799            let mut start = selection.start;
 7800            let mut end = selection.end;
 7801            let is_entire_line = selection.is_empty();
 7802            if is_entire_line {
 7803                start = Point::new(start.row, 0);"
 7804                .to_string()
 7805        ),
 7806        "Regular copying preserves all indentation selected",
 7807    );
 7808    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7809    assert_eq!(
 7810        cx.read_from_clipboard()
 7811            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7812        Some(
 7813            "for selection in selections.iter() {
 7814let mut start = selection.start;
 7815let mut end = selection.end;
 7816let is_entire_line = selection.is_empty();
 7817if is_entire_line {
 7818    start = Point::new(start.row, 0);"
 7819                .to_string()
 7820        ),
 7821        "Copying with stripping should strip all leading whitespaces"
 7822    );
 7823
 7824    cx.set_state(
 7825        r#"       «     for selection in selections.iter() {
 7826            let mut start = selection.start;
 7827            let mut end = selection.end;
 7828            let is_entire_line = selection.is_empty();
 7829            if is_entire_line {
 7830                start = Point::new(start.row, 0);ˇ»
 7831                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7832            }
 7833        "#,
 7834    );
 7835    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7836    assert_eq!(
 7837        cx.read_from_clipboard()
 7838            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7839        Some(
 7840            "     for selection in selections.iter() {
 7841            let mut start = selection.start;
 7842            let mut end = selection.end;
 7843            let is_entire_line = selection.is_empty();
 7844            if is_entire_line {
 7845                start = Point::new(start.row, 0);"
 7846                .to_string()
 7847        ),
 7848        "Regular copying preserves all indentation selected",
 7849    );
 7850    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7851    assert_eq!(
 7852        cx.read_from_clipboard()
 7853            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7854        Some(
 7855            "for selection in selections.iter() {
 7856let mut start = selection.start;
 7857let mut end = selection.end;
 7858let is_entire_line = selection.is_empty();
 7859if is_entire_line {
 7860    start = Point::new(start.row, 0);"
 7861                .to_string()
 7862        ),
 7863        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7864    );
 7865
 7866    cx.set_state(
 7867        r#"       «ˇ     for selection in selections.iter() {
 7868            let mut start = selection.start;
 7869            let mut end = selection.end;
 7870            let is_entire_line = selection.is_empty();
 7871            if is_entire_line {
 7872                start = Point::new(start.row, 0);»
 7873                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7874            }
 7875        "#,
 7876    );
 7877    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7878    assert_eq!(
 7879        cx.read_from_clipboard()
 7880            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7881        Some(
 7882            "     for selection in selections.iter() {
 7883            let mut start = selection.start;
 7884            let mut end = selection.end;
 7885            let is_entire_line = selection.is_empty();
 7886            if is_entire_line {
 7887                start = Point::new(start.row, 0);"
 7888                .to_string()
 7889        ),
 7890        "Regular copying for reverse selection works the same",
 7891    );
 7892    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7893    assert_eq!(
 7894        cx.read_from_clipboard()
 7895            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7896        Some(
 7897            "for selection in selections.iter() {
 7898let mut start = selection.start;
 7899let mut end = selection.end;
 7900let is_entire_line = selection.is_empty();
 7901if is_entire_line {
 7902    start = Point::new(start.row, 0);"
 7903                .to_string()
 7904        ),
 7905        "Copying with stripping for reverse selection works the same"
 7906    );
 7907
 7908    cx.set_state(
 7909        r#"            for selection «in selections.iter() {
 7910            let mut start = selection.start;
 7911            let mut end = selection.end;
 7912            let is_entire_line = selection.is_empty();
 7913            if is_entire_line {
 7914                start = Point::new(start.row, 0);ˇ»
 7915                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7916            }
 7917        "#,
 7918    );
 7919    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7920    assert_eq!(
 7921        cx.read_from_clipboard()
 7922            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7923        Some(
 7924            "in selections.iter() {
 7925            let mut start = selection.start;
 7926            let mut end = selection.end;
 7927            let is_entire_line = selection.is_empty();
 7928            if is_entire_line {
 7929                start = Point::new(start.row, 0);"
 7930                .to_string()
 7931        ),
 7932        "When selecting past the indent, the copying works as usual",
 7933    );
 7934    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7935    assert_eq!(
 7936        cx.read_from_clipboard()
 7937            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7938        Some(
 7939            "in selections.iter() {
 7940            let mut start = selection.start;
 7941            let mut end = selection.end;
 7942            let is_entire_line = selection.is_empty();
 7943            if is_entire_line {
 7944                start = Point::new(start.row, 0);"
 7945                .to_string()
 7946        ),
 7947        "When selecting past the indent, nothing is trimmed"
 7948    );
 7949
 7950    cx.set_state(
 7951        r#"            «for selection in selections.iter() {
 7952            let mut start = selection.start;
 7953
 7954            let mut end = selection.end;
 7955            let is_entire_line = selection.is_empty();
 7956            if is_entire_line {
 7957                start = Point::new(start.row, 0);
 7958ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7959            }
 7960        "#,
 7961    );
 7962    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7963    assert_eq!(
 7964        cx.read_from_clipboard()
 7965            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7966        Some(
 7967            "for selection in selections.iter() {
 7968let mut start = selection.start;
 7969
 7970let mut end = selection.end;
 7971let is_entire_line = selection.is_empty();
 7972if is_entire_line {
 7973    start = Point::new(start.row, 0);
 7974"
 7975            .to_string()
 7976        ),
 7977        "Copying with stripping should ignore empty lines"
 7978    );
 7979}
 7980
 7981#[gpui::test]
 7982async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7983    init_test(cx, |_| {});
 7984
 7985    let mut cx = EditorTestContext::new(cx).await;
 7986
 7987    cx.set_state(indoc! {"
 7988        «    a
 7989            bˇ»
 7990    "});
 7991    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7992    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7993
 7994    assert_eq!(
 7995        cx.read_from_clipboard().and_then(|item| item.text()),
 7996        Some("a\nb\n".to_string())
 7997    );
 7998}
 7999
 8000#[gpui::test]
 8001async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 8002    init_test(cx, |_| {});
 8003
 8004    let fs = FakeFs::new(cx.executor());
 8005    fs.insert_file(
 8006        path!("/file.txt"),
 8007        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 8008    )
 8009    .await;
 8010
 8011    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 8012
 8013    let buffer = project
 8014        .update(cx, |project, cx| {
 8015            project.open_local_buffer(path!("/file.txt"), cx)
 8016        })
 8017        .await
 8018        .unwrap();
 8019
 8020    let multibuffer = cx.new(|cx| {
 8021        let mut multibuffer = MultiBuffer::new(ReadWrite);
 8022        multibuffer.push_excerpts(
 8023            buffer.clone(),
 8024            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 8025            cx,
 8026        );
 8027        multibuffer
 8028    });
 8029
 8030    let (editor, cx) = cx.add_window_view(|window, cx| {
 8031        build_editor_with_project(project.clone(), multibuffer, window, cx)
 8032    });
 8033
 8034    editor.update_in(cx, |editor, window, cx| {
 8035        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 8036
 8037        editor.select_all(&SelectAll, window, cx);
 8038        editor.copy(&Copy, window, cx);
 8039    });
 8040
 8041    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 8042        .read_from_clipboard()
 8043        .and_then(|item| item.entries().first().cloned())
 8044        .and_then(|entry| match entry {
 8045            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8046            _ => None,
 8047        });
 8048
 8049    let selections = clipboard_selections.expect("should have clipboard selections");
 8050    assert_eq!(selections.len(), 1);
 8051    let selection = &selections[0];
 8052    assert_eq!(
 8053        selection.line_range,
 8054        Some(2..=5),
 8055        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 8056    );
 8057}
 8058
 8059#[gpui::test]
 8060async fn test_paste_multiline(cx: &mut TestAppContext) {
 8061    init_test(cx, |_| {});
 8062
 8063    let mut cx = EditorTestContext::new(cx).await;
 8064    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8065
 8066    // Cut an indented block, without the leading whitespace.
 8067    cx.set_state(indoc! {"
 8068        const a: B = (
 8069            c(),
 8070            «d(
 8071                e,
 8072                f
 8073            )ˇ»
 8074        );
 8075    "});
 8076    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8077    cx.assert_editor_state(indoc! {"
 8078        const a: B = (
 8079            c(),
 8080            ˇ
 8081        );
 8082    "});
 8083
 8084    // Paste it at the same position.
 8085    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8086    cx.assert_editor_state(indoc! {"
 8087        const a: B = (
 8088            c(),
 8089            d(
 8090                e,
 8091                f
 8092 8093        );
 8094    "});
 8095
 8096    // Paste it at a line with a lower indent level.
 8097    cx.set_state(indoc! {"
 8098        ˇ
 8099        const a: B = (
 8100            c(),
 8101        );
 8102    "});
 8103    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8104    cx.assert_editor_state(indoc! {"
 8105        d(
 8106            e,
 8107            f
 8108 8109        const a: B = (
 8110            c(),
 8111        );
 8112    "});
 8113
 8114    // Cut an indented block, with the leading whitespace.
 8115    cx.set_state(indoc! {"
 8116        const a: B = (
 8117            c(),
 8118        «    d(
 8119                e,
 8120                f
 8121            )
 8122        ˇ»);
 8123    "});
 8124    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8125    cx.assert_editor_state(indoc! {"
 8126        const a: B = (
 8127            c(),
 8128        ˇ);
 8129    "});
 8130
 8131    // Paste it at the same position.
 8132    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8133    cx.assert_editor_state(indoc! {"
 8134        const a: B = (
 8135            c(),
 8136            d(
 8137                e,
 8138                f
 8139            )
 8140        ˇ);
 8141    "});
 8142
 8143    // Paste it at a line with a higher indent level.
 8144    cx.set_state(indoc! {"
 8145        const a: B = (
 8146            c(),
 8147            d(
 8148                e,
 8149 8150            )
 8151        );
 8152    "});
 8153    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8154    cx.assert_editor_state(indoc! {"
 8155        const a: B = (
 8156            c(),
 8157            d(
 8158                e,
 8159                f    d(
 8160                    e,
 8161                    f
 8162                )
 8163        ˇ
 8164            )
 8165        );
 8166    "});
 8167
 8168    // Copy an indented block, starting mid-line
 8169    cx.set_state(indoc! {"
 8170        const a: B = (
 8171            c(),
 8172            somethin«g(
 8173                e,
 8174                f
 8175            )ˇ»
 8176        );
 8177    "});
 8178    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8179
 8180    // Paste it on a line with a lower indent level
 8181    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 8182    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8183    cx.assert_editor_state(indoc! {"
 8184        const a: B = (
 8185            c(),
 8186            something(
 8187                e,
 8188                f
 8189            )
 8190        );
 8191        g(
 8192            e,
 8193            f
 8194"});
 8195}
 8196
 8197#[gpui::test]
 8198async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 8199    init_test(cx, |_| {});
 8200
 8201    cx.write_to_clipboard(ClipboardItem::new_string(
 8202        "    d(\n        e\n    );\n".into(),
 8203    ));
 8204
 8205    let mut cx = EditorTestContext::new(cx).await;
 8206    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8207    cx.run_until_parked();
 8208
 8209    cx.set_state(indoc! {"
 8210        fn a() {
 8211            b();
 8212            if c() {
 8213                ˇ
 8214            }
 8215        }
 8216    "});
 8217
 8218    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8219    cx.assert_editor_state(indoc! {"
 8220        fn a() {
 8221            b();
 8222            if c() {
 8223                d(
 8224                    e
 8225                );
 8226        ˇ
 8227            }
 8228        }
 8229    "});
 8230
 8231    cx.set_state(indoc! {"
 8232        fn a() {
 8233            b();
 8234            ˇ
 8235        }
 8236    "});
 8237
 8238    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8239    cx.assert_editor_state(indoc! {"
 8240        fn a() {
 8241            b();
 8242            d(
 8243                e
 8244            );
 8245        ˇ
 8246        }
 8247    "});
 8248}
 8249
 8250#[gpui::test]
 8251fn test_select_all(cx: &mut TestAppContext) {
 8252    init_test(cx, |_| {});
 8253
 8254    let editor = cx.add_window(|window, cx| {
 8255        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 8256        build_editor(buffer, window, cx)
 8257    });
 8258    _ = editor.update(cx, |editor, window, cx| {
 8259        editor.select_all(&SelectAll, window, cx);
 8260        assert_eq!(
 8261            display_ranges(editor, cx),
 8262            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 8263        );
 8264    });
 8265}
 8266
 8267#[gpui::test]
 8268fn test_select_line(cx: &mut TestAppContext) {
 8269    init_test(cx, |_| {});
 8270
 8271    let editor = cx.add_window(|window, cx| {
 8272        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 8273        build_editor(buffer, window, cx)
 8274    });
 8275    _ = editor.update(cx, |editor, window, cx| {
 8276        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8277            s.select_display_ranges([
 8278                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8279                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8280                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8281                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 8282            ])
 8283        });
 8284        editor.select_line(&SelectLine, window, cx);
 8285        // Adjacent line selections should NOT merge (only overlapping ones do)
 8286        assert_eq!(
 8287            display_ranges(editor, cx),
 8288            vec![
 8289                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8290                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 8291                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 8292            ]
 8293        );
 8294    });
 8295
 8296    _ = editor.update(cx, |editor, window, cx| {
 8297        editor.select_line(&SelectLine, window, cx);
 8298        assert_eq!(
 8299            display_ranges(editor, cx),
 8300            vec![
 8301                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 8302                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 8303            ]
 8304        );
 8305    });
 8306
 8307    _ = editor.update(cx, |editor, window, cx| {
 8308        editor.select_line(&SelectLine, window, cx);
 8309        // Adjacent but not overlapping, so they stay separate
 8310        assert_eq!(
 8311            display_ranges(editor, cx),
 8312            vec![
 8313                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 8314                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 8315            ]
 8316        );
 8317    });
 8318}
 8319
 8320#[gpui::test]
 8321async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 8322    init_test(cx, |_| {});
 8323    let mut cx = EditorTestContext::new(cx).await;
 8324
 8325    #[track_caller]
 8326    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 8327        cx.set_state(initial_state);
 8328        cx.update_editor(|e, window, cx| {
 8329            e.split_selection_into_lines(&Default::default(), window, cx)
 8330        });
 8331        cx.assert_editor_state(expected_state);
 8332    }
 8333
 8334    // Selection starts and ends at the middle of lines, left-to-right
 8335    test(
 8336        &mut cx,
 8337        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 8338        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 8339    );
 8340    // Same thing, right-to-left
 8341    test(
 8342        &mut cx,
 8343        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 8344        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 8345    );
 8346
 8347    // Whole buffer, left-to-right, last line *doesn't* end with newline
 8348    test(
 8349        &mut cx,
 8350        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 8351        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8352    );
 8353    // Same thing, right-to-left
 8354    test(
 8355        &mut cx,
 8356        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 8357        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8358    );
 8359
 8360    // Whole buffer, left-to-right, last line ends with newline
 8361    test(
 8362        &mut cx,
 8363        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 8364        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8365    );
 8366    // Same thing, right-to-left
 8367    test(
 8368        &mut cx,
 8369        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8370        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8371    );
 8372
 8373    // Starts at the end of a line, ends at the start of another
 8374    test(
 8375        &mut cx,
 8376        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8377        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8378    );
 8379}
 8380
 8381#[gpui::test]
 8382async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8383    init_test(cx, |_| {});
 8384
 8385    let editor = cx.add_window(|window, cx| {
 8386        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8387        build_editor(buffer, window, cx)
 8388    });
 8389
 8390    // setup
 8391    _ = editor.update(cx, |editor, window, cx| {
 8392        editor.fold_creases(
 8393            vec![
 8394                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8395                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8396                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8397            ],
 8398            true,
 8399            window,
 8400            cx,
 8401        );
 8402        assert_eq!(
 8403            editor.display_text(cx),
 8404            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8405        );
 8406    });
 8407
 8408    _ = editor.update(cx, |editor, window, cx| {
 8409        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8410            s.select_display_ranges([
 8411                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8412                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8413                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8414                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8415            ])
 8416        });
 8417        editor.split_selection_into_lines(&Default::default(), window, cx);
 8418        assert_eq!(
 8419            editor.display_text(cx),
 8420            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8421        );
 8422    });
 8423    EditorTestContext::for_editor(editor, cx)
 8424        .await
 8425        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8426
 8427    _ = editor.update(cx, |editor, window, cx| {
 8428        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8429            s.select_display_ranges([
 8430                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8431            ])
 8432        });
 8433        editor.split_selection_into_lines(&Default::default(), window, cx);
 8434        assert_eq!(
 8435            editor.display_text(cx),
 8436            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8437        );
 8438        assert_eq!(
 8439            display_ranges(editor, cx),
 8440            [
 8441                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8442                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8443                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8444                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8445                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8446                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8447                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8448            ]
 8449        );
 8450    });
 8451    EditorTestContext::for_editor(editor, cx)
 8452        .await
 8453        .assert_editor_state(
 8454            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8455        );
 8456}
 8457
 8458#[gpui::test]
 8459async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8460    init_test(cx, |_| {});
 8461
 8462    let mut cx = EditorTestContext::new(cx).await;
 8463
 8464    cx.set_state(indoc!(
 8465        r#"abc
 8466           defˇghi
 8467
 8468           jk
 8469           nlmo
 8470           "#
 8471    ));
 8472
 8473    cx.update_editor(|editor, window, cx| {
 8474        editor.add_selection_above(&Default::default(), window, cx);
 8475    });
 8476
 8477    cx.assert_editor_state(indoc!(
 8478        r#"abcˇ
 8479           defˇghi
 8480
 8481           jk
 8482           nlmo
 8483           "#
 8484    ));
 8485
 8486    cx.update_editor(|editor, window, cx| {
 8487        editor.add_selection_above(&Default::default(), window, cx);
 8488    });
 8489
 8490    cx.assert_editor_state(indoc!(
 8491        r#"abcˇ
 8492            defˇghi
 8493
 8494            jk
 8495            nlmo
 8496            "#
 8497    ));
 8498
 8499    cx.update_editor(|editor, window, cx| {
 8500        editor.add_selection_below(&Default::default(), window, cx);
 8501    });
 8502
 8503    cx.assert_editor_state(indoc!(
 8504        r#"abc
 8505           defˇghi
 8506
 8507           jk
 8508           nlmo
 8509           "#
 8510    ));
 8511
 8512    cx.update_editor(|editor, window, cx| {
 8513        editor.undo_selection(&Default::default(), window, cx);
 8514    });
 8515
 8516    cx.assert_editor_state(indoc!(
 8517        r#"abcˇ
 8518           defˇghi
 8519
 8520           jk
 8521           nlmo
 8522           "#
 8523    ));
 8524
 8525    cx.update_editor(|editor, window, cx| {
 8526        editor.redo_selection(&Default::default(), window, cx);
 8527    });
 8528
 8529    cx.assert_editor_state(indoc!(
 8530        r#"abc
 8531           defˇghi
 8532
 8533           jk
 8534           nlmo
 8535           "#
 8536    ));
 8537
 8538    cx.update_editor(|editor, window, cx| {
 8539        editor.add_selection_below(&Default::default(), window, cx);
 8540    });
 8541
 8542    cx.assert_editor_state(indoc!(
 8543        r#"abc
 8544           defˇghi
 8545           ˇ
 8546           jk
 8547           nlmo
 8548           "#
 8549    ));
 8550
 8551    cx.update_editor(|editor, window, cx| {
 8552        editor.add_selection_below(&Default::default(), window, cx);
 8553    });
 8554
 8555    cx.assert_editor_state(indoc!(
 8556        r#"abc
 8557           defˇghi
 8558           ˇ
 8559           jkˇ
 8560           nlmo
 8561           "#
 8562    ));
 8563
 8564    cx.update_editor(|editor, window, cx| {
 8565        editor.add_selection_below(&Default::default(), window, cx);
 8566    });
 8567
 8568    cx.assert_editor_state(indoc!(
 8569        r#"abc
 8570           defˇghi
 8571           ˇ
 8572           jkˇ
 8573           nlmˇo
 8574           "#
 8575    ));
 8576
 8577    cx.update_editor(|editor, window, cx| {
 8578        editor.add_selection_below(&Default::default(), window, cx);
 8579    });
 8580
 8581    cx.assert_editor_state(indoc!(
 8582        r#"abc
 8583           defˇghi
 8584           ˇ
 8585           jkˇ
 8586           nlmˇo
 8587           ˇ"#
 8588    ));
 8589
 8590    // change selections
 8591    cx.set_state(indoc!(
 8592        r#"abc
 8593           def«ˇg»hi
 8594
 8595           jk
 8596           nlmo
 8597           "#
 8598    ));
 8599
 8600    cx.update_editor(|editor, window, cx| {
 8601        editor.add_selection_below(&Default::default(), window, cx);
 8602    });
 8603
 8604    cx.assert_editor_state(indoc!(
 8605        r#"abc
 8606           def«ˇg»hi
 8607
 8608           jk
 8609           nlm«ˇo»
 8610           "#
 8611    ));
 8612
 8613    cx.update_editor(|editor, window, cx| {
 8614        editor.add_selection_below(&Default::default(), window, cx);
 8615    });
 8616
 8617    cx.assert_editor_state(indoc!(
 8618        r#"abc
 8619           def«ˇg»hi
 8620
 8621           jk
 8622           nlm«ˇo»
 8623           "#
 8624    ));
 8625
 8626    cx.update_editor(|editor, window, cx| {
 8627        editor.add_selection_above(&Default::default(), window, cx);
 8628    });
 8629
 8630    cx.assert_editor_state(indoc!(
 8631        r#"abc
 8632           def«ˇg»hi
 8633
 8634           jk
 8635           nlmo
 8636           "#
 8637    ));
 8638
 8639    cx.update_editor(|editor, window, cx| {
 8640        editor.add_selection_above(&Default::default(), window, cx);
 8641    });
 8642
 8643    cx.assert_editor_state(indoc!(
 8644        r#"abc
 8645           def«ˇg»hi
 8646
 8647           jk
 8648           nlmo
 8649           "#
 8650    ));
 8651
 8652    // Change selections again
 8653    cx.set_state(indoc!(
 8654        r#"a«bc
 8655           defgˇ»hi
 8656
 8657           jk
 8658           nlmo
 8659           "#
 8660    ));
 8661
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.add_selection_below(&Default::default(), window, cx);
 8664    });
 8665
 8666    cx.assert_editor_state(indoc!(
 8667        r#"a«bcˇ»
 8668           d«efgˇ»hi
 8669
 8670           j«kˇ»
 8671           nlmo
 8672           "#
 8673    ));
 8674
 8675    cx.update_editor(|editor, window, cx| {
 8676        editor.add_selection_below(&Default::default(), window, cx);
 8677    });
 8678    cx.assert_editor_state(indoc!(
 8679        r#"a«bcˇ»
 8680           d«efgˇ»hi
 8681
 8682           j«kˇ»
 8683           n«lmoˇ»
 8684           "#
 8685    ));
 8686    cx.update_editor(|editor, window, cx| {
 8687        editor.add_selection_above(&Default::default(), window, cx);
 8688    });
 8689
 8690    cx.assert_editor_state(indoc!(
 8691        r#"a«bcˇ»
 8692           d«efgˇ»hi
 8693
 8694           j«kˇ»
 8695           nlmo
 8696           "#
 8697    ));
 8698
 8699    // Change selections again
 8700    cx.set_state(indoc!(
 8701        r#"abc
 8702           d«ˇefghi
 8703
 8704           jk
 8705           nlm»o
 8706           "#
 8707    ));
 8708
 8709    cx.update_editor(|editor, window, cx| {
 8710        editor.add_selection_above(&Default::default(), window, cx);
 8711    });
 8712
 8713    cx.assert_editor_state(indoc!(
 8714        r#"a«ˇbc»
 8715           d«ˇef»ghi
 8716
 8717           j«ˇk»
 8718           n«ˇlm»o
 8719           "#
 8720    ));
 8721
 8722    cx.update_editor(|editor, window, cx| {
 8723        editor.add_selection_below(&Default::default(), window, cx);
 8724    });
 8725
 8726    cx.assert_editor_state(indoc!(
 8727        r#"abc
 8728           d«ˇef»ghi
 8729
 8730           j«ˇk»
 8731           n«ˇlm»o
 8732           "#
 8733    ));
 8734
 8735    // Assert that the oldest selection's goal column is used when adding more
 8736    // selections, not the most recently added selection's actual column.
 8737    cx.set_state(indoc! {"
 8738        foo bar bazˇ
 8739        foo
 8740        foo bar
 8741    "});
 8742
 8743    cx.update_editor(|editor, window, cx| {
 8744        editor.add_selection_below(
 8745            &AddSelectionBelow {
 8746                skip_soft_wrap: true,
 8747            },
 8748            window,
 8749            cx,
 8750        );
 8751    });
 8752
 8753    cx.assert_editor_state(indoc! {"
 8754        foo bar bazˇ
 8755        fooˇ
 8756        foo bar
 8757    "});
 8758
 8759    cx.update_editor(|editor, window, cx| {
 8760        editor.add_selection_below(
 8761            &AddSelectionBelow {
 8762                skip_soft_wrap: true,
 8763            },
 8764            window,
 8765            cx,
 8766        );
 8767    });
 8768
 8769    cx.assert_editor_state(indoc! {"
 8770        foo bar bazˇ
 8771        fooˇ
 8772        foo barˇ
 8773    "});
 8774
 8775    cx.set_state(indoc! {"
 8776        foo bar baz
 8777        foo
 8778        foo barˇ
 8779    "});
 8780
 8781    cx.update_editor(|editor, window, cx| {
 8782        editor.add_selection_above(
 8783            &AddSelectionAbove {
 8784                skip_soft_wrap: true,
 8785            },
 8786            window,
 8787            cx,
 8788        );
 8789    });
 8790
 8791    cx.assert_editor_state(indoc! {"
 8792        foo bar baz
 8793        fooˇ
 8794        foo barˇ
 8795    "});
 8796
 8797    cx.update_editor(|editor, window, cx| {
 8798        editor.add_selection_above(
 8799            &AddSelectionAbove {
 8800                skip_soft_wrap: true,
 8801            },
 8802            window,
 8803            cx,
 8804        );
 8805    });
 8806
 8807    cx.assert_editor_state(indoc! {"
 8808        foo barˇ baz
 8809        fooˇ
 8810        foo barˇ
 8811    "});
 8812}
 8813
 8814#[gpui::test]
 8815async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8816    init_test(cx, |_| {});
 8817    let mut cx = EditorTestContext::new(cx).await;
 8818
 8819    cx.set_state(indoc!(
 8820        r#"line onˇe
 8821           liˇne two
 8822           line three
 8823           line four"#
 8824    ));
 8825
 8826    cx.update_editor(|editor, window, cx| {
 8827        editor.add_selection_below(&Default::default(), window, cx);
 8828    });
 8829
 8830    // test multiple cursors expand in the same direction
 8831    cx.assert_editor_state(indoc!(
 8832        r#"line onˇe
 8833           liˇne twˇo
 8834           liˇne three
 8835           line four"#
 8836    ));
 8837
 8838    cx.update_editor(|editor, window, cx| {
 8839        editor.add_selection_below(&Default::default(), window, cx);
 8840    });
 8841
 8842    cx.update_editor(|editor, window, cx| {
 8843        editor.add_selection_below(&Default::default(), window, cx);
 8844    });
 8845
 8846    // test multiple cursors expand below overflow
 8847    cx.assert_editor_state(indoc!(
 8848        r#"line onˇe
 8849           liˇne twˇo
 8850           liˇne thˇree
 8851           liˇne foˇur"#
 8852    ));
 8853
 8854    cx.update_editor(|editor, window, cx| {
 8855        editor.add_selection_above(&Default::default(), window, cx);
 8856    });
 8857
 8858    // test multiple cursors retrieves back correctly
 8859    cx.assert_editor_state(indoc!(
 8860        r#"line onˇe
 8861           liˇne twˇo
 8862           liˇne thˇree
 8863           line four"#
 8864    ));
 8865
 8866    cx.update_editor(|editor, window, cx| {
 8867        editor.add_selection_above(&Default::default(), window, cx);
 8868    });
 8869
 8870    cx.update_editor(|editor, window, cx| {
 8871        editor.add_selection_above(&Default::default(), window, cx);
 8872    });
 8873
 8874    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8875    cx.assert_editor_state(indoc!(
 8876        r#"liˇne onˇe
 8877           liˇne two
 8878           line three
 8879           line four"#
 8880    ));
 8881
 8882    cx.update_editor(|editor, window, cx| {
 8883        editor.undo_selection(&Default::default(), window, cx);
 8884    });
 8885
 8886    // test undo
 8887    cx.assert_editor_state(indoc!(
 8888        r#"line onˇe
 8889           liˇne twˇo
 8890           line three
 8891           line four"#
 8892    ));
 8893
 8894    cx.update_editor(|editor, window, cx| {
 8895        editor.redo_selection(&Default::default(), window, cx);
 8896    });
 8897
 8898    // test redo
 8899    cx.assert_editor_state(indoc!(
 8900        r#"liˇne onˇe
 8901           liˇne two
 8902           line three
 8903           line four"#
 8904    ));
 8905
 8906    cx.set_state(indoc!(
 8907        r#"abcd
 8908           ef«ghˇ»
 8909           ijkl
 8910           «mˇ»nop"#
 8911    ));
 8912
 8913    cx.update_editor(|editor, window, cx| {
 8914        editor.add_selection_above(&Default::default(), window, cx);
 8915    });
 8916
 8917    // test multiple selections expand in the same direction
 8918    cx.assert_editor_state(indoc!(
 8919        r#"ab«cdˇ»
 8920           ef«ghˇ»
 8921           «iˇ»jkl
 8922           «mˇ»nop"#
 8923    ));
 8924
 8925    cx.update_editor(|editor, window, cx| {
 8926        editor.add_selection_above(&Default::default(), window, cx);
 8927    });
 8928
 8929    // test multiple selection upward overflow
 8930    cx.assert_editor_state(indoc!(
 8931        r#"ab«cdˇ»
 8932           «eˇ»f«ghˇ»
 8933           «iˇ»jkl
 8934           «mˇ»nop"#
 8935    ));
 8936
 8937    cx.update_editor(|editor, window, cx| {
 8938        editor.add_selection_below(&Default::default(), window, cx);
 8939    });
 8940
 8941    // test multiple selection retrieves back correctly
 8942    cx.assert_editor_state(indoc!(
 8943        r#"abcd
 8944           ef«ghˇ»
 8945           «iˇ»jkl
 8946           «mˇ»nop"#
 8947    ));
 8948
 8949    cx.update_editor(|editor, window, cx| {
 8950        editor.add_selection_below(&Default::default(), window, cx);
 8951    });
 8952
 8953    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8954    cx.assert_editor_state(indoc!(
 8955        r#"abcd
 8956           ef«ghˇ»
 8957           ij«klˇ»
 8958           «mˇ»nop"#
 8959    ));
 8960
 8961    cx.update_editor(|editor, window, cx| {
 8962        editor.undo_selection(&Default::default(), window, cx);
 8963    });
 8964
 8965    // test undo
 8966    cx.assert_editor_state(indoc!(
 8967        r#"abcd
 8968           ef«ghˇ»
 8969           «iˇ»jkl
 8970           «mˇ»nop"#
 8971    ));
 8972
 8973    cx.update_editor(|editor, window, cx| {
 8974        editor.redo_selection(&Default::default(), window, cx);
 8975    });
 8976
 8977    // test redo
 8978    cx.assert_editor_state(indoc!(
 8979        r#"abcd
 8980           ef«ghˇ»
 8981           ij«klˇ»
 8982           «mˇ»nop"#
 8983    ));
 8984}
 8985
 8986#[gpui::test]
 8987async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8988    init_test(cx, |_| {});
 8989    let mut cx = EditorTestContext::new(cx).await;
 8990
 8991    cx.set_state(indoc!(
 8992        r#"line onˇe
 8993           liˇne two
 8994           line three
 8995           line four"#
 8996    ));
 8997
 8998    cx.update_editor(|editor, window, cx| {
 8999        editor.add_selection_below(&Default::default(), window, cx);
 9000        editor.add_selection_below(&Default::default(), window, cx);
 9001        editor.add_selection_below(&Default::default(), window, cx);
 9002    });
 9003
 9004    // initial state with two multi cursor groups
 9005    cx.assert_editor_state(indoc!(
 9006        r#"line onˇe
 9007           liˇne twˇo
 9008           liˇne thˇree
 9009           liˇne foˇur"#
 9010    ));
 9011
 9012    // add single cursor in middle - simulate opt click
 9013    cx.update_editor(|editor, window, cx| {
 9014        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 9015        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9016        editor.end_selection(window, cx);
 9017    });
 9018
 9019    cx.assert_editor_state(indoc!(
 9020        r#"line onˇe
 9021           liˇne twˇo
 9022           liˇneˇ thˇree
 9023           liˇne foˇur"#
 9024    ));
 9025
 9026    cx.update_editor(|editor, window, cx| {
 9027        editor.add_selection_above(&Default::default(), window, cx);
 9028    });
 9029
 9030    // test new added selection expands above and existing selection shrinks
 9031    cx.assert_editor_state(indoc!(
 9032        r#"line onˇe
 9033           liˇneˇ twˇo
 9034           liˇneˇ thˇree
 9035           line four"#
 9036    ));
 9037
 9038    cx.update_editor(|editor, window, cx| {
 9039        editor.add_selection_above(&Default::default(), window, cx);
 9040    });
 9041
 9042    // test new added selection expands above and existing selection shrinks
 9043    cx.assert_editor_state(indoc!(
 9044        r#"lineˇ onˇe
 9045           liˇneˇ twˇo
 9046           lineˇ three
 9047           line four"#
 9048    ));
 9049
 9050    // intial state with two selection groups
 9051    cx.set_state(indoc!(
 9052        r#"abcd
 9053           ef«ghˇ»
 9054           ijkl
 9055           «mˇ»nop"#
 9056    ));
 9057
 9058    cx.update_editor(|editor, window, cx| {
 9059        editor.add_selection_above(&Default::default(), window, cx);
 9060        editor.add_selection_above(&Default::default(), window, cx);
 9061    });
 9062
 9063    cx.assert_editor_state(indoc!(
 9064        r#"ab«cdˇ»
 9065           «eˇ»f«ghˇ»
 9066           «iˇ»jkl
 9067           «mˇ»nop"#
 9068    ));
 9069
 9070    // add single selection in middle - simulate opt drag
 9071    cx.update_editor(|editor, window, cx| {
 9072        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 9073        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9074        editor.update_selection(
 9075            DisplayPoint::new(DisplayRow(2), 4),
 9076            0,
 9077            gpui::Point::<f32>::default(),
 9078            window,
 9079            cx,
 9080        );
 9081        editor.end_selection(window, cx);
 9082    });
 9083
 9084    cx.assert_editor_state(indoc!(
 9085        r#"ab«cdˇ»
 9086           «eˇ»f«ghˇ»
 9087           «iˇ»jk«lˇ»
 9088           «mˇ»nop"#
 9089    ));
 9090
 9091    cx.update_editor(|editor, window, cx| {
 9092        editor.add_selection_below(&Default::default(), window, cx);
 9093    });
 9094
 9095    // test new added selection expands below, others shrinks from above
 9096    cx.assert_editor_state(indoc!(
 9097        r#"abcd
 9098           ef«ghˇ»
 9099           «iˇ»jk«lˇ»
 9100           «mˇ»no«pˇ»"#
 9101    ));
 9102}
 9103
 9104#[gpui::test]
 9105async fn test_select_next(cx: &mut TestAppContext) {
 9106    init_test(cx, |_| {});
 9107    let mut cx = EditorTestContext::new(cx).await;
 9108
 9109    // Enable case sensitive search.
 9110    update_test_editor_settings(&mut cx, |settings| {
 9111        let mut search_settings = SearchSettingsContent::default();
 9112        search_settings.case_sensitive = Some(true);
 9113        settings.search = Some(search_settings);
 9114    });
 9115
 9116    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9117
 9118    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9119        .unwrap();
 9120    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9121
 9122    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9123        .unwrap();
 9124    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9125
 9126    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9127    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9128
 9129    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9130    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9131
 9132    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9133        .unwrap();
 9134    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9135
 9136    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9137        .unwrap();
 9138    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9139
 9140    // Test selection direction should be preserved
 9141    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9142
 9143    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9144        .unwrap();
 9145    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 9146
 9147    // Test case sensitivity
 9148    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 9149    cx.update_editor(|e, window, cx| {
 9150        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9151    });
 9152    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9153
 9154    // Disable case sensitive search.
 9155    update_test_editor_settings(&mut cx, |settings| {
 9156        let mut search_settings = SearchSettingsContent::default();
 9157        search_settings.case_sensitive = Some(false);
 9158        settings.search = Some(search_settings);
 9159    });
 9160
 9161    cx.set_state("«ˇfoo»\nFOO\nFoo");
 9162    cx.update_editor(|e, window, cx| {
 9163        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9164        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9165    });
 9166    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9167}
 9168
 9169#[gpui::test]
 9170async fn test_select_all_matches(cx: &mut TestAppContext) {
 9171    init_test(cx, |_| {});
 9172    let mut cx = EditorTestContext::new(cx).await;
 9173
 9174    // Enable case sensitive search.
 9175    update_test_editor_settings(&mut cx, |settings| {
 9176        let mut search_settings = SearchSettingsContent::default();
 9177        search_settings.case_sensitive = Some(true);
 9178        settings.search = Some(search_settings);
 9179    });
 9180
 9181    // Test caret-only selections
 9182    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9183    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9184        .unwrap();
 9185    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9186
 9187    // Test left-to-right selections
 9188    cx.set_state("abc\n«abcˇ»\nabc");
 9189    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9190        .unwrap();
 9191    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 9192
 9193    // Test right-to-left selections
 9194    cx.set_state("abc\n«ˇabc»\nabc");
 9195    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9196        .unwrap();
 9197    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 9198
 9199    // Test selecting whitespace with caret selection
 9200    cx.set_state("abc\nˇ   abc\nabc");
 9201    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9202        .unwrap();
 9203    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 9204
 9205    // Test selecting whitespace with left-to-right selection
 9206    cx.set_state("abc\n«ˇ  »abc\nabc");
 9207    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9208        .unwrap();
 9209    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 9210
 9211    // Test no matches with right-to-left selection
 9212    cx.set_state("abc\n«  ˇ»abc\nabc");
 9213    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9214        .unwrap();
 9215    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 9216
 9217    // Test with a single word and clip_at_line_ends=true (#29823)
 9218    cx.set_state("aˇbc");
 9219    cx.update_editor(|e, window, cx| {
 9220        e.set_clip_at_line_ends(true, cx);
 9221        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9222        e.set_clip_at_line_ends(false, cx);
 9223    });
 9224    cx.assert_editor_state("«abcˇ»");
 9225
 9226    // Test case sensitivity
 9227    cx.set_state("fˇoo\nFOO\nFoo");
 9228    cx.update_editor(|e, window, cx| {
 9229        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9230    });
 9231    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 9232
 9233    // Disable case sensitive search.
 9234    update_test_editor_settings(&mut cx, |settings| {
 9235        let mut search_settings = SearchSettingsContent::default();
 9236        search_settings.case_sensitive = Some(false);
 9237        settings.search = Some(search_settings);
 9238    });
 9239
 9240    cx.set_state("fˇoo\nFOO\nFoo");
 9241    cx.update_editor(|e, window, cx| {
 9242        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9243    });
 9244    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 9245}
 9246
 9247#[gpui::test]
 9248async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 9249    init_test(cx, |_| {});
 9250
 9251    let mut cx = EditorTestContext::new(cx).await;
 9252
 9253    let large_body_1 = "\nd".repeat(200);
 9254    let large_body_2 = "\ne".repeat(200);
 9255
 9256    cx.set_state(&format!(
 9257        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 9258    ));
 9259    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 9260        let scroll_position = editor.scroll_position(cx);
 9261        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 9262        scroll_position
 9263    });
 9264
 9265    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9266        .unwrap();
 9267    cx.assert_editor_state(&format!(
 9268        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 9269    ));
 9270    let scroll_position_after_selection =
 9271        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 9272    assert_eq!(
 9273        initial_scroll_position, scroll_position_after_selection,
 9274        "Scroll position should not change after selecting all matches"
 9275    );
 9276}
 9277
 9278#[gpui::test]
 9279async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 9280    init_test(cx, |_| {});
 9281
 9282    let mut cx = EditorLspTestContext::new_rust(
 9283        lsp::ServerCapabilities {
 9284            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9285            ..Default::default()
 9286        },
 9287        cx,
 9288    )
 9289    .await;
 9290
 9291    cx.set_state(indoc! {"
 9292        line 1
 9293        line 2
 9294        linˇe 3
 9295        line 4
 9296        line 5
 9297    "});
 9298
 9299    // Make an edit
 9300    cx.update_editor(|editor, window, cx| {
 9301        editor.handle_input("X", window, cx);
 9302    });
 9303
 9304    // Move cursor to a different position
 9305    cx.update_editor(|editor, window, cx| {
 9306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9307            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 9308        });
 9309    });
 9310
 9311    cx.assert_editor_state(indoc! {"
 9312        line 1
 9313        line 2
 9314        linXe 3
 9315        line 4
 9316        liˇne 5
 9317    "});
 9318
 9319    cx.lsp
 9320        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 9321            Ok(Some(vec![lsp::TextEdit::new(
 9322                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9323                "PREFIX ".to_string(),
 9324            )]))
 9325        });
 9326
 9327    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 9328        .unwrap()
 9329        .await
 9330        .unwrap();
 9331
 9332    cx.assert_editor_state(indoc! {"
 9333        PREFIX line 1
 9334        line 2
 9335        linXe 3
 9336        line 4
 9337        liˇne 5
 9338    "});
 9339
 9340    // Undo formatting
 9341    cx.update_editor(|editor, window, cx| {
 9342        editor.undo(&Default::default(), window, cx);
 9343    });
 9344
 9345    // Verify cursor moved back to position after edit
 9346    cx.assert_editor_state(indoc! {"
 9347        line 1
 9348        line 2
 9349        linXˇe 3
 9350        line 4
 9351        line 5
 9352    "});
 9353}
 9354
 9355#[gpui::test]
 9356async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 9357    init_test(cx, |_| {});
 9358
 9359    let mut cx = EditorTestContext::new(cx).await;
 9360
 9361    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 9362    cx.update_editor(|editor, window, cx| {
 9363        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 9364    });
 9365
 9366    cx.set_state(indoc! {"
 9367        line 1
 9368        line 2
 9369        linˇe 3
 9370        line 4
 9371        line 5
 9372        line 6
 9373        line 7
 9374        line 8
 9375        line 9
 9376        line 10
 9377    "});
 9378
 9379    let snapshot = cx.buffer_snapshot();
 9380    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9381
 9382    cx.update(|_, cx| {
 9383        provider.update(cx, |provider, _| {
 9384            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9385                id: None,
 9386                edits: vec![(edit_position..edit_position, "X".into())],
 9387                cursor_position: None,
 9388                edit_preview: None,
 9389            }))
 9390        })
 9391    });
 9392
 9393    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9394    cx.update_editor(|editor, window, cx| {
 9395        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9396    });
 9397
 9398    cx.assert_editor_state(indoc! {"
 9399        line 1
 9400        line 2
 9401        lineXˇ 3
 9402        line 4
 9403        line 5
 9404        line 6
 9405        line 7
 9406        line 8
 9407        line 9
 9408        line 10
 9409    "});
 9410
 9411    cx.update_editor(|editor, window, cx| {
 9412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9413            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9414        });
 9415    });
 9416
 9417    cx.assert_editor_state(indoc! {"
 9418        line 1
 9419        line 2
 9420        lineX 3
 9421        line 4
 9422        line 5
 9423        line 6
 9424        line 7
 9425        line 8
 9426        line 9
 9427        liˇne 10
 9428    "});
 9429
 9430    cx.update_editor(|editor, window, cx| {
 9431        editor.undo(&Default::default(), window, cx);
 9432    });
 9433
 9434    cx.assert_editor_state(indoc! {"
 9435        line 1
 9436        line 2
 9437        lineˇ 3
 9438        line 4
 9439        line 5
 9440        line 6
 9441        line 7
 9442        line 8
 9443        line 9
 9444        line 10
 9445    "});
 9446}
 9447
 9448#[gpui::test]
 9449async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9450    init_test(cx, |_| {});
 9451
 9452    let mut cx = EditorTestContext::new(cx).await;
 9453    cx.set_state(
 9454        r#"let foo = 2;
 9455lˇet foo = 2;
 9456let fooˇ = 2;
 9457let foo = 2;
 9458let foo = ˇ2;"#,
 9459    );
 9460
 9461    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9462        .unwrap();
 9463    cx.assert_editor_state(
 9464        r#"let foo = 2;
 9465«letˇ» foo = 2;
 9466let «fooˇ» = 2;
 9467let foo = 2;
 9468let foo = «2ˇ»;"#,
 9469    );
 9470
 9471    // noop for multiple selections with different contents
 9472    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9473        .unwrap();
 9474    cx.assert_editor_state(
 9475        r#"let foo = 2;
 9476«letˇ» foo = 2;
 9477let «fooˇ» = 2;
 9478let foo = 2;
 9479let foo = «2ˇ»;"#,
 9480    );
 9481
 9482    // Test last selection direction should be preserved
 9483    cx.set_state(
 9484        r#"let foo = 2;
 9485let foo = 2;
 9486let «fooˇ» = 2;
 9487let «ˇfoo» = 2;
 9488let foo = 2;"#,
 9489    );
 9490
 9491    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9492        .unwrap();
 9493    cx.assert_editor_state(
 9494        r#"let foo = 2;
 9495let foo = 2;
 9496let «fooˇ» = 2;
 9497let «ˇfoo» = 2;
 9498let «ˇfoo» = 2;"#,
 9499    );
 9500}
 9501
 9502#[gpui::test]
 9503async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9504    init_test(cx, |_| {});
 9505
 9506    let mut cx =
 9507        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9508
 9509    cx.assert_editor_state(indoc! {"
 9510        ˇbbb
 9511        ccc
 9512
 9513        bbb
 9514        ccc
 9515        "});
 9516    cx.dispatch_action(SelectPrevious::default());
 9517    cx.assert_editor_state(indoc! {"
 9518                «bbbˇ»
 9519                ccc
 9520
 9521                bbb
 9522                ccc
 9523                "});
 9524    cx.dispatch_action(SelectPrevious::default());
 9525    cx.assert_editor_state(indoc! {"
 9526                «bbbˇ»
 9527                ccc
 9528
 9529                «bbbˇ»
 9530                ccc
 9531                "});
 9532}
 9533
 9534#[gpui::test]
 9535async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9536    init_test(cx, |_| {});
 9537
 9538    let mut cx = EditorTestContext::new(cx).await;
 9539    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9540
 9541    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9542        .unwrap();
 9543    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9544
 9545    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9546        .unwrap();
 9547    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9548
 9549    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9550    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9551
 9552    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9553    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9554
 9555    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9556        .unwrap();
 9557    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9558
 9559    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9560        .unwrap();
 9561    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9562}
 9563
 9564#[gpui::test]
 9565async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9566    init_test(cx, |_| {});
 9567
 9568    let mut cx = EditorTestContext::new(cx).await;
 9569    cx.set_state("");
 9570
 9571    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9572        .unwrap();
 9573    cx.assert_editor_state("«aˇ»");
 9574    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9575        .unwrap();
 9576    cx.assert_editor_state("«aˇ»");
 9577}
 9578
 9579#[gpui::test]
 9580async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9581    init_test(cx, |_| {});
 9582
 9583    let mut cx = EditorTestContext::new(cx).await;
 9584    cx.set_state(
 9585        r#"let foo = 2;
 9586lˇet foo = 2;
 9587let fooˇ = 2;
 9588let foo = 2;
 9589let foo = ˇ2;"#,
 9590    );
 9591
 9592    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9593        .unwrap();
 9594    cx.assert_editor_state(
 9595        r#"let foo = 2;
 9596«letˇ» foo = 2;
 9597let «fooˇ» = 2;
 9598let foo = 2;
 9599let foo = «2ˇ»;"#,
 9600    );
 9601
 9602    // noop for multiple selections with different contents
 9603    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9604        .unwrap();
 9605    cx.assert_editor_state(
 9606        r#"let foo = 2;
 9607«letˇ» foo = 2;
 9608let «fooˇ» = 2;
 9609let foo = 2;
 9610let foo = «2ˇ»;"#,
 9611    );
 9612}
 9613
 9614#[gpui::test]
 9615async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9616    init_test(cx, |_| {});
 9617    let mut cx = EditorTestContext::new(cx).await;
 9618
 9619    // Enable case sensitive search.
 9620    update_test_editor_settings(&mut cx, |settings| {
 9621        let mut search_settings = SearchSettingsContent::default();
 9622        search_settings.case_sensitive = Some(true);
 9623        settings.search = Some(search_settings);
 9624    });
 9625
 9626    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9627
 9628    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9629        .unwrap();
 9630    // selection direction is preserved
 9631    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9632
 9633    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9634        .unwrap();
 9635    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9636
 9637    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9638    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9639
 9640    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9641    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9642
 9643    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9644        .unwrap();
 9645    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9646
 9647    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9648        .unwrap();
 9649    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9650
 9651    // Test case sensitivity
 9652    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9653    cx.update_editor(|e, window, cx| {
 9654        e.select_previous(&SelectPrevious::default(), window, cx)
 9655            .unwrap();
 9656        e.select_previous(&SelectPrevious::default(), window, cx)
 9657            .unwrap();
 9658    });
 9659    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9660
 9661    // Disable case sensitive search.
 9662    update_test_editor_settings(&mut cx, |settings| {
 9663        let mut search_settings = SearchSettingsContent::default();
 9664        search_settings.case_sensitive = Some(false);
 9665        settings.search = Some(search_settings);
 9666    });
 9667
 9668    cx.set_state("foo\nFOO\n«ˇFoo»");
 9669    cx.update_editor(|e, window, cx| {
 9670        e.select_previous(&SelectPrevious::default(), window, cx)
 9671            .unwrap();
 9672        e.select_previous(&SelectPrevious::default(), window, cx)
 9673            .unwrap();
 9674    });
 9675    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9676}
 9677
 9678#[gpui::test]
 9679async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9680    init_test(cx, |_| {});
 9681
 9682    let language = Arc::new(Language::new(
 9683        LanguageConfig::default(),
 9684        Some(tree_sitter_rust::LANGUAGE.into()),
 9685    ));
 9686
 9687    let text = r#"
 9688        use mod1::mod2::{mod3, mod4};
 9689
 9690        fn fn_1(param1: bool, param2: &str) {
 9691            let var1 = "text";
 9692        }
 9693    "#
 9694    .unindent();
 9695
 9696    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9697    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9698    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9699
 9700    editor
 9701        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9702        .await;
 9703
 9704    editor.update_in(cx, |editor, window, cx| {
 9705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9706            s.select_display_ranges([
 9707                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9708                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9709                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9710            ]);
 9711        });
 9712        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9713    });
 9714    editor.update(cx, |editor, cx| {
 9715        assert_text_with_selections(
 9716            editor,
 9717            indoc! {r#"
 9718                use mod1::mod2::{mod3, «mod4ˇ»};
 9719
 9720                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9721                    let var1 = "«ˇtext»";
 9722                }
 9723            "#},
 9724            cx,
 9725        );
 9726    });
 9727
 9728    editor.update_in(cx, |editor, window, cx| {
 9729        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9730    });
 9731    editor.update(cx, |editor, cx| {
 9732        assert_text_with_selections(
 9733            editor,
 9734            indoc! {r#"
 9735                use mod1::mod2::«{mod3, mod4}ˇ»;
 9736
 9737                «ˇfn fn_1(param1: bool, param2: &str) {
 9738                    let var1 = "text";
 9739 9740            "#},
 9741            cx,
 9742        );
 9743    });
 9744
 9745    editor.update_in(cx, |editor, window, cx| {
 9746        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9747    });
 9748    assert_eq!(
 9749        editor.update(cx, |editor, cx| editor
 9750            .selections
 9751            .display_ranges(&editor.display_snapshot(cx))),
 9752        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9753    );
 9754
 9755    // Trying to expand the selected syntax node one more time has no effect.
 9756    editor.update_in(cx, |editor, window, cx| {
 9757        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9758    });
 9759    assert_eq!(
 9760        editor.update(cx, |editor, cx| editor
 9761            .selections
 9762            .display_ranges(&editor.display_snapshot(cx))),
 9763        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9764    );
 9765
 9766    editor.update_in(cx, |editor, window, cx| {
 9767        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9768    });
 9769    editor.update(cx, |editor, cx| {
 9770        assert_text_with_selections(
 9771            editor,
 9772            indoc! {r#"
 9773                use mod1::mod2::«{mod3, mod4}ˇ»;
 9774
 9775                «ˇfn fn_1(param1: bool, param2: &str) {
 9776                    let var1 = "text";
 9777 9778            "#},
 9779            cx,
 9780        );
 9781    });
 9782
 9783    editor.update_in(cx, |editor, window, cx| {
 9784        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9785    });
 9786    editor.update(cx, |editor, cx| {
 9787        assert_text_with_selections(
 9788            editor,
 9789            indoc! {r#"
 9790                use mod1::mod2::{mod3, «mod4ˇ»};
 9791
 9792                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9793                    let var1 = "«ˇtext»";
 9794                }
 9795            "#},
 9796            cx,
 9797        );
 9798    });
 9799
 9800    editor.update_in(cx, |editor, window, cx| {
 9801        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9802    });
 9803    editor.update(cx, |editor, cx| {
 9804        assert_text_with_selections(
 9805            editor,
 9806            indoc! {r#"
 9807                use mod1::mod2::{mod3, moˇd4};
 9808
 9809                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9810                    let var1 = "teˇxt";
 9811                }
 9812            "#},
 9813            cx,
 9814        );
 9815    });
 9816
 9817    // Trying to shrink the selected syntax node one more time has no effect.
 9818    editor.update_in(cx, |editor, window, cx| {
 9819        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9820    });
 9821    editor.update_in(cx, |editor, _, cx| {
 9822        assert_text_with_selections(
 9823            editor,
 9824            indoc! {r#"
 9825                use mod1::mod2::{mod3, moˇd4};
 9826
 9827                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9828                    let var1 = "teˇxt";
 9829                }
 9830            "#},
 9831            cx,
 9832        );
 9833    });
 9834
 9835    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9836    // a fold.
 9837    editor.update_in(cx, |editor, window, cx| {
 9838        editor.fold_creases(
 9839            vec![
 9840                Crease::simple(
 9841                    Point::new(0, 21)..Point::new(0, 24),
 9842                    FoldPlaceholder::test(),
 9843                ),
 9844                Crease::simple(
 9845                    Point::new(3, 20)..Point::new(3, 22),
 9846                    FoldPlaceholder::test(),
 9847                ),
 9848            ],
 9849            true,
 9850            window,
 9851            cx,
 9852        );
 9853        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9854    });
 9855    editor.update(cx, |editor, cx| {
 9856        assert_text_with_selections(
 9857            editor,
 9858            indoc! {r#"
 9859                use mod1::mod2::«{mod3, mod4}ˇ»;
 9860
 9861                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9862                    let var1 = "«ˇtext»";
 9863                }
 9864            "#},
 9865            cx,
 9866        );
 9867    });
 9868}
 9869
 9870#[gpui::test]
 9871async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9872    init_test(cx, |_| {});
 9873
 9874    let language = Arc::new(Language::new(
 9875        LanguageConfig::default(),
 9876        Some(tree_sitter_rust::LANGUAGE.into()),
 9877    ));
 9878
 9879    let text = "let a = 2;";
 9880
 9881    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9882    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9883    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9884
 9885    editor
 9886        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9887        .await;
 9888
 9889    // Test case 1: Cursor at end of word
 9890    editor.update_in(cx, |editor, window, cx| {
 9891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9892            s.select_display_ranges([
 9893                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9894            ]);
 9895        });
 9896    });
 9897    editor.update(cx, |editor, cx| {
 9898        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9899    });
 9900    editor.update_in(cx, |editor, window, cx| {
 9901        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9902    });
 9903    editor.update(cx, |editor, cx| {
 9904        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9905    });
 9906    editor.update_in(cx, |editor, window, cx| {
 9907        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9908    });
 9909    editor.update(cx, |editor, cx| {
 9910        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9911    });
 9912
 9913    // Test case 2: Cursor at end of statement
 9914    editor.update_in(cx, |editor, window, cx| {
 9915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9916            s.select_display_ranges([
 9917                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9918            ]);
 9919        });
 9920    });
 9921    editor.update(cx, |editor, cx| {
 9922        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9923    });
 9924    editor.update_in(cx, |editor, window, cx| {
 9925        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9926    });
 9927    editor.update(cx, |editor, cx| {
 9928        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9929    });
 9930}
 9931
 9932#[gpui::test]
 9933async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9934    init_test(cx, |_| {});
 9935
 9936    let language = Arc::new(Language::new(
 9937        LanguageConfig {
 9938            name: "JavaScript".into(),
 9939            ..Default::default()
 9940        },
 9941        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9942    ));
 9943
 9944    let text = r#"
 9945        let a = {
 9946            key: "value",
 9947        };
 9948    "#
 9949    .unindent();
 9950
 9951    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9952    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9953    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9954
 9955    editor
 9956        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9957        .await;
 9958
 9959    // Test case 1: Cursor after '{'
 9960    editor.update_in(cx, |editor, window, cx| {
 9961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9962            s.select_display_ranges([
 9963                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9964            ]);
 9965        });
 9966    });
 9967    editor.update(cx, |editor, cx| {
 9968        assert_text_with_selections(
 9969            editor,
 9970            indoc! {r#"
 9971                let a = {ˇ
 9972                    key: "value",
 9973                };
 9974            "#},
 9975            cx,
 9976        );
 9977    });
 9978    editor.update_in(cx, |editor, window, cx| {
 9979        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9980    });
 9981    editor.update(cx, |editor, cx| {
 9982        assert_text_with_selections(
 9983            editor,
 9984            indoc! {r#"
 9985                let a = «ˇ{
 9986                    key: "value",
 9987                }»;
 9988            "#},
 9989            cx,
 9990        );
 9991    });
 9992
 9993    // Test case 2: Cursor after ':'
 9994    editor.update_in(cx, |editor, window, cx| {
 9995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9996            s.select_display_ranges([
 9997                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9998            ]);
 9999        });
10000    });
10001    editor.update(cx, |editor, cx| {
10002        assert_text_with_selections(
10003            editor,
10004            indoc! {r#"
10005                let a = {
10006                    key:ˇ "value",
10007                };
10008            "#},
10009            cx,
10010        );
10011    });
10012    editor.update_in(cx, |editor, window, cx| {
10013        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10014    });
10015    editor.update(cx, |editor, cx| {
10016        assert_text_with_selections(
10017            editor,
10018            indoc! {r#"
10019                let a = {
10020                    «ˇkey: "value"»,
10021                };
10022            "#},
10023            cx,
10024        );
10025    });
10026    editor.update_in(cx, |editor, window, cx| {
10027        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10028    });
10029    editor.update(cx, |editor, cx| {
10030        assert_text_with_selections(
10031            editor,
10032            indoc! {r#"
10033                let a = «ˇ{
10034                    key: "value",
10035                }»;
10036            "#},
10037            cx,
10038        );
10039    });
10040
10041    // Test case 3: Cursor after ','
10042    editor.update_in(cx, |editor, window, cx| {
10043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10044            s.select_display_ranges([
10045                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
10046            ]);
10047        });
10048    });
10049    editor.update(cx, |editor, cx| {
10050        assert_text_with_selections(
10051            editor,
10052            indoc! {r#"
10053                let a = {
10054                    key: "value",ˇ
10055                };
10056            "#},
10057            cx,
10058        );
10059    });
10060    editor.update_in(cx, |editor, window, cx| {
10061        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10062    });
10063    editor.update(cx, |editor, cx| {
10064        assert_text_with_selections(
10065            editor,
10066            indoc! {r#"
10067                let a = «ˇ{
10068                    key: "value",
10069                }»;
10070            "#},
10071            cx,
10072        );
10073    });
10074
10075    // Test case 4: Cursor after ';'
10076    editor.update_in(cx, |editor, window, cx| {
10077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10078            s.select_display_ranges([
10079                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
10080            ]);
10081        });
10082    });
10083    editor.update(cx, |editor, cx| {
10084        assert_text_with_selections(
10085            editor,
10086            indoc! {r#"
10087                let a = {
10088                    key: "value",
10089                };ˇ
10090            "#},
10091            cx,
10092        );
10093    });
10094    editor.update_in(cx, |editor, window, cx| {
10095        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10096    });
10097    editor.update(cx, |editor, cx| {
10098        assert_text_with_selections(
10099            editor,
10100            indoc! {r#"
10101                «ˇlet a = {
10102                    key: "value",
10103                };
10104                »"#},
10105            cx,
10106        );
10107    });
10108}
10109
10110#[gpui::test]
10111async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
10112    init_test(cx, |_| {});
10113
10114    let language = Arc::new(Language::new(
10115        LanguageConfig::default(),
10116        Some(tree_sitter_rust::LANGUAGE.into()),
10117    ));
10118
10119    let text = r#"
10120        use mod1::mod2::{mod3, mod4};
10121
10122        fn fn_1(param1: bool, param2: &str) {
10123            let var1 = "hello world";
10124        }
10125    "#
10126    .unindent();
10127
10128    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10129    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10130    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10131
10132    editor
10133        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10134        .await;
10135
10136    // Test 1: Cursor on a letter of a string word
10137    editor.update_in(cx, |editor, window, cx| {
10138        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10139            s.select_display_ranges([
10140                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
10141            ]);
10142        });
10143    });
10144    editor.update_in(cx, |editor, window, cx| {
10145        assert_text_with_selections(
10146            editor,
10147            indoc! {r#"
10148                use mod1::mod2::{mod3, mod4};
10149
10150                fn fn_1(param1: bool, param2: &str) {
10151                    let var1 = "hˇello world";
10152                }
10153            "#},
10154            cx,
10155        );
10156        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10157        assert_text_with_selections(
10158            editor,
10159            indoc! {r#"
10160                use mod1::mod2::{mod3, mod4};
10161
10162                fn fn_1(param1: bool, param2: &str) {
10163                    let var1 = "«ˇhello» world";
10164                }
10165            "#},
10166            cx,
10167        );
10168    });
10169
10170    // Test 2: Partial selection within a word
10171    editor.update_in(cx, |editor, window, cx| {
10172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10173            s.select_display_ranges([
10174                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
10175            ]);
10176        });
10177    });
10178    editor.update_in(cx, |editor, window, cx| {
10179        assert_text_with_selections(
10180            editor,
10181            indoc! {r#"
10182                use mod1::mod2::{mod3, mod4};
10183
10184                fn fn_1(param1: bool, param2: &str) {
10185                    let var1 = "h«elˇ»lo world";
10186                }
10187            "#},
10188            cx,
10189        );
10190        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10191        assert_text_with_selections(
10192            editor,
10193            indoc! {r#"
10194                use mod1::mod2::{mod3, mod4};
10195
10196                fn fn_1(param1: bool, param2: &str) {
10197                    let var1 = "«ˇhello» world";
10198                }
10199            "#},
10200            cx,
10201        );
10202    });
10203
10204    // Test 3: Complete word already selected
10205    editor.update_in(cx, |editor, window, cx| {
10206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10207            s.select_display_ranges([
10208                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
10209            ]);
10210        });
10211    });
10212    editor.update_in(cx, |editor, window, cx| {
10213        assert_text_with_selections(
10214            editor,
10215            indoc! {r#"
10216                use mod1::mod2::{mod3, mod4};
10217
10218                fn fn_1(param1: bool, param2: &str) {
10219                    let var1 = "«helloˇ» world";
10220                }
10221            "#},
10222            cx,
10223        );
10224        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10225        assert_text_with_selections(
10226            editor,
10227            indoc! {r#"
10228                use mod1::mod2::{mod3, mod4};
10229
10230                fn fn_1(param1: bool, param2: &str) {
10231                    let var1 = "«hello worldˇ»";
10232                }
10233            "#},
10234            cx,
10235        );
10236    });
10237
10238    // Test 4: Selection spanning across words
10239    editor.update_in(cx, |editor, window, cx| {
10240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10241            s.select_display_ranges([
10242                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
10243            ]);
10244        });
10245    });
10246    editor.update_in(cx, |editor, window, cx| {
10247        assert_text_with_selections(
10248            editor,
10249            indoc! {r#"
10250                use mod1::mod2::{mod3, mod4};
10251
10252                fn fn_1(param1: bool, param2: &str) {
10253                    let var1 = "hel«lo woˇ»rld";
10254                }
10255            "#},
10256            cx,
10257        );
10258        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10259        assert_text_with_selections(
10260            editor,
10261            indoc! {r#"
10262                use mod1::mod2::{mod3, mod4};
10263
10264                fn fn_1(param1: bool, param2: &str) {
10265                    let var1 = "«ˇhello world»";
10266                }
10267            "#},
10268            cx,
10269        );
10270    });
10271
10272    // Test 5: Expansion beyond string
10273    editor.update_in(cx, |editor, window, cx| {
10274        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10275        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10276        assert_text_with_selections(
10277            editor,
10278            indoc! {r#"
10279                use mod1::mod2::{mod3, mod4};
10280
10281                fn fn_1(param1: bool, param2: &str) {
10282                    «ˇlet var1 = "hello world";»
10283                }
10284            "#},
10285            cx,
10286        );
10287    });
10288}
10289
10290#[gpui::test]
10291async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
10292    init_test(cx, |_| {});
10293
10294    let mut cx = EditorTestContext::new(cx).await;
10295
10296    let language = Arc::new(Language::new(
10297        LanguageConfig::default(),
10298        Some(tree_sitter_rust::LANGUAGE.into()),
10299    ));
10300
10301    cx.update_buffer(|buffer, cx| {
10302        buffer.set_language(Some(language), cx);
10303    });
10304
10305    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
10306    cx.update_editor(|editor, window, cx| {
10307        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10308    });
10309
10310    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
10311
10312    cx.set_state(indoc! { r#"fn a() {
10313          // what
10314          // a
10315          // ˇlong
10316          // method
10317          // I
10318          // sure
10319          // hope
10320          // it
10321          // works
10322    }"# });
10323
10324    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
10325    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
10326    cx.update(|_, cx| {
10327        multi_buffer.update(cx, |multi_buffer, cx| {
10328            multi_buffer.set_excerpts_for_path(
10329                PathKey::for_buffer(&buffer, cx),
10330                buffer,
10331                [Point::new(1, 0)..Point::new(1, 0)],
10332                3,
10333                cx,
10334            );
10335        });
10336    });
10337
10338    let editor2 = cx.new_window_entity(|window, cx| {
10339        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
10340    });
10341
10342    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
10343    cx.update_editor(|editor, window, cx| {
10344        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10345            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
10346        })
10347    });
10348
10349    cx.assert_editor_state(indoc! { "
10350        fn a() {
10351              // what
10352              // a
10353        ˇ      // long
10354              // method"});
10355
10356    cx.update_editor(|editor, window, cx| {
10357        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10358    });
10359
10360    // Although we could potentially make the action work when the syntax node
10361    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
10362    // did. Maybe we could also expand the excerpt to contain the range?
10363    cx.assert_editor_state(indoc! { "
10364        fn a() {
10365              // what
10366              // a
10367        ˇ      // long
10368              // method"});
10369}
10370
10371#[gpui::test]
10372async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10373    init_test(cx, |_| {});
10374
10375    let base_text = r#"
10376        impl A {
10377            // this is an uncommitted comment
10378
10379            fn b() {
10380                c();
10381            }
10382
10383            // this is another uncommitted comment
10384
10385            fn d() {
10386                // e
10387                // f
10388            }
10389        }
10390
10391        fn g() {
10392            // h
10393        }
10394    "#
10395    .unindent();
10396
10397    let text = r#"
10398        ˇimpl A {
10399
10400            fn b() {
10401                c();
10402            }
10403
10404            fn d() {
10405                // e
10406                // f
10407            }
10408        }
10409
10410        fn g() {
10411            // h
10412        }
10413    "#
10414    .unindent();
10415
10416    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10417    cx.set_state(&text);
10418    cx.set_head_text(&base_text);
10419    cx.update_editor(|editor, window, cx| {
10420        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10421    });
10422
10423    cx.assert_state_with_diff(
10424        "
10425        ˇimpl A {
10426      -     // this is an uncommitted comment
10427
10428            fn b() {
10429                c();
10430            }
10431
10432      -     // this is another uncommitted comment
10433      -
10434            fn d() {
10435                // e
10436                // f
10437            }
10438        }
10439
10440        fn g() {
10441            // h
10442        }
10443    "
10444        .unindent(),
10445    );
10446
10447    let expected_display_text = "
10448        impl A {
10449            // this is an uncommitted comment
10450
10451            fn b() {
1045210453            }
10454
10455            // this is another uncommitted comment
10456
10457            fn d() {
1045810459            }
10460        }
10461
10462        fn g() {
1046310464        }
10465        "
10466    .unindent();
10467
10468    cx.update_editor(|editor, window, cx| {
10469        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10470        assert_eq!(editor.display_text(cx), expected_display_text);
10471    });
10472}
10473
10474#[gpui::test]
10475async fn test_autoindent(cx: &mut TestAppContext) {
10476    init_test(cx, |_| {});
10477
10478    let language = Arc::new(
10479        Language::new(
10480            LanguageConfig {
10481                brackets: BracketPairConfig {
10482                    pairs: vec![
10483                        BracketPair {
10484                            start: "{".to_string(),
10485                            end: "}".to_string(),
10486                            close: false,
10487                            surround: false,
10488                            newline: true,
10489                        },
10490                        BracketPair {
10491                            start: "(".to_string(),
10492                            end: ")".to_string(),
10493                            close: false,
10494                            surround: false,
10495                            newline: true,
10496                        },
10497                    ],
10498                    ..Default::default()
10499                },
10500                ..Default::default()
10501            },
10502            Some(tree_sitter_rust::LANGUAGE.into()),
10503        )
10504        .with_indents_query(
10505            r#"
10506                (_ "(" ")" @end) @indent
10507                (_ "{" "}" @end) @indent
10508            "#,
10509        )
10510        .unwrap(),
10511    );
10512
10513    let text = "fn a() {}";
10514
10515    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10516    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10517    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10518    editor
10519        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10520        .await;
10521
10522    editor.update_in(cx, |editor, window, cx| {
10523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10524            s.select_ranges([
10525                MultiBufferOffset(5)..MultiBufferOffset(5),
10526                MultiBufferOffset(8)..MultiBufferOffset(8),
10527                MultiBufferOffset(9)..MultiBufferOffset(9),
10528            ])
10529        });
10530        editor.newline(&Newline, window, cx);
10531        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10532        assert_eq!(
10533            editor.selections.ranges(&editor.display_snapshot(cx)),
10534            &[
10535                Point::new(1, 4)..Point::new(1, 4),
10536                Point::new(3, 4)..Point::new(3, 4),
10537                Point::new(5, 0)..Point::new(5, 0)
10538            ]
10539        );
10540    });
10541}
10542
10543#[gpui::test]
10544async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10545    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10546
10547    let language = Arc::new(
10548        Language::new(
10549            LanguageConfig {
10550                brackets: BracketPairConfig {
10551                    pairs: vec![
10552                        BracketPair {
10553                            start: "{".to_string(),
10554                            end: "}".to_string(),
10555                            close: false,
10556                            surround: false,
10557                            newline: true,
10558                        },
10559                        BracketPair {
10560                            start: "(".to_string(),
10561                            end: ")".to_string(),
10562                            close: false,
10563                            surround: false,
10564                            newline: true,
10565                        },
10566                    ],
10567                    ..Default::default()
10568                },
10569                ..Default::default()
10570            },
10571            Some(tree_sitter_rust::LANGUAGE.into()),
10572        )
10573        .with_indents_query(
10574            r#"
10575                (_ "(" ")" @end) @indent
10576                (_ "{" "}" @end) @indent
10577            "#,
10578        )
10579        .unwrap(),
10580    );
10581
10582    let text = "fn a() {}";
10583
10584    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10585    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10586    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10587    editor
10588        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10589        .await;
10590
10591    editor.update_in(cx, |editor, window, cx| {
10592        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10593            s.select_ranges([
10594                MultiBufferOffset(5)..MultiBufferOffset(5),
10595                MultiBufferOffset(8)..MultiBufferOffset(8),
10596                MultiBufferOffset(9)..MultiBufferOffset(9),
10597            ])
10598        });
10599        editor.newline(&Newline, window, cx);
10600        assert_eq!(
10601            editor.text(cx),
10602            indoc!(
10603                "
10604                fn a(
10605
10606                ) {
10607
10608                }
10609                "
10610            )
10611        );
10612        assert_eq!(
10613            editor.selections.ranges(&editor.display_snapshot(cx)),
10614            &[
10615                Point::new(1, 0)..Point::new(1, 0),
10616                Point::new(3, 0)..Point::new(3, 0),
10617                Point::new(5, 0)..Point::new(5, 0)
10618            ]
10619        );
10620    });
10621}
10622
10623#[gpui::test]
10624async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10625    init_test(cx, |settings| {
10626        settings.defaults.auto_indent = Some(true);
10627        settings.languages.0.insert(
10628            "python".into(),
10629            LanguageSettingsContent {
10630                auto_indent: Some(false),
10631                ..Default::default()
10632            },
10633        );
10634    });
10635
10636    let mut cx = EditorTestContext::new(cx).await;
10637
10638    let injected_language = Arc::new(
10639        Language::new(
10640            LanguageConfig {
10641                brackets: BracketPairConfig {
10642                    pairs: vec![
10643                        BracketPair {
10644                            start: "{".to_string(),
10645                            end: "}".to_string(),
10646                            close: false,
10647                            surround: false,
10648                            newline: true,
10649                        },
10650                        BracketPair {
10651                            start: "(".to_string(),
10652                            end: ")".to_string(),
10653                            close: true,
10654                            surround: false,
10655                            newline: true,
10656                        },
10657                    ],
10658                    ..Default::default()
10659                },
10660                name: "python".into(),
10661                ..Default::default()
10662            },
10663            Some(tree_sitter_python::LANGUAGE.into()),
10664        )
10665        .with_indents_query(
10666            r#"
10667                (_ "(" ")" @end) @indent
10668                (_ "{" "}" @end) @indent
10669            "#,
10670        )
10671        .unwrap(),
10672    );
10673
10674    let language = Arc::new(
10675        Language::new(
10676            LanguageConfig {
10677                brackets: BracketPairConfig {
10678                    pairs: vec![
10679                        BracketPair {
10680                            start: "{".to_string(),
10681                            end: "}".to_string(),
10682                            close: false,
10683                            surround: false,
10684                            newline: true,
10685                        },
10686                        BracketPair {
10687                            start: "(".to_string(),
10688                            end: ")".to_string(),
10689                            close: true,
10690                            surround: false,
10691                            newline: true,
10692                        },
10693                    ],
10694                    ..Default::default()
10695                },
10696                name: LanguageName::new_static("rust"),
10697                ..Default::default()
10698            },
10699            Some(tree_sitter_rust::LANGUAGE.into()),
10700        )
10701        .with_indents_query(
10702            r#"
10703                (_ "(" ")" @end) @indent
10704                (_ "{" "}" @end) @indent
10705            "#,
10706        )
10707        .unwrap()
10708        .with_injection_query(
10709            r#"
10710            (macro_invocation
10711                macro: (identifier) @_macro_name
10712                (token_tree) @injection.content
10713                (#set! injection.language "python"))
10714           "#,
10715        )
10716        .unwrap(),
10717    );
10718
10719    cx.language_registry().add(injected_language);
10720    cx.language_registry().add(language.clone());
10721
10722    cx.update_buffer(|buffer, cx| {
10723        buffer.set_language(Some(language), cx);
10724    });
10725
10726    cx.set_state(r#"struct A {ˇ}"#);
10727
10728    cx.update_editor(|editor, window, cx| {
10729        editor.newline(&Default::default(), window, cx);
10730    });
10731
10732    cx.assert_editor_state(indoc!(
10733        "struct A {
10734            ˇ
10735        }"
10736    ));
10737
10738    cx.set_state(r#"select_biased!(ˇ)"#);
10739
10740    cx.update_editor(|editor, window, cx| {
10741        editor.newline(&Default::default(), window, cx);
10742        editor.handle_input("def ", window, cx);
10743        editor.handle_input("(", window, cx);
10744        editor.newline(&Default::default(), window, cx);
10745        editor.handle_input("a", window, cx);
10746    });
10747
10748    cx.assert_editor_state(indoc!(
10749        "select_biased!(
10750        def (
1075110752        )
10753        )"
10754    ));
10755}
10756
10757#[gpui::test]
10758async fn test_autoindent_selections(cx: &mut TestAppContext) {
10759    init_test(cx, |_| {});
10760
10761    {
10762        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10763        cx.set_state(indoc! {"
10764            impl A {
10765
10766                fn b() {}
10767
10768            «fn c() {
10769
10770            }ˇ»
10771            }
10772        "});
10773
10774        cx.update_editor(|editor, window, cx| {
10775            editor.autoindent(&Default::default(), window, cx);
10776        });
10777        cx.wait_for_autoindent_applied().await;
10778
10779        cx.assert_editor_state(indoc! {"
10780            impl A {
10781
10782                fn b() {}
10783
10784                «fn c() {
10785
10786                }ˇ»
10787            }
10788        "});
10789    }
10790
10791    {
10792        let mut cx = EditorTestContext::new_multibuffer(
10793            cx,
10794            [indoc! { "
10795                impl A {
10796                «
10797                // a
10798                fn b(){}
10799                »
10800                «
10801                    }
10802                    fn c(){}
10803                »
10804            "}],
10805        );
10806
10807        let buffer = cx.update_editor(|editor, _, cx| {
10808            let buffer = editor.buffer().update(cx, |buffer, _| {
10809                buffer.all_buffers().iter().next().unwrap().clone()
10810            });
10811            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10812            buffer
10813        });
10814
10815        cx.run_until_parked();
10816        cx.update_editor(|editor, window, cx| {
10817            editor.select_all(&Default::default(), window, cx);
10818            editor.autoindent(&Default::default(), window, cx)
10819        });
10820        cx.run_until_parked();
10821
10822        cx.update(|_, cx| {
10823            assert_eq!(
10824                buffer.read(cx).text(),
10825                indoc! { "
10826                    impl A {
10827
10828                        // a
10829                        fn b(){}
10830
10831
10832                    }
10833                    fn c(){}
10834
10835                " }
10836            )
10837        });
10838    }
10839}
10840
10841#[gpui::test]
10842async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10843    init_test(cx, |_| {});
10844
10845    let mut cx = EditorTestContext::new(cx).await;
10846
10847    let language = Arc::new(Language::new(
10848        LanguageConfig {
10849            brackets: BracketPairConfig {
10850                pairs: vec![
10851                    BracketPair {
10852                        start: "{".to_string(),
10853                        end: "}".to_string(),
10854                        close: true,
10855                        surround: true,
10856                        newline: true,
10857                    },
10858                    BracketPair {
10859                        start: "(".to_string(),
10860                        end: ")".to_string(),
10861                        close: true,
10862                        surround: true,
10863                        newline: true,
10864                    },
10865                    BracketPair {
10866                        start: "/*".to_string(),
10867                        end: " */".to_string(),
10868                        close: true,
10869                        surround: true,
10870                        newline: true,
10871                    },
10872                    BracketPair {
10873                        start: "[".to_string(),
10874                        end: "]".to_string(),
10875                        close: false,
10876                        surround: false,
10877                        newline: true,
10878                    },
10879                    BracketPair {
10880                        start: "\"".to_string(),
10881                        end: "\"".to_string(),
10882                        close: true,
10883                        surround: true,
10884                        newline: false,
10885                    },
10886                    BracketPair {
10887                        start: "<".to_string(),
10888                        end: ">".to_string(),
10889                        close: false,
10890                        surround: true,
10891                        newline: true,
10892                    },
10893                ],
10894                ..Default::default()
10895            },
10896            autoclose_before: "})]".to_string(),
10897            ..Default::default()
10898        },
10899        Some(tree_sitter_rust::LANGUAGE.into()),
10900    ));
10901
10902    cx.language_registry().add(language.clone());
10903    cx.update_buffer(|buffer, cx| {
10904        buffer.set_language(Some(language), cx);
10905    });
10906
10907    cx.set_state(
10908        &r#"
10909            🏀ˇ
10910            εˇ
10911            ❤️ˇ
10912        "#
10913        .unindent(),
10914    );
10915
10916    // autoclose multiple nested brackets at multiple cursors
10917    cx.update_editor(|editor, window, cx| {
10918        editor.handle_input("{", window, cx);
10919        editor.handle_input("{", window, cx);
10920        editor.handle_input("{", window, cx);
10921    });
10922    cx.assert_editor_state(
10923        &"
10924            🏀{{{ˇ}}}
10925            ε{{{ˇ}}}
10926            ❤️{{{ˇ}}}
10927        "
10928        .unindent(),
10929    );
10930
10931    // insert a different closing bracket
10932    cx.update_editor(|editor, window, cx| {
10933        editor.handle_input(")", window, cx);
10934    });
10935    cx.assert_editor_state(
10936        &"
10937            🏀{{{)ˇ}}}
10938            ε{{{)ˇ}}}
10939            ❤️{{{)ˇ}}}
10940        "
10941        .unindent(),
10942    );
10943
10944    // skip over the auto-closed brackets when typing a closing bracket
10945    cx.update_editor(|editor, window, cx| {
10946        editor.move_right(&MoveRight, window, cx);
10947        editor.handle_input("}", window, cx);
10948        editor.handle_input("}", window, cx);
10949        editor.handle_input("}", window, cx);
10950    });
10951    cx.assert_editor_state(
10952        &"
10953            🏀{{{)}}}}ˇ
10954            ε{{{)}}}}ˇ
10955            ❤️{{{)}}}}ˇ
10956        "
10957        .unindent(),
10958    );
10959
10960    // autoclose multi-character pairs
10961    cx.set_state(
10962        &"
10963            ˇ
10964            ˇ
10965        "
10966        .unindent(),
10967    );
10968    cx.update_editor(|editor, window, cx| {
10969        editor.handle_input("/", window, cx);
10970        editor.handle_input("*", window, cx);
10971    });
10972    cx.assert_editor_state(
10973        &"
10974            /*ˇ */
10975            /*ˇ */
10976        "
10977        .unindent(),
10978    );
10979
10980    // one cursor autocloses a multi-character pair, one cursor
10981    // does not autoclose.
10982    cx.set_state(
10983        &"
1098410985            ˇ
10986        "
10987        .unindent(),
10988    );
10989    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10990    cx.assert_editor_state(
10991        &"
10992            /*ˇ */
1099310994        "
10995        .unindent(),
10996    );
10997
10998    // Don't autoclose if the next character isn't whitespace and isn't
10999    // listed in the language's "autoclose_before" section.
11000    cx.set_state("ˇa b");
11001    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11002    cx.assert_editor_state("{ˇa b");
11003
11004    // Don't autoclose if `close` is false for the bracket pair
11005    cx.set_state("ˇ");
11006    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
11007    cx.assert_editor_state("");
11008
11009    // Surround with brackets if text is selected
11010    cx.set_state("«aˇ» b");
11011    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11012    cx.assert_editor_state("{«aˇ»} b");
11013
11014    // Autoclose when not immediately after a word character
11015    cx.set_state("a ˇ");
11016    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11017    cx.assert_editor_state("a \"ˇ\"");
11018
11019    // Autoclose pair where the start and end characters are the same
11020    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11021    cx.assert_editor_state("a \"\"ˇ");
11022
11023    // Don't autoclose when immediately after a word character
11024    cx.set_state("");
11025    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11026    cx.assert_editor_state("a\"ˇ");
11027
11028    // Do autoclose when after a non-word character
11029    cx.set_state("");
11030    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11031    cx.assert_editor_state("{\"ˇ\"");
11032
11033    // Non identical pairs autoclose regardless of preceding character
11034    cx.set_state("");
11035    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11036    cx.assert_editor_state("a{ˇ}");
11037
11038    // Don't autoclose pair if autoclose is disabled
11039    cx.set_state("ˇ");
11040    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11041    cx.assert_editor_state("");
11042
11043    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
11044    cx.set_state("«aˇ» b");
11045    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11046    cx.assert_editor_state("<«aˇ»> b");
11047}
11048
11049#[gpui::test]
11050async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
11051    init_test(cx, |settings| {
11052        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11053    });
11054
11055    let mut cx = EditorTestContext::new(cx).await;
11056
11057    let language = Arc::new(Language::new(
11058        LanguageConfig {
11059            brackets: BracketPairConfig {
11060                pairs: vec![
11061                    BracketPair {
11062                        start: "{".to_string(),
11063                        end: "}".to_string(),
11064                        close: true,
11065                        surround: true,
11066                        newline: true,
11067                    },
11068                    BracketPair {
11069                        start: "(".to_string(),
11070                        end: ")".to_string(),
11071                        close: true,
11072                        surround: true,
11073                        newline: true,
11074                    },
11075                    BracketPair {
11076                        start: "[".to_string(),
11077                        end: "]".to_string(),
11078                        close: false,
11079                        surround: false,
11080                        newline: true,
11081                    },
11082                ],
11083                ..Default::default()
11084            },
11085            autoclose_before: "})]".to_string(),
11086            ..Default::default()
11087        },
11088        Some(tree_sitter_rust::LANGUAGE.into()),
11089    ));
11090
11091    cx.language_registry().add(language.clone());
11092    cx.update_buffer(|buffer, cx| {
11093        buffer.set_language(Some(language), cx);
11094    });
11095
11096    cx.set_state(
11097        &"
11098            ˇ
11099            ˇ
11100            ˇ
11101        "
11102        .unindent(),
11103    );
11104
11105    // ensure only matching closing brackets are skipped over
11106    cx.update_editor(|editor, window, cx| {
11107        editor.handle_input("}", window, cx);
11108        editor.move_left(&MoveLeft, window, cx);
11109        editor.handle_input(")", window, cx);
11110        editor.move_left(&MoveLeft, window, cx);
11111    });
11112    cx.assert_editor_state(
11113        &"
11114            ˇ)}
11115            ˇ)}
11116            ˇ)}
11117        "
11118        .unindent(),
11119    );
11120
11121    // skip-over closing brackets at multiple cursors
11122    cx.update_editor(|editor, window, cx| {
11123        editor.handle_input(")", window, cx);
11124        editor.handle_input("}", window, cx);
11125    });
11126    cx.assert_editor_state(
11127        &"
11128            )}ˇ
11129            )}ˇ
11130            )}ˇ
11131        "
11132        .unindent(),
11133    );
11134
11135    // ignore non-close brackets
11136    cx.update_editor(|editor, window, cx| {
11137        editor.handle_input("]", window, cx);
11138        editor.move_left(&MoveLeft, window, cx);
11139        editor.handle_input("]", window, cx);
11140    });
11141    cx.assert_editor_state(
11142        &"
11143            )}]ˇ]
11144            )}]ˇ]
11145            )}]ˇ]
11146        "
11147        .unindent(),
11148    );
11149}
11150
11151#[gpui::test]
11152async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
11153    init_test(cx, |_| {});
11154
11155    let mut cx = EditorTestContext::new(cx).await;
11156
11157    let html_language = Arc::new(
11158        Language::new(
11159            LanguageConfig {
11160                name: "HTML".into(),
11161                brackets: BracketPairConfig {
11162                    pairs: vec![
11163                        BracketPair {
11164                            start: "<".into(),
11165                            end: ">".into(),
11166                            close: true,
11167                            ..Default::default()
11168                        },
11169                        BracketPair {
11170                            start: "{".into(),
11171                            end: "}".into(),
11172                            close: true,
11173                            ..Default::default()
11174                        },
11175                        BracketPair {
11176                            start: "(".into(),
11177                            end: ")".into(),
11178                            close: true,
11179                            ..Default::default()
11180                        },
11181                    ],
11182                    ..Default::default()
11183                },
11184                autoclose_before: "})]>".into(),
11185                ..Default::default()
11186            },
11187            Some(tree_sitter_html::LANGUAGE.into()),
11188        )
11189        .with_injection_query(
11190            r#"
11191            (script_element
11192                (raw_text) @injection.content
11193                (#set! injection.language "javascript"))
11194            "#,
11195        )
11196        .unwrap(),
11197    );
11198
11199    let javascript_language = Arc::new(Language::new(
11200        LanguageConfig {
11201            name: "JavaScript".into(),
11202            brackets: BracketPairConfig {
11203                pairs: vec![
11204                    BracketPair {
11205                        start: "/*".into(),
11206                        end: " */".into(),
11207                        close: true,
11208                        ..Default::default()
11209                    },
11210                    BracketPair {
11211                        start: "{".into(),
11212                        end: "}".into(),
11213                        close: true,
11214                        ..Default::default()
11215                    },
11216                    BracketPair {
11217                        start: "(".into(),
11218                        end: ")".into(),
11219                        close: true,
11220                        ..Default::default()
11221                    },
11222                ],
11223                ..Default::default()
11224            },
11225            autoclose_before: "})]>".into(),
11226            ..Default::default()
11227        },
11228        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11229    ));
11230
11231    cx.language_registry().add(html_language.clone());
11232    cx.language_registry().add(javascript_language);
11233    cx.executor().run_until_parked();
11234
11235    cx.update_buffer(|buffer, cx| {
11236        buffer.set_language(Some(html_language), cx);
11237    });
11238
11239    cx.set_state(
11240        &r#"
11241            <body>ˇ
11242                <script>
11243                    var x = 1;ˇ
11244                </script>
11245            </body>ˇ
11246        "#
11247        .unindent(),
11248    );
11249
11250    // Precondition: different languages are active at different locations.
11251    cx.update_editor(|editor, window, cx| {
11252        let snapshot = editor.snapshot(window, cx);
11253        let cursors = editor
11254            .selections
11255            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
11256        let languages = cursors
11257            .iter()
11258            .map(|c| snapshot.language_at(c.start).unwrap().name())
11259            .collect::<Vec<_>>();
11260        assert_eq!(
11261            languages,
11262            &["HTML".into(), "JavaScript".into(), "HTML".into()]
11263        );
11264    });
11265
11266    // Angle brackets autoclose in HTML, but not JavaScript.
11267    cx.update_editor(|editor, window, cx| {
11268        editor.handle_input("<", window, cx);
11269        editor.handle_input("a", window, cx);
11270    });
11271    cx.assert_editor_state(
11272        &r#"
11273            <body><aˇ>
11274                <script>
11275                    var x = 1;<aˇ
11276                </script>
11277            </body><aˇ>
11278        "#
11279        .unindent(),
11280    );
11281
11282    // Curly braces and parens autoclose in both HTML and JavaScript.
11283    cx.update_editor(|editor, window, cx| {
11284        editor.handle_input(" b=", window, cx);
11285        editor.handle_input("{", window, cx);
11286        editor.handle_input("c", window, cx);
11287        editor.handle_input("(", window, cx);
11288    });
11289    cx.assert_editor_state(
11290        &r#"
11291            <body><a b={c(ˇ)}>
11292                <script>
11293                    var x = 1;<a b={c(ˇ)}
11294                </script>
11295            </body><a b={c(ˇ)}>
11296        "#
11297        .unindent(),
11298    );
11299
11300    // Brackets that were already autoclosed are skipped.
11301    cx.update_editor(|editor, window, cx| {
11302        editor.handle_input(")", window, cx);
11303        editor.handle_input("d", window, cx);
11304        editor.handle_input("}", window, cx);
11305    });
11306    cx.assert_editor_state(
11307        &r#"
11308            <body><a b={c()d}ˇ>
11309                <script>
11310                    var x = 1;<a b={c()d}ˇ
11311                </script>
11312            </body><a b={c()d}ˇ>
11313        "#
11314        .unindent(),
11315    );
11316    cx.update_editor(|editor, window, cx| {
11317        editor.handle_input(">", window, cx);
11318    });
11319    cx.assert_editor_state(
11320        &r#"
11321            <body><a b={c()d}>ˇ
11322                <script>
11323                    var x = 1;<a b={c()d}>ˇ
11324                </script>
11325            </body><a b={c()d}>ˇ
11326        "#
11327        .unindent(),
11328    );
11329
11330    // Reset
11331    cx.set_state(
11332        &r#"
11333            <body>ˇ
11334                <script>
11335                    var x = 1;ˇ
11336                </script>
11337            </body>ˇ
11338        "#
11339        .unindent(),
11340    );
11341
11342    cx.update_editor(|editor, window, cx| {
11343        editor.handle_input("<", window, cx);
11344    });
11345    cx.assert_editor_state(
11346        &r#"
11347            <body><ˇ>
11348                <script>
11349                    var x = 1;<ˇ
11350                </script>
11351            </body><ˇ>
11352        "#
11353        .unindent(),
11354    );
11355
11356    // When backspacing, the closing angle brackets are removed.
11357    cx.update_editor(|editor, window, cx| {
11358        editor.backspace(&Backspace, window, cx);
11359    });
11360    cx.assert_editor_state(
11361        &r#"
11362            <body>ˇ
11363                <script>
11364                    var x = 1;ˇ
11365                </script>
11366            </body>ˇ
11367        "#
11368        .unindent(),
11369    );
11370
11371    // Block comments autoclose in JavaScript, but not HTML.
11372    cx.update_editor(|editor, window, cx| {
11373        editor.handle_input("/", window, cx);
11374        editor.handle_input("*", window, cx);
11375    });
11376    cx.assert_editor_state(
11377        &r#"
11378            <body>/*ˇ
11379                <script>
11380                    var x = 1;/*ˇ */
11381                </script>
11382            </body>/*ˇ
11383        "#
11384        .unindent(),
11385    );
11386}
11387
11388#[gpui::test]
11389async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11390    init_test(cx, |_| {});
11391
11392    let mut cx = EditorTestContext::new(cx).await;
11393
11394    let rust_language = Arc::new(
11395        Language::new(
11396            LanguageConfig {
11397                name: "Rust".into(),
11398                brackets: serde_json::from_value(json!([
11399                    { "start": "{", "end": "}", "close": true, "newline": true },
11400                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11401                ]))
11402                .unwrap(),
11403                autoclose_before: "})]>".into(),
11404                ..Default::default()
11405            },
11406            Some(tree_sitter_rust::LANGUAGE.into()),
11407        )
11408        .with_override_query("(string_literal) @string")
11409        .unwrap(),
11410    );
11411
11412    cx.language_registry().add(rust_language.clone());
11413    cx.update_buffer(|buffer, cx| {
11414        buffer.set_language(Some(rust_language), cx);
11415    });
11416
11417    cx.set_state(
11418        &r#"
11419            let x = ˇ
11420        "#
11421        .unindent(),
11422    );
11423
11424    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11425    cx.update_editor(|editor, window, cx| {
11426        editor.handle_input("\"", window, cx);
11427    });
11428    cx.assert_editor_state(
11429        &r#"
11430            let x = "ˇ"
11431        "#
11432        .unindent(),
11433    );
11434
11435    // Inserting another quotation mark. The cursor moves across the existing
11436    // automatically-inserted quotation mark.
11437    cx.update_editor(|editor, window, cx| {
11438        editor.handle_input("\"", window, cx);
11439    });
11440    cx.assert_editor_state(
11441        &r#"
11442            let x = ""ˇ
11443        "#
11444        .unindent(),
11445    );
11446
11447    // Reset
11448    cx.set_state(
11449        &r#"
11450            let x = ˇ
11451        "#
11452        .unindent(),
11453    );
11454
11455    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11456    cx.update_editor(|editor, window, cx| {
11457        editor.handle_input("\"", window, cx);
11458        editor.handle_input(" ", window, cx);
11459        editor.move_left(&Default::default(), window, cx);
11460        editor.handle_input("\\", window, cx);
11461        editor.handle_input("\"", window, cx);
11462    });
11463    cx.assert_editor_state(
11464        &r#"
11465            let x = "\"ˇ "
11466        "#
11467        .unindent(),
11468    );
11469
11470    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11471    // mark. Nothing is inserted.
11472    cx.update_editor(|editor, window, cx| {
11473        editor.move_right(&Default::default(), window, cx);
11474        editor.handle_input("\"", window, cx);
11475    });
11476    cx.assert_editor_state(
11477        &r#"
11478            let x = "\" "ˇ
11479        "#
11480        .unindent(),
11481    );
11482}
11483
11484#[gpui::test]
11485async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11486    init_test(cx, |_| {});
11487
11488    let mut cx = EditorTestContext::new(cx).await;
11489    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11490
11491    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11492
11493    // Double quote inside single-quoted string
11494    cx.set_state(indoc! {r#"
11495        def main():
11496            items = ['"', ˇ]
11497    "#});
11498    cx.update_editor(|editor, window, cx| {
11499        editor.handle_input("\"", window, cx);
11500    });
11501    cx.assert_editor_state(indoc! {r#"
11502        def main():
11503            items = ['"', "ˇ"]
11504    "#});
11505
11506    // Two double quotes inside single-quoted string
11507    cx.set_state(indoc! {r#"
11508        def main():
11509            items = ['""', ˇ]
11510    "#});
11511    cx.update_editor(|editor, window, cx| {
11512        editor.handle_input("\"", window, cx);
11513    });
11514    cx.assert_editor_state(indoc! {r#"
11515        def main():
11516            items = ['""', "ˇ"]
11517    "#});
11518
11519    // Single quote inside double-quoted string
11520    cx.set_state(indoc! {r#"
11521        def main():
11522            items = ["'", ˇ]
11523    "#});
11524    cx.update_editor(|editor, window, cx| {
11525        editor.handle_input("'", window, cx);
11526    });
11527    cx.assert_editor_state(indoc! {r#"
11528        def main():
11529            items = ["'", 'ˇ']
11530    "#});
11531
11532    // Two single quotes inside double-quoted string
11533    cx.set_state(indoc! {r#"
11534        def main():
11535            items = ["''", ˇ]
11536    "#});
11537    cx.update_editor(|editor, window, cx| {
11538        editor.handle_input("'", window, cx);
11539    });
11540    cx.assert_editor_state(indoc! {r#"
11541        def main():
11542            items = ["''", 'ˇ']
11543    "#});
11544
11545    // Mixed quotes on same line
11546    cx.set_state(indoc! {r#"
11547        def main():
11548            items = ['"""', "'''''", ˇ]
11549    "#});
11550    cx.update_editor(|editor, window, cx| {
11551        editor.handle_input("\"", window, cx);
11552    });
11553    cx.assert_editor_state(indoc! {r#"
11554        def main():
11555            items = ['"""', "'''''", "ˇ"]
11556    "#});
11557    cx.update_editor(|editor, window, cx| {
11558        editor.move_right(&MoveRight, window, cx);
11559    });
11560    cx.update_editor(|editor, window, cx| {
11561        editor.handle_input(", ", window, cx);
11562    });
11563    cx.update_editor(|editor, window, cx| {
11564        editor.handle_input("'", window, cx);
11565    });
11566    cx.assert_editor_state(indoc! {r#"
11567        def main():
11568            items = ['"""', "'''''", "", 'ˇ']
11569    "#});
11570}
11571
11572#[gpui::test]
11573async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11574    init_test(cx, |_| {});
11575
11576    let mut cx = EditorTestContext::new(cx).await;
11577    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11578    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11579
11580    cx.set_state(indoc! {r#"
11581        def main():
11582            items = ["🎉", ˇ]
11583    "#});
11584    cx.update_editor(|editor, window, cx| {
11585        editor.handle_input("\"", window, cx);
11586    });
11587    cx.assert_editor_state(indoc! {r#"
11588        def main():
11589            items = ["🎉", "ˇ"]
11590    "#});
11591}
11592
11593#[gpui::test]
11594async fn test_surround_with_pair(cx: &mut TestAppContext) {
11595    init_test(cx, |_| {});
11596
11597    let language = Arc::new(Language::new(
11598        LanguageConfig {
11599            brackets: BracketPairConfig {
11600                pairs: vec![
11601                    BracketPair {
11602                        start: "{".to_string(),
11603                        end: "}".to_string(),
11604                        close: true,
11605                        surround: true,
11606                        newline: true,
11607                    },
11608                    BracketPair {
11609                        start: "/* ".to_string(),
11610                        end: "*/".to_string(),
11611                        close: true,
11612                        surround: true,
11613                        ..Default::default()
11614                    },
11615                ],
11616                ..Default::default()
11617            },
11618            ..Default::default()
11619        },
11620        Some(tree_sitter_rust::LANGUAGE.into()),
11621    ));
11622
11623    let text = r#"
11624        a
11625        b
11626        c
11627    "#
11628    .unindent();
11629
11630    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11631    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11632    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11633    editor
11634        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11635        .await;
11636
11637    editor.update_in(cx, |editor, window, cx| {
11638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11639            s.select_display_ranges([
11640                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11641                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11642                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11643            ])
11644        });
11645
11646        editor.handle_input("{", window, cx);
11647        editor.handle_input("{", window, cx);
11648        editor.handle_input("{", window, cx);
11649        assert_eq!(
11650            editor.text(cx),
11651            "
11652                {{{a}}}
11653                {{{b}}}
11654                {{{c}}}
11655            "
11656            .unindent()
11657        );
11658        assert_eq!(
11659            display_ranges(editor, cx),
11660            [
11661                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11662                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11663                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11664            ]
11665        );
11666
11667        editor.undo(&Undo, window, cx);
11668        editor.undo(&Undo, window, cx);
11669        editor.undo(&Undo, window, cx);
11670        assert_eq!(
11671            editor.text(cx),
11672            "
11673                a
11674                b
11675                c
11676            "
11677            .unindent()
11678        );
11679        assert_eq!(
11680            display_ranges(editor, cx),
11681            [
11682                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11683                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11684                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11685            ]
11686        );
11687
11688        // Ensure inserting the first character of a multi-byte bracket pair
11689        // doesn't surround the selections with the bracket.
11690        editor.handle_input("/", window, cx);
11691        assert_eq!(
11692            editor.text(cx),
11693            "
11694                /
11695                /
11696                /
11697            "
11698            .unindent()
11699        );
11700        assert_eq!(
11701            display_ranges(editor, cx),
11702            [
11703                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11704                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11705                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11706            ]
11707        );
11708
11709        editor.undo(&Undo, window, cx);
11710        assert_eq!(
11711            editor.text(cx),
11712            "
11713                a
11714                b
11715                c
11716            "
11717            .unindent()
11718        );
11719        assert_eq!(
11720            display_ranges(editor, cx),
11721            [
11722                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11723                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11724                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11725            ]
11726        );
11727
11728        // Ensure inserting the last character of a multi-byte bracket pair
11729        // doesn't surround the selections with the bracket.
11730        editor.handle_input("*", window, cx);
11731        assert_eq!(
11732            editor.text(cx),
11733            "
11734                *
11735                *
11736                *
11737            "
11738            .unindent()
11739        );
11740        assert_eq!(
11741            display_ranges(editor, cx),
11742            [
11743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11744                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11745                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11746            ]
11747        );
11748    });
11749}
11750
11751#[gpui::test]
11752async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11753    init_test(cx, |_| {});
11754
11755    let language = Arc::new(Language::new(
11756        LanguageConfig {
11757            brackets: BracketPairConfig {
11758                pairs: vec![BracketPair {
11759                    start: "{".to_string(),
11760                    end: "}".to_string(),
11761                    close: true,
11762                    surround: true,
11763                    newline: true,
11764                }],
11765                ..Default::default()
11766            },
11767            autoclose_before: "}".to_string(),
11768            ..Default::default()
11769        },
11770        Some(tree_sitter_rust::LANGUAGE.into()),
11771    ));
11772
11773    let text = r#"
11774        a
11775        b
11776        c
11777    "#
11778    .unindent();
11779
11780    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11781    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11782    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11783    editor
11784        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11785        .await;
11786
11787    editor.update_in(cx, |editor, window, cx| {
11788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11789            s.select_ranges([
11790                Point::new(0, 1)..Point::new(0, 1),
11791                Point::new(1, 1)..Point::new(1, 1),
11792                Point::new(2, 1)..Point::new(2, 1),
11793            ])
11794        });
11795
11796        editor.handle_input("{", window, cx);
11797        editor.handle_input("{", window, cx);
11798        editor.handle_input("_", window, cx);
11799        assert_eq!(
11800            editor.text(cx),
11801            "
11802                a{{_}}
11803                b{{_}}
11804                c{{_}}
11805            "
11806            .unindent()
11807        );
11808        assert_eq!(
11809            editor
11810                .selections
11811                .ranges::<Point>(&editor.display_snapshot(cx)),
11812            [
11813                Point::new(0, 4)..Point::new(0, 4),
11814                Point::new(1, 4)..Point::new(1, 4),
11815                Point::new(2, 4)..Point::new(2, 4)
11816            ]
11817        );
11818
11819        editor.backspace(&Default::default(), window, cx);
11820        editor.backspace(&Default::default(), window, cx);
11821        assert_eq!(
11822            editor.text(cx),
11823            "
11824                a{}
11825                b{}
11826                c{}
11827            "
11828            .unindent()
11829        );
11830        assert_eq!(
11831            editor
11832                .selections
11833                .ranges::<Point>(&editor.display_snapshot(cx)),
11834            [
11835                Point::new(0, 2)..Point::new(0, 2),
11836                Point::new(1, 2)..Point::new(1, 2),
11837                Point::new(2, 2)..Point::new(2, 2)
11838            ]
11839        );
11840
11841        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11842        assert_eq!(
11843            editor.text(cx),
11844            "
11845                a
11846                b
11847                c
11848            "
11849            .unindent()
11850        );
11851        assert_eq!(
11852            editor
11853                .selections
11854                .ranges::<Point>(&editor.display_snapshot(cx)),
11855            [
11856                Point::new(0, 1)..Point::new(0, 1),
11857                Point::new(1, 1)..Point::new(1, 1),
11858                Point::new(2, 1)..Point::new(2, 1)
11859            ]
11860        );
11861    });
11862}
11863
11864#[gpui::test]
11865async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11866    init_test(cx, |settings| {
11867        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11868    });
11869
11870    let mut cx = EditorTestContext::new(cx).await;
11871
11872    let language = Arc::new(Language::new(
11873        LanguageConfig {
11874            brackets: BracketPairConfig {
11875                pairs: vec![
11876                    BracketPair {
11877                        start: "{".to_string(),
11878                        end: "}".to_string(),
11879                        close: true,
11880                        surround: true,
11881                        newline: true,
11882                    },
11883                    BracketPair {
11884                        start: "(".to_string(),
11885                        end: ")".to_string(),
11886                        close: true,
11887                        surround: true,
11888                        newline: true,
11889                    },
11890                    BracketPair {
11891                        start: "[".to_string(),
11892                        end: "]".to_string(),
11893                        close: false,
11894                        surround: true,
11895                        newline: true,
11896                    },
11897                ],
11898                ..Default::default()
11899            },
11900            autoclose_before: "})]".to_string(),
11901            ..Default::default()
11902        },
11903        Some(tree_sitter_rust::LANGUAGE.into()),
11904    ));
11905
11906    cx.language_registry().add(language.clone());
11907    cx.update_buffer(|buffer, cx| {
11908        buffer.set_language(Some(language), cx);
11909    });
11910
11911    cx.set_state(
11912        &"
11913            {(ˇ)}
11914            [[ˇ]]
11915            {(ˇ)}
11916        "
11917        .unindent(),
11918    );
11919
11920    cx.update_editor(|editor, window, cx| {
11921        editor.backspace(&Default::default(), window, cx);
11922        editor.backspace(&Default::default(), window, cx);
11923    });
11924
11925    cx.assert_editor_state(
11926        &"
11927            ˇ
11928            ˇ]]
11929            ˇ
11930        "
11931        .unindent(),
11932    );
11933
11934    cx.update_editor(|editor, window, cx| {
11935        editor.handle_input("{", window, cx);
11936        editor.handle_input("{", window, cx);
11937        editor.move_right(&MoveRight, window, cx);
11938        editor.move_right(&MoveRight, window, cx);
11939        editor.move_left(&MoveLeft, window, cx);
11940        editor.move_left(&MoveLeft, window, cx);
11941        editor.backspace(&Default::default(), window, cx);
11942    });
11943
11944    cx.assert_editor_state(
11945        &"
11946            {ˇ}
11947            {ˇ}]]
11948            {ˇ}
11949        "
11950        .unindent(),
11951    );
11952
11953    cx.update_editor(|editor, window, cx| {
11954        editor.backspace(&Default::default(), window, cx);
11955    });
11956
11957    cx.assert_editor_state(
11958        &"
11959            ˇ
11960            ˇ]]
11961            ˇ
11962        "
11963        .unindent(),
11964    );
11965}
11966
11967#[gpui::test]
11968async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11969    init_test(cx, |_| {});
11970
11971    let language = Arc::new(Language::new(
11972        LanguageConfig::default(),
11973        Some(tree_sitter_rust::LANGUAGE.into()),
11974    ));
11975
11976    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11977    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11978    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11979    editor
11980        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11981        .await;
11982
11983    editor.update_in(cx, |editor, window, cx| {
11984        editor.set_auto_replace_emoji_shortcode(true);
11985
11986        editor.handle_input("Hello ", window, cx);
11987        editor.handle_input(":wave", window, cx);
11988        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11989
11990        editor.handle_input(":", window, cx);
11991        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11992
11993        editor.handle_input(" :smile", window, cx);
11994        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11995
11996        editor.handle_input(":", window, cx);
11997        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11998
11999        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
12000        editor.handle_input(":wave", window, cx);
12001        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
12002
12003        editor.handle_input(":", window, cx);
12004        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
12005
12006        editor.handle_input(":1", window, cx);
12007        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
12008
12009        editor.handle_input(":", window, cx);
12010        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
12011
12012        // Ensure shortcode does not get replaced when it is part of a word
12013        editor.handle_input(" Test:wave", window, cx);
12014        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
12015
12016        editor.handle_input(":", window, cx);
12017        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
12018
12019        editor.set_auto_replace_emoji_shortcode(false);
12020
12021        // Ensure shortcode does not get replaced when auto replace is off
12022        editor.handle_input(" :wave", window, cx);
12023        assert_eq!(
12024            editor.text(cx),
12025            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
12026        );
12027
12028        editor.handle_input(":", window, cx);
12029        assert_eq!(
12030            editor.text(cx),
12031            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
12032        );
12033    });
12034}
12035
12036#[gpui::test]
12037async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
12038    init_test(cx, |_| {});
12039
12040    let (text, insertion_ranges) = marked_text_ranges(
12041        indoc! {"
12042            ˇ
12043        "},
12044        false,
12045    );
12046
12047    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12048    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12049
12050    _ = editor.update_in(cx, |editor, window, cx| {
12051        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
12052
12053        editor
12054            .insert_snippet(
12055                &insertion_ranges
12056                    .iter()
12057                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12058                    .collect::<Vec<_>>(),
12059                snippet,
12060                window,
12061                cx,
12062            )
12063            .unwrap();
12064
12065        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12066            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12067            assert_eq!(editor.text(cx), expected_text);
12068            assert_eq!(
12069                editor.selections.ranges(&editor.display_snapshot(cx)),
12070                selection_ranges
12071                    .iter()
12072                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12073                    .collect::<Vec<_>>()
12074            );
12075        }
12076
12077        assert(
12078            editor,
12079            cx,
12080            indoc! {"
12081            type «» =•
12082            "},
12083        );
12084
12085        assert!(editor.context_menu_visible(), "There should be a matches");
12086    });
12087}
12088
12089#[gpui::test]
12090async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
12091    init_test(cx, |_| {});
12092
12093    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12094        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12095        assert_eq!(editor.text(cx), expected_text);
12096        assert_eq!(
12097            editor.selections.ranges(&editor.display_snapshot(cx)),
12098            selection_ranges
12099                .iter()
12100                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12101                .collect::<Vec<_>>()
12102        );
12103    }
12104
12105    let (text, insertion_ranges) = marked_text_ranges(
12106        indoc! {"
12107            ˇ
12108        "},
12109        false,
12110    );
12111
12112    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12113    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12114
12115    _ = editor.update_in(cx, |editor, window, cx| {
12116        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
12117
12118        editor
12119            .insert_snippet(
12120                &insertion_ranges
12121                    .iter()
12122                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12123                    .collect::<Vec<_>>(),
12124                snippet,
12125                window,
12126                cx,
12127            )
12128            .unwrap();
12129
12130        assert_state(
12131            editor,
12132            cx,
12133            indoc! {"
12134            type «» = ;•
12135            "},
12136        );
12137
12138        assert!(
12139            editor.context_menu_visible(),
12140            "Context menu should be visible for placeholder choices"
12141        );
12142
12143        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12144
12145        assert_state(
12146            editor,
12147            cx,
12148            indoc! {"
12149            type  = «»;•
12150            "},
12151        );
12152
12153        assert!(
12154            !editor.context_menu_visible(),
12155            "Context menu should be hidden after moving to next tabstop"
12156        );
12157
12158        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12159
12160        assert_state(
12161            editor,
12162            cx,
12163            indoc! {"
12164            type  = ; ˇ
12165            "},
12166        );
12167
12168        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12169
12170        assert_state(
12171            editor,
12172            cx,
12173            indoc! {"
12174            type  = ; ˇ
12175            "},
12176        );
12177    });
12178
12179    _ = editor.update_in(cx, |editor, window, cx| {
12180        editor.select_all(&SelectAll, window, cx);
12181        editor.backspace(&Backspace, window, cx);
12182
12183        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
12184        let insertion_ranges = editor
12185            .selections
12186            .all(&editor.display_snapshot(cx))
12187            .iter()
12188            .map(|s| s.range())
12189            .collect::<Vec<_>>();
12190
12191        editor
12192            .insert_snippet(&insertion_ranges, snippet, window, cx)
12193            .unwrap();
12194
12195        assert_state(editor, cx, "fn «» = value;•");
12196
12197        assert!(
12198            editor.context_menu_visible(),
12199            "Context menu should be visible for placeholder choices"
12200        );
12201
12202        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12203
12204        assert_state(editor, cx, "fn  = «valueˇ»;•");
12205
12206        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12207
12208        assert_state(editor, cx, "fn «» = value;•");
12209
12210        assert!(
12211            editor.context_menu_visible(),
12212            "Context menu should be visible again after returning to first tabstop"
12213        );
12214
12215        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12216
12217        assert_state(editor, cx, "fn «» = value;•");
12218    });
12219}
12220
12221#[gpui::test]
12222async fn test_snippets(cx: &mut TestAppContext) {
12223    init_test(cx, |_| {});
12224
12225    let mut cx = EditorTestContext::new(cx).await;
12226
12227    cx.set_state(indoc! {"
12228        a.ˇ b
12229        a.ˇ b
12230        a.ˇ b
12231    "});
12232
12233    cx.update_editor(|editor, window, cx| {
12234        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
12235        let insertion_ranges = editor
12236            .selections
12237            .all(&editor.display_snapshot(cx))
12238            .iter()
12239            .map(|s| s.range())
12240            .collect::<Vec<_>>();
12241        editor
12242            .insert_snippet(&insertion_ranges, snippet, window, cx)
12243            .unwrap();
12244    });
12245
12246    cx.assert_editor_state(indoc! {"
12247        a.f(«oneˇ», two, «threeˇ») b
12248        a.f(«oneˇ», two, «threeˇ») b
12249        a.f(«oneˇ», two, «threeˇ») b
12250    "});
12251
12252    // Can't move earlier than the first tab stop
12253    cx.update_editor(|editor, window, cx| {
12254        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12255    });
12256    cx.assert_editor_state(indoc! {"
12257        a.f(«oneˇ», two, «threeˇ») b
12258        a.f(«oneˇ», two, «threeˇ») b
12259        a.f(«oneˇ», two, «threeˇ») b
12260    "});
12261
12262    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12263    cx.assert_editor_state(indoc! {"
12264        a.f(one, «twoˇ», three) b
12265        a.f(one, «twoˇ», three) b
12266        a.f(one, «twoˇ», three) b
12267    "});
12268
12269    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
12270    cx.assert_editor_state(indoc! {"
12271        a.f(«oneˇ», two, «threeˇ») b
12272        a.f(«oneˇ», two, «threeˇ») b
12273        a.f(«oneˇ», two, «threeˇ») b
12274    "});
12275
12276    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12277    cx.assert_editor_state(indoc! {"
12278        a.f(one, «twoˇ», three) b
12279        a.f(one, «twoˇ», three) b
12280        a.f(one, «twoˇ», three) b
12281    "});
12282    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12283    cx.assert_editor_state(indoc! {"
12284        a.f(one, two, three)ˇ b
12285        a.f(one, two, three)ˇ b
12286        a.f(one, two, three)ˇ b
12287    "});
12288
12289    // As soon as the last tab stop is reached, snippet state is gone
12290    cx.update_editor(|editor, window, cx| {
12291        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12292    });
12293    cx.assert_editor_state(indoc! {"
12294        a.f(one, two, three)ˇ b
12295        a.f(one, two, three)ˇ b
12296        a.f(one, two, three)ˇ b
12297    "});
12298}
12299
12300#[gpui::test]
12301async fn test_snippet_indentation(cx: &mut TestAppContext) {
12302    init_test(cx, |_| {});
12303
12304    let mut cx = EditorTestContext::new(cx).await;
12305
12306    cx.update_editor(|editor, window, cx| {
12307        let snippet = Snippet::parse(indoc! {"
12308            /*
12309             * Multiline comment with leading indentation
12310             *
12311             * $1
12312             */
12313            $0"})
12314        .unwrap();
12315        let insertion_ranges = editor
12316            .selections
12317            .all(&editor.display_snapshot(cx))
12318            .iter()
12319            .map(|s| s.range())
12320            .collect::<Vec<_>>();
12321        editor
12322            .insert_snippet(&insertion_ranges, snippet, window, cx)
12323            .unwrap();
12324    });
12325
12326    cx.assert_editor_state(indoc! {"
12327        /*
12328         * Multiline comment with leading indentation
12329         *
12330         * ˇ
12331         */
12332    "});
12333
12334    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12335    cx.assert_editor_state(indoc! {"
12336        /*
12337         * Multiline comment with leading indentation
12338         *
12339         *•
12340         */
12341        ˇ"});
12342}
12343
12344#[gpui::test]
12345async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
12346    init_test(cx, |_| {});
12347
12348    let mut cx = EditorTestContext::new(cx).await;
12349    cx.update_editor(|editor, _, cx| {
12350        editor.project().unwrap().update(cx, |project, cx| {
12351            project.snippets().update(cx, |snippets, _cx| {
12352                let snippet = project::snippet_provider::Snippet {
12353                    prefix: vec!["multi word".to_string()],
12354                    body: "this is many words".to_string(),
12355                    description: Some("description".to_string()),
12356                    name: "multi-word snippet test".to_string(),
12357                };
12358                snippets.add_snippet_for_test(
12359                    None,
12360                    PathBuf::from("test_snippets.json"),
12361                    vec![Arc::new(snippet)],
12362                );
12363            });
12364        })
12365    });
12366
12367    for (input_to_simulate, should_match_snippet) in [
12368        ("m", true),
12369        ("m ", true),
12370        ("m w", true),
12371        ("aa m w", true),
12372        ("aa m g", false),
12373    ] {
12374        cx.set_state("ˇ");
12375        cx.simulate_input(input_to_simulate); // fails correctly
12376
12377        cx.update_editor(|editor, _, _| {
12378            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12379            else {
12380                assert!(!should_match_snippet); // no completions! don't even show the menu
12381                return;
12382            };
12383            assert!(context_menu.visible());
12384            let completions = context_menu.completions.borrow();
12385
12386            assert_eq!(!completions.is_empty(), should_match_snippet);
12387        });
12388    }
12389}
12390
12391#[gpui::test]
12392async fn test_document_format_during_save(cx: &mut TestAppContext) {
12393    init_test(cx, |_| {});
12394
12395    let fs = FakeFs::new(cx.executor());
12396    fs.insert_file(path!("/file.rs"), Default::default()).await;
12397
12398    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12399
12400    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12401    language_registry.add(rust_lang());
12402    let mut fake_servers = language_registry.register_fake_lsp(
12403        "Rust",
12404        FakeLspAdapter {
12405            capabilities: lsp::ServerCapabilities {
12406                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12407                ..Default::default()
12408            },
12409            ..Default::default()
12410        },
12411    );
12412
12413    let buffer = project
12414        .update(cx, |project, cx| {
12415            project.open_local_buffer(path!("/file.rs"), cx)
12416        })
12417        .await
12418        .unwrap();
12419
12420    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12421    let (editor, cx) = cx.add_window_view(|window, cx| {
12422        build_editor_with_project(project.clone(), buffer, window, cx)
12423    });
12424    editor.update_in(cx, |editor, window, cx| {
12425        editor.set_text("one\ntwo\nthree\n", window, cx)
12426    });
12427    assert!(cx.read(|cx| editor.is_dirty(cx)));
12428
12429    let fake_server = fake_servers.next().await.unwrap();
12430
12431    {
12432        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12433            move |params, _| async move {
12434                assert_eq!(
12435                    params.text_document.uri,
12436                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12437                );
12438                assert_eq!(params.options.tab_size, 4);
12439                Ok(Some(vec![lsp::TextEdit::new(
12440                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12441                    ", ".to_string(),
12442                )]))
12443            },
12444        );
12445        let save = editor
12446            .update_in(cx, |editor, window, cx| {
12447                editor.save(
12448                    SaveOptions {
12449                        format: true,
12450                        autosave: false,
12451                    },
12452                    project.clone(),
12453                    window,
12454                    cx,
12455                )
12456            })
12457            .unwrap();
12458        save.await;
12459
12460        assert_eq!(
12461            editor.update(cx, |editor, cx| editor.text(cx)),
12462            "one, two\nthree\n"
12463        );
12464        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12465    }
12466
12467    {
12468        editor.update_in(cx, |editor, window, cx| {
12469            editor.set_text("one\ntwo\nthree\n", window, cx)
12470        });
12471        assert!(cx.read(|cx| editor.is_dirty(cx)));
12472
12473        // Ensure we can still save even if formatting hangs.
12474        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12475            move |params, _| async move {
12476                assert_eq!(
12477                    params.text_document.uri,
12478                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12479                );
12480                futures::future::pending::<()>().await;
12481                unreachable!()
12482            },
12483        );
12484        let save = editor
12485            .update_in(cx, |editor, window, cx| {
12486                editor.save(
12487                    SaveOptions {
12488                        format: true,
12489                        autosave: false,
12490                    },
12491                    project.clone(),
12492                    window,
12493                    cx,
12494                )
12495            })
12496            .unwrap();
12497        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12498        save.await;
12499        assert_eq!(
12500            editor.update(cx, |editor, cx| editor.text(cx)),
12501            "one\ntwo\nthree\n"
12502        );
12503    }
12504
12505    // Set rust language override and assert overridden tabsize is sent to language server
12506    update_test_language_settings(cx, |settings| {
12507        settings.languages.0.insert(
12508            "Rust".into(),
12509            LanguageSettingsContent {
12510                tab_size: NonZeroU32::new(8),
12511                ..Default::default()
12512            },
12513        );
12514    });
12515
12516    {
12517        editor.update_in(cx, |editor, window, cx| {
12518            editor.set_text("somehting_new\n", window, cx)
12519        });
12520        assert!(cx.read(|cx| editor.is_dirty(cx)));
12521        let _formatting_request_signal = fake_server
12522            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12523                assert_eq!(
12524                    params.text_document.uri,
12525                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12526                );
12527                assert_eq!(params.options.tab_size, 8);
12528                Ok(Some(vec![]))
12529            });
12530        let save = editor
12531            .update_in(cx, |editor, window, cx| {
12532                editor.save(
12533                    SaveOptions {
12534                        format: true,
12535                        autosave: false,
12536                    },
12537                    project.clone(),
12538                    window,
12539                    cx,
12540                )
12541            })
12542            .unwrap();
12543        save.await;
12544    }
12545}
12546
12547#[gpui::test]
12548async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12549    init_test(cx, |settings| {
12550        settings.defaults.ensure_final_newline_on_save = Some(false);
12551    });
12552
12553    let fs = FakeFs::new(cx.executor());
12554    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12555
12556    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12557
12558    let buffer = project
12559        .update(cx, |project, cx| {
12560            project.open_local_buffer(path!("/file.txt"), cx)
12561        })
12562        .await
12563        .unwrap();
12564
12565    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12566    let (editor, cx) = cx.add_window_view(|window, cx| {
12567        build_editor_with_project(project.clone(), buffer, window, cx)
12568    });
12569    editor.update_in(cx, |editor, window, cx| {
12570        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12571            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12572        });
12573    });
12574    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12575
12576    editor.update_in(cx, |editor, window, cx| {
12577        editor.handle_input("\n", window, cx)
12578    });
12579    cx.run_until_parked();
12580    save(&editor, &project, cx).await;
12581    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12582
12583    editor.update_in(cx, |editor, window, cx| {
12584        editor.undo(&Default::default(), window, cx);
12585    });
12586    save(&editor, &project, cx).await;
12587    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12588
12589    editor.update_in(cx, |editor, window, cx| {
12590        editor.redo(&Default::default(), window, cx);
12591    });
12592    cx.run_until_parked();
12593    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12594
12595    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12596        let save = editor
12597            .update_in(cx, |editor, window, cx| {
12598                editor.save(
12599                    SaveOptions {
12600                        format: true,
12601                        autosave: false,
12602                    },
12603                    project.clone(),
12604                    window,
12605                    cx,
12606                )
12607            })
12608            .unwrap();
12609        save.await;
12610        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12611    }
12612}
12613
12614#[gpui::test]
12615async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12616    init_test(cx, |_| {});
12617
12618    let cols = 4;
12619    let rows = 10;
12620    let sample_text_1 = sample_text(rows, cols, 'a');
12621    assert_eq!(
12622        sample_text_1,
12623        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12624    );
12625    let sample_text_2 = sample_text(rows, cols, 'l');
12626    assert_eq!(
12627        sample_text_2,
12628        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12629    );
12630    let sample_text_3 = sample_text(rows, cols, 'v');
12631    assert_eq!(
12632        sample_text_3,
12633        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12634    );
12635
12636    let fs = FakeFs::new(cx.executor());
12637    fs.insert_tree(
12638        path!("/a"),
12639        json!({
12640            "main.rs": sample_text_1,
12641            "other.rs": sample_text_2,
12642            "lib.rs": sample_text_3,
12643        }),
12644    )
12645    .await;
12646
12647    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12648    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
12649    let cx = &mut VisualTestContext::from_window(*window, cx);
12650
12651    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12652    language_registry.add(rust_lang());
12653    let mut fake_servers = language_registry.register_fake_lsp(
12654        "Rust",
12655        FakeLspAdapter {
12656            capabilities: lsp::ServerCapabilities {
12657                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12658                ..Default::default()
12659            },
12660            ..Default::default()
12661        },
12662    );
12663
12664    let worktree = project.update(cx, |project, cx| {
12665        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12666        assert_eq!(worktrees.len(), 1);
12667        worktrees.pop().unwrap()
12668    });
12669    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12670
12671    let buffer_1 = project
12672        .update(cx, |project, cx| {
12673            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12674        })
12675        .await
12676        .unwrap();
12677    let buffer_2 = project
12678        .update(cx, |project, cx| {
12679            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12680        })
12681        .await
12682        .unwrap();
12683    let buffer_3 = project
12684        .update(cx, |project, cx| {
12685            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12686        })
12687        .await
12688        .unwrap();
12689
12690    let multi_buffer = cx.new(|cx| {
12691        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12692        multi_buffer.push_excerpts(
12693            buffer_1.clone(),
12694            [
12695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12698            ],
12699            cx,
12700        );
12701        multi_buffer.push_excerpts(
12702            buffer_2.clone(),
12703            [
12704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12707            ],
12708            cx,
12709        );
12710        multi_buffer.push_excerpts(
12711            buffer_3.clone(),
12712            [
12713                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12714                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12715                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12716            ],
12717            cx,
12718        );
12719        multi_buffer
12720    });
12721    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12722        Editor::new(
12723            EditorMode::full(),
12724            multi_buffer,
12725            Some(project.clone()),
12726            window,
12727            cx,
12728        )
12729    });
12730
12731    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12732        editor.change_selections(
12733            SelectionEffects::scroll(Autoscroll::Next),
12734            window,
12735            cx,
12736            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12737        );
12738        editor.insert("|one|two|three|", window, cx);
12739    });
12740    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12741    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12742        editor.change_selections(
12743            SelectionEffects::scroll(Autoscroll::Next),
12744            window,
12745            cx,
12746            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12747        );
12748        editor.insert("|four|five|six|", window, cx);
12749    });
12750    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12751
12752    // First two buffers should be edited, but not the third one.
12753    assert_eq!(
12754        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12755        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
12756    );
12757    buffer_1.update(cx, |buffer, _| {
12758        assert!(buffer.is_dirty());
12759        assert_eq!(
12760            buffer.text(),
12761            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12762        )
12763    });
12764    buffer_2.update(cx, |buffer, _| {
12765        assert!(buffer.is_dirty());
12766        assert_eq!(
12767            buffer.text(),
12768            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12769        )
12770    });
12771    buffer_3.update(cx, |buffer, _| {
12772        assert!(!buffer.is_dirty());
12773        assert_eq!(buffer.text(), sample_text_3,)
12774    });
12775    cx.executor().run_until_parked();
12776
12777    let save = multi_buffer_editor
12778        .update_in(cx, |editor, window, cx| {
12779            editor.save(
12780                SaveOptions {
12781                    format: true,
12782                    autosave: false,
12783                },
12784                project.clone(),
12785                window,
12786                cx,
12787            )
12788        })
12789        .unwrap();
12790
12791    let fake_server = fake_servers.next().await.unwrap();
12792    fake_server
12793        .server
12794        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12795            Ok(Some(vec![lsp::TextEdit::new(
12796                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12797                format!("[{} formatted]", params.text_document.uri),
12798            )]))
12799        })
12800        .detach();
12801    save.await;
12802
12803    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12804    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12805    assert_eq!(
12806        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12807        uri!(
12808            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
12809        ),
12810    );
12811    buffer_1.update(cx, |buffer, _| {
12812        assert!(!buffer.is_dirty());
12813        assert_eq!(
12814            buffer.text(),
12815            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12816        )
12817    });
12818    buffer_2.update(cx, |buffer, _| {
12819        assert!(!buffer.is_dirty());
12820        assert_eq!(
12821            buffer.text(),
12822            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12823        )
12824    });
12825    buffer_3.update(cx, |buffer, _| {
12826        assert!(!buffer.is_dirty());
12827        assert_eq!(buffer.text(), sample_text_3,)
12828    });
12829}
12830
12831#[gpui::test]
12832async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12833    init_test(cx, |_| {});
12834
12835    let fs = FakeFs::new(cx.executor());
12836    fs.insert_tree(
12837        path!("/dir"),
12838        json!({
12839            "file1.rs": "fn main() { println!(\"hello\"); }",
12840            "file2.rs": "fn test() { println!(\"test\"); }",
12841            "file3.rs": "fn other() { println!(\"other\"); }\n",
12842        }),
12843    )
12844    .await;
12845
12846    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12847    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
12848    let cx = &mut VisualTestContext::from_window(*window, cx);
12849
12850    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12851    language_registry.add(rust_lang());
12852
12853    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12854    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12855
12856    // Open three buffers
12857    let buffer_1 = project
12858        .update(cx, |project, cx| {
12859            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12860        })
12861        .await
12862        .unwrap();
12863    let buffer_2 = project
12864        .update(cx, |project, cx| {
12865            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12866        })
12867        .await
12868        .unwrap();
12869    let buffer_3 = project
12870        .update(cx, |project, cx| {
12871            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12872        })
12873        .await
12874        .unwrap();
12875
12876    // Create a multi-buffer with all three buffers
12877    let multi_buffer = cx.new(|cx| {
12878        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12879        multi_buffer.push_excerpts(
12880            buffer_1.clone(),
12881            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12882            cx,
12883        );
12884        multi_buffer.push_excerpts(
12885            buffer_2.clone(),
12886            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12887            cx,
12888        );
12889        multi_buffer.push_excerpts(
12890            buffer_3.clone(),
12891            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12892            cx,
12893        );
12894        multi_buffer
12895    });
12896
12897    let editor = cx.new_window_entity(|window, cx| {
12898        Editor::new(
12899            EditorMode::full(),
12900            multi_buffer,
12901            Some(project.clone()),
12902            window,
12903            cx,
12904        )
12905    });
12906
12907    // Edit only the first buffer
12908    editor.update_in(cx, |editor, window, cx| {
12909        editor.change_selections(
12910            SelectionEffects::scroll(Autoscroll::Next),
12911            window,
12912            cx,
12913            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12914        );
12915        editor.insert("// edited", window, cx);
12916    });
12917
12918    // Verify that only buffer 1 is dirty
12919    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12920    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12921    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12922
12923    // Get write counts after file creation (files were created with initial content)
12924    // We expect each file to have been written once during creation
12925    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12926    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12927    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12928
12929    // Perform autosave
12930    let save_task = editor.update_in(cx, |editor, window, cx| {
12931        editor.save(
12932            SaveOptions {
12933                format: true,
12934                autosave: true,
12935            },
12936            project.clone(),
12937            window,
12938            cx,
12939        )
12940    });
12941    save_task.await.unwrap();
12942
12943    // Only the dirty buffer should have been saved
12944    assert_eq!(
12945        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12946        1,
12947        "Buffer 1 was dirty, so it should have been written once during autosave"
12948    );
12949    assert_eq!(
12950        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12951        0,
12952        "Buffer 2 was clean, so it should not have been written during autosave"
12953    );
12954    assert_eq!(
12955        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12956        0,
12957        "Buffer 3 was clean, so it should not have been written during autosave"
12958    );
12959
12960    // Verify buffer states after autosave
12961    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12962    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12963    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12964
12965    // Now perform a manual save (format = true)
12966    let save_task = editor.update_in(cx, |editor, window, cx| {
12967        editor.save(
12968            SaveOptions {
12969                format: true,
12970                autosave: false,
12971            },
12972            project.clone(),
12973            window,
12974            cx,
12975        )
12976    });
12977    save_task.await.unwrap();
12978
12979    // During manual save, clean buffers don't get written to disk
12980    // They just get did_save called for language server notifications
12981    assert_eq!(
12982        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12983        1,
12984        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12985    );
12986    assert_eq!(
12987        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12988        0,
12989        "Buffer 2 should not have been written at all"
12990    );
12991    assert_eq!(
12992        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12993        0,
12994        "Buffer 3 should not have been written at all"
12995    );
12996}
12997
12998async fn setup_range_format_test(
12999    cx: &mut TestAppContext,
13000) -> (
13001    Entity<Project>,
13002    Entity<Editor>,
13003    &mut gpui::VisualTestContext,
13004    lsp::FakeLanguageServer,
13005) {
13006    init_test(cx, |_| {});
13007
13008    let fs = FakeFs::new(cx.executor());
13009    fs.insert_file(path!("/file.rs"), Default::default()).await;
13010
13011    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13012
13013    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13014    language_registry.add(rust_lang());
13015    let mut fake_servers = language_registry.register_fake_lsp(
13016        "Rust",
13017        FakeLspAdapter {
13018            capabilities: lsp::ServerCapabilities {
13019                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
13020                ..lsp::ServerCapabilities::default()
13021            },
13022            ..FakeLspAdapter::default()
13023        },
13024    );
13025
13026    let buffer = project
13027        .update(cx, |project, cx| {
13028            project.open_local_buffer(path!("/file.rs"), cx)
13029        })
13030        .await
13031        .unwrap();
13032
13033    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13034    let (editor, cx) = cx.add_window_view(|window, cx| {
13035        build_editor_with_project(project.clone(), buffer, window, cx)
13036    });
13037
13038    let fake_server = fake_servers.next().await.unwrap();
13039
13040    (project, editor, cx, fake_server)
13041}
13042
13043#[gpui::test]
13044async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
13045    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13046
13047    editor.update_in(cx, |editor, window, cx| {
13048        editor.set_text("one\ntwo\nthree\n", window, cx)
13049    });
13050    assert!(cx.read(|cx| editor.is_dirty(cx)));
13051
13052    let save = editor
13053        .update_in(cx, |editor, window, cx| {
13054            editor.save(
13055                SaveOptions {
13056                    format: true,
13057                    autosave: false,
13058                },
13059                project.clone(),
13060                window,
13061                cx,
13062            )
13063        })
13064        .unwrap();
13065    fake_server
13066        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13067            assert_eq!(
13068                params.text_document.uri,
13069                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13070            );
13071            assert_eq!(params.options.tab_size, 4);
13072            Ok(Some(vec![lsp::TextEdit::new(
13073                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13074                ", ".to_string(),
13075            )]))
13076        })
13077        .next()
13078        .await;
13079    save.await;
13080    assert_eq!(
13081        editor.update(cx, |editor, cx| editor.text(cx)),
13082        "one, two\nthree\n"
13083    );
13084    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13085}
13086
13087#[gpui::test]
13088async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
13089    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13090
13091    editor.update_in(cx, |editor, window, cx| {
13092        editor.set_text("one\ntwo\nthree\n", window, cx)
13093    });
13094    assert!(cx.read(|cx| editor.is_dirty(cx)));
13095
13096    // Test that save still works when formatting hangs
13097    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
13098        move |params, _| async move {
13099            assert_eq!(
13100                params.text_document.uri,
13101                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13102            );
13103            futures::future::pending::<()>().await;
13104            unreachable!()
13105        },
13106    );
13107    let save = editor
13108        .update_in(cx, |editor, window, cx| {
13109            editor.save(
13110                SaveOptions {
13111                    format: true,
13112                    autosave: false,
13113                },
13114                project.clone(),
13115                window,
13116                cx,
13117            )
13118        })
13119        .unwrap();
13120    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13121    save.await;
13122    assert_eq!(
13123        editor.update(cx, |editor, cx| editor.text(cx)),
13124        "one\ntwo\nthree\n"
13125    );
13126    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13127}
13128
13129#[gpui::test]
13130async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
13131    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13132
13133    // Buffer starts clean, no formatting should be requested
13134    let save = editor
13135        .update_in(cx, |editor, window, cx| {
13136            editor.save(
13137                SaveOptions {
13138                    format: false,
13139                    autosave: false,
13140                },
13141                project.clone(),
13142                window,
13143                cx,
13144            )
13145        })
13146        .unwrap();
13147    let _pending_format_request = fake_server
13148        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
13149            panic!("Should not be invoked");
13150        })
13151        .next();
13152    save.await;
13153    cx.run_until_parked();
13154}
13155
13156#[gpui::test]
13157async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
13158    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13159
13160    // Set Rust language override and assert overridden tabsize is sent to language server
13161    update_test_language_settings(cx, |settings| {
13162        settings.languages.0.insert(
13163            "Rust".into(),
13164            LanguageSettingsContent {
13165                tab_size: NonZeroU32::new(8),
13166                ..Default::default()
13167            },
13168        );
13169    });
13170
13171    editor.update_in(cx, |editor, window, cx| {
13172        editor.set_text("something_new\n", window, cx)
13173    });
13174    assert!(cx.read(|cx| editor.is_dirty(cx)));
13175    let save = editor
13176        .update_in(cx, |editor, window, cx| {
13177            editor.save(
13178                SaveOptions {
13179                    format: true,
13180                    autosave: false,
13181                },
13182                project.clone(),
13183                window,
13184                cx,
13185            )
13186        })
13187        .unwrap();
13188    fake_server
13189        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13190            assert_eq!(
13191                params.text_document.uri,
13192                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13193            );
13194            assert_eq!(params.options.tab_size, 8);
13195            Ok(Some(Vec::new()))
13196        })
13197        .next()
13198        .await;
13199    save.await;
13200}
13201
13202#[gpui::test]
13203async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
13204    init_test(cx, |settings| {
13205        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
13206            settings::LanguageServerFormatterSpecifier::Current,
13207        )))
13208    });
13209
13210    let fs = FakeFs::new(cx.executor());
13211    fs.insert_file(path!("/file.rs"), Default::default()).await;
13212
13213    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13214
13215    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13216    language_registry.add(Arc::new(Language::new(
13217        LanguageConfig {
13218            name: "Rust".into(),
13219            matcher: LanguageMatcher {
13220                path_suffixes: vec!["rs".to_string()],
13221                ..Default::default()
13222            },
13223            ..LanguageConfig::default()
13224        },
13225        Some(tree_sitter_rust::LANGUAGE.into()),
13226    )));
13227    update_test_language_settings(cx, |settings| {
13228        // Enable Prettier formatting for the same buffer, and ensure
13229        // LSP is called instead of Prettier.
13230        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13231    });
13232    let mut fake_servers = language_registry.register_fake_lsp(
13233        "Rust",
13234        FakeLspAdapter {
13235            capabilities: lsp::ServerCapabilities {
13236                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13237                ..Default::default()
13238            },
13239            ..Default::default()
13240        },
13241    );
13242
13243    let buffer = project
13244        .update(cx, |project, cx| {
13245            project.open_local_buffer(path!("/file.rs"), cx)
13246        })
13247        .await
13248        .unwrap();
13249
13250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13251    let (editor, cx) = cx.add_window_view(|window, cx| {
13252        build_editor_with_project(project.clone(), buffer, window, cx)
13253    });
13254    editor.update_in(cx, |editor, window, cx| {
13255        editor.set_text("one\ntwo\nthree\n", window, cx)
13256    });
13257
13258    let fake_server = fake_servers.next().await.unwrap();
13259
13260    let format = editor
13261        .update_in(cx, |editor, window, cx| {
13262            editor.perform_format(
13263                project.clone(),
13264                FormatTrigger::Manual,
13265                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13266                window,
13267                cx,
13268            )
13269        })
13270        .unwrap();
13271    fake_server
13272        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13273            assert_eq!(
13274                params.text_document.uri,
13275                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13276            );
13277            assert_eq!(params.options.tab_size, 4);
13278            Ok(Some(vec![lsp::TextEdit::new(
13279                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13280                ", ".to_string(),
13281            )]))
13282        })
13283        .next()
13284        .await;
13285    format.await;
13286    assert_eq!(
13287        editor.update(cx, |editor, cx| editor.text(cx)),
13288        "one, two\nthree\n"
13289    );
13290
13291    editor.update_in(cx, |editor, window, cx| {
13292        editor.set_text("one\ntwo\nthree\n", window, cx)
13293    });
13294    // Ensure we don't lock if formatting hangs.
13295    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13296        move |params, _| async move {
13297            assert_eq!(
13298                params.text_document.uri,
13299                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13300            );
13301            futures::future::pending::<()>().await;
13302            unreachable!()
13303        },
13304    );
13305    let format = editor
13306        .update_in(cx, |editor, window, cx| {
13307            editor.perform_format(
13308                project,
13309                FormatTrigger::Manual,
13310                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13311                window,
13312                cx,
13313            )
13314        })
13315        .unwrap();
13316    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13317    format.await;
13318    assert_eq!(
13319        editor.update(cx, |editor, cx| editor.text(cx)),
13320        "one\ntwo\nthree\n"
13321    );
13322}
13323
13324#[gpui::test]
13325async fn test_multiple_formatters(cx: &mut TestAppContext) {
13326    init_test(cx, |settings| {
13327        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
13328        settings.defaults.formatter = Some(FormatterList::Vec(vec![
13329            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
13330            Formatter::CodeAction("code-action-1".into()),
13331            Formatter::CodeAction("code-action-2".into()),
13332        ]))
13333    });
13334
13335    let fs = FakeFs::new(cx.executor());
13336    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
13337        .await;
13338
13339    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13340    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13341    language_registry.add(rust_lang());
13342
13343    let mut fake_servers = language_registry.register_fake_lsp(
13344        "Rust",
13345        FakeLspAdapter {
13346            capabilities: lsp::ServerCapabilities {
13347                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13348                execute_command_provider: Some(lsp::ExecuteCommandOptions {
13349                    commands: vec!["the-command-for-code-action-1".into()],
13350                    ..Default::default()
13351                }),
13352                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13353                ..Default::default()
13354            },
13355            ..Default::default()
13356        },
13357    );
13358
13359    let buffer = project
13360        .update(cx, |project, cx| {
13361            project.open_local_buffer(path!("/file.rs"), cx)
13362        })
13363        .await
13364        .unwrap();
13365
13366    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13367    let (editor, cx) = cx.add_window_view(|window, cx| {
13368        build_editor_with_project(project.clone(), buffer, window, cx)
13369    });
13370
13371    let fake_server = fake_servers.next().await.unwrap();
13372    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13373        move |_params, _| async move {
13374            Ok(Some(vec![lsp::TextEdit::new(
13375                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13376                "applied-formatting\n".to_string(),
13377            )]))
13378        },
13379    );
13380    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13381        move |params, _| async move {
13382            let requested_code_actions = params.context.only.expect("Expected code action request");
13383            assert_eq!(requested_code_actions.len(), 1);
13384
13385            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
13386            let code_action = match requested_code_actions[0].as_str() {
13387                "code-action-1" => lsp::CodeAction {
13388                    kind: Some("code-action-1".into()),
13389                    edit: Some(lsp::WorkspaceEdit::new(
13390                        [(
13391                            uri,
13392                            vec![lsp::TextEdit::new(
13393                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13394                                "applied-code-action-1-edit\n".to_string(),
13395                            )],
13396                        )]
13397                        .into_iter()
13398                        .collect(),
13399                    )),
13400                    command: Some(lsp::Command {
13401                        command: "the-command-for-code-action-1".into(),
13402                        ..Default::default()
13403                    }),
13404                    ..Default::default()
13405                },
13406                "code-action-2" => lsp::CodeAction {
13407                    kind: Some("code-action-2".into()),
13408                    edit: Some(lsp::WorkspaceEdit::new(
13409                        [(
13410                            uri,
13411                            vec![lsp::TextEdit::new(
13412                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
13413                                "applied-code-action-2-edit\n".to_string(),
13414                            )],
13415                        )]
13416                        .into_iter()
13417                        .collect(),
13418                    )),
13419                    ..Default::default()
13420                },
13421                req => panic!("Unexpected code action request: {:?}", req),
13422            };
13423            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13424                code_action,
13425            )]))
13426        },
13427    );
13428
13429    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
13430        move |params, _| async move { Ok(params) }
13431    });
13432
13433    let command_lock = Arc::new(futures::lock::Mutex::new(()));
13434    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
13435        let fake = fake_server.clone();
13436        let lock = command_lock.clone();
13437        move |params, _| {
13438            assert_eq!(params.command, "the-command-for-code-action-1");
13439            let fake = fake.clone();
13440            let lock = lock.clone();
13441            async move {
13442                lock.lock().await;
13443                fake.server
13444                    .request::<lsp::request::ApplyWorkspaceEdit>(
13445                        lsp::ApplyWorkspaceEditParams {
13446                            label: None,
13447                            edit: lsp::WorkspaceEdit {
13448                                changes: Some(
13449                                    [(
13450                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13451                                        vec![lsp::TextEdit {
13452                                            range: lsp::Range::new(
13453                                                lsp::Position::new(0, 0),
13454                                                lsp::Position::new(0, 0),
13455                                            ),
13456                                            new_text: "applied-code-action-1-command\n".into(),
13457                                        }],
13458                                    )]
13459                                    .into_iter()
13460                                    .collect(),
13461                                ),
13462                                ..Default::default()
13463                            },
13464                        },
13465                        DEFAULT_LSP_REQUEST_TIMEOUT,
13466                    )
13467                    .await
13468                    .into_response()
13469                    .unwrap();
13470                Ok(Some(json!(null)))
13471            }
13472        }
13473    });
13474
13475    editor
13476        .update_in(cx, |editor, window, cx| {
13477            editor.perform_format(
13478                project.clone(),
13479                FormatTrigger::Manual,
13480                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13481                window,
13482                cx,
13483            )
13484        })
13485        .unwrap()
13486        .await;
13487    editor.update(cx, |editor, cx| {
13488        assert_eq!(
13489            editor.text(cx),
13490            r#"
13491                applied-code-action-2-edit
13492                applied-code-action-1-command
13493                applied-code-action-1-edit
13494                applied-formatting
13495                one
13496                two
13497                three
13498            "#
13499            .unindent()
13500        );
13501    });
13502
13503    editor.update_in(cx, |editor, window, cx| {
13504        editor.undo(&Default::default(), window, cx);
13505        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13506    });
13507
13508    // Perform a manual edit while waiting for an LSP command
13509    // that's being run as part of a formatting code action.
13510    let lock_guard = command_lock.lock().await;
13511    let format = editor
13512        .update_in(cx, |editor, window, cx| {
13513            editor.perform_format(
13514                project.clone(),
13515                FormatTrigger::Manual,
13516                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13517                window,
13518                cx,
13519            )
13520        })
13521        .unwrap();
13522    cx.run_until_parked();
13523    editor.update(cx, |editor, cx| {
13524        assert_eq!(
13525            editor.text(cx),
13526            r#"
13527                applied-code-action-1-edit
13528                applied-formatting
13529                one
13530                two
13531                three
13532            "#
13533            .unindent()
13534        );
13535
13536        editor.buffer.update(cx, |buffer, cx| {
13537            let ix = buffer.len(cx);
13538            buffer.edit([(ix..ix, "edited\n")], None, cx);
13539        });
13540    });
13541
13542    // Allow the LSP command to proceed. Because the buffer was edited,
13543    // the second code action will not be run.
13544    drop(lock_guard);
13545    format.await;
13546    editor.update_in(cx, |editor, window, cx| {
13547        assert_eq!(
13548            editor.text(cx),
13549            r#"
13550                applied-code-action-1-command
13551                applied-code-action-1-edit
13552                applied-formatting
13553                one
13554                two
13555                three
13556                edited
13557            "#
13558            .unindent()
13559        );
13560
13561        // The manual edit is undone first, because it is the last thing the user did
13562        // (even though the command completed afterwards).
13563        editor.undo(&Default::default(), window, cx);
13564        assert_eq!(
13565            editor.text(cx),
13566            r#"
13567                applied-code-action-1-command
13568                applied-code-action-1-edit
13569                applied-formatting
13570                one
13571                two
13572                three
13573            "#
13574            .unindent()
13575        );
13576
13577        // All the formatting (including the command, which completed after the manual edit)
13578        // is undone together.
13579        editor.undo(&Default::default(), window, cx);
13580        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13581    });
13582}
13583
13584#[gpui::test]
13585async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13586    init_test(cx, |settings| {
13587        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13588            settings::LanguageServerFormatterSpecifier::Current,
13589        )]))
13590    });
13591
13592    let fs = FakeFs::new(cx.executor());
13593    fs.insert_file(path!("/file.ts"), Default::default()).await;
13594
13595    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13596
13597    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13598    language_registry.add(Arc::new(Language::new(
13599        LanguageConfig {
13600            name: "TypeScript".into(),
13601            matcher: LanguageMatcher {
13602                path_suffixes: vec!["ts".to_string()],
13603                ..Default::default()
13604            },
13605            ..LanguageConfig::default()
13606        },
13607        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13608    )));
13609    update_test_language_settings(cx, |settings| {
13610        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13611    });
13612    let mut fake_servers = language_registry.register_fake_lsp(
13613        "TypeScript",
13614        FakeLspAdapter {
13615            capabilities: lsp::ServerCapabilities {
13616                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13617                ..Default::default()
13618            },
13619            ..Default::default()
13620        },
13621    );
13622
13623    let buffer = project
13624        .update(cx, |project, cx| {
13625            project.open_local_buffer(path!("/file.ts"), cx)
13626        })
13627        .await
13628        .unwrap();
13629
13630    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13631    let (editor, cx) = cx.add_window_view(|window, cx| {
13632        build_editor_with_project(project.clone(), buffer, window, cx)
13633    });
13634    editor.update_in(cx, |editor, window, cx| {
13635        editor.set_text(
13636            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13637            window,
13638            cx,
13639        )
13640    });
13641
13642    let fake_server = fake_servers.next().await.unwrap();
13643
13644    let format = editor
13645        .update_in(cx, |editor, window, cx| {
13646            editor.perform_code_action_kind(
13647                project.clone(),
13648                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13649                window,
13650                cx,
13651            )
13652        })
13653        .unwrap();
13654    fake_server
13655        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13656            assert_eq!(
13657                params.text_document.uri,
13658                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13659            );
13660            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13661                lsp::CodeAction {
13662                    title: "Organize Imports".to_string(),
13663                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13664                    edit: Some(lsp::WorkspaceEdit {
13665                        changes: Some(
13666                            [(
13667                                params.text_document.uri.clone(),
13668                                vec![lsp::TextEdit::new(
13669                                    lsp::Range::new(
13670                                        lsp::Position::new(1, 0),
13671                                        lsp::Position::new(2, 0),
13672                                    ),
13673                                    "".to_string(),
13674                                )],
13675                            )]
13676                            .into_iter()
13677                            .collect(),
13678                        ),
13679                        ..Default::default()
13680                    }),
13681                    ..Default::default()
13682                },
13683            )]))
13684        })
13685        .next()
13686        .await;
13687    format.await;
13688    assert_eq!(
13689        editor.update(cx, |editor, cx| editor.text(cx)),
13690        "import { a } from 'module';\n\nconst x = a;\n"
13691    );
13692
13693    editor.update_in(cx, |editor, window, cx| {
13694        editor.set_text(
13695            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13696            window,
13697            cx,
13698        )
13699    });
13700    // Ensure we don't lock if code action hangs.
13701    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13702        move |params, _| async move {
13703            assert_eq!(
13704                params.text_document.uri,
13705                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13706            );
13707            futures::future::pending::<()>().await;
13708            unreachable!()
13709        },
13710    );
13711    let format = editor
13712        .update_in(cx, |editor, window, cx| {
13713            editor.perform_code_action_kind(
13714                project,
13715                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13716                window,
13717                cx,
13718            )
13719        })
13720        .unwrap();
13721    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13722    format.await;
13723    assert_eq!(
13724        editor.update(cx, |editor, cx| editor.text(cx)),
13725        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13726    );
13727}
13728
13729#[gpui::test]
13730async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13731    init_test(cx, |_| {});
13732
13733    let mut cx = EditorLspTestContext::new_rust(
13734        lsp::ServerCapabilities {
13735            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13736            ..Default::default()
13737        },
13738        cx,
13739    )
13740    .await;
13741
13742    cx.set_state(indoc! {"
13743        one.twoˇ
13744    "});
13745
13746    // The format request takes a long time. When it completes, it inserts
13747    // a newline and an indent before the `.`
13748    cx.lsp
13749        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13750            let executor = cx.background_executor().clone();
13751            async move {
13752                executor.timer(Duration::from_millis(100)).await;
13753                Ok(Some(vec![lsp::TextEdit {
13754                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13755                    new_text: "\n    ".into(),
13756                }]))
13757            }
13758        });
13759
13760    // Submit a format request.
13761    let format_1 = cx
13762        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13763        .unwrap();
13764    cx.executor().run_until_parked();
13765
13766    // Submit a second format request.
13767    let format_2 = cx
13768        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13769        .unwrap();
13770    cx.executor().run_until_parked();
13771
13772    // Wait for both format requests to complete
13773    cx.executor().advance_clock(Duration::from_millis(200));
13774    format_1.await.unwrap();
13775    format_2.await.unwrap();
13776
13777    // The formatting edits only happens once.
13778    cx.assert_editor_state(indoc! {"
13779        one
13780            .twoˇ
13781    "});
13782}
13783
13784#[gpui::test]
13785async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13786    init_test(cx, |settings| {
13787        settings.defaults.formatter = Some(FormatterList::default())
13788    });
13789
13790    let mut cx = EditorLspTestContext::new_rust(
13791        lsp::ServerCapabilities {
13792            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13793            ..Default::default()
13794        },
13795        cx,
13796    )
13797    .await;
13798
13799    // Record which buffer changes have been sent to the language server
13800    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13801    cx.lsp
13802        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13803            let buffer_changes = buffer_changes.clone();
13804            move |params, _| {
13805                buffer_changes.lock().extend(
13806                    params
13807                        .content_changes
13808                        .into_iter()
13809                        .map(|e| (e.range.unwrap(), e.text)),
13810                );
13811            }
13812        });
13813    // Handle formatting requests to the language server.
13814    cx.lsp
13815        .set_request_handler::<lsp::request::Formatting, _, _>({
13816            move |_, _| {
13817                // Insert blank lines between each line of the buffer.
13818                async move {
13819                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13820                    // DidChangedTextDocument to the LSP before sending the formatting request.
13821                    // assert_eq!(
13822                    //     &buffer_changes.lock()[1..],
13823                    //     &[
13824                    //         (
13825                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13826                    //             "".into()
13827                    //         ),
13828                    //         (
13829                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13830                    //             "".into()
13831                    //         ),
13832                    //         (
13833                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13834                    //             "\n".into()
13835                    //         ),
13836                    //     ]
13837                    // );
13838
13839                    Ok(Some(vec![
13840                        lsp::TextEdit {
13841                            range: lsp::Range::new(
13842                                lsp::Position::new(1, 0),
13843                                lsp::Position::new(1, 0),
13844                            ),
13845                            new_text: "\n".into(),
13846                        },
13847                        lsp::TextEdit {
13848                            range: lsp::Range::new(
13849                                lsp::Position::new(2, 0),
13850                                lsp::Position::new(2, 0),
13851                            ),
13852                            new_text: "\n".into(),
13853                        },
13854                    ]))
13855                }
13856            }
13857        });
13858
13859    // Set up a buffer white some trailing whitespace and no trailing newline.
13860    cx.set_state(
13861        &[
13862            "one ",   //
13863            "twoˇ",   //
13864            "three ", //
13865            "four",   //
13866        ]
13867        .join("\n"),
13868    );
13869
13870    // Submit a format request.
13871    let format = cx
13872        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13873        .unwrap();
13874
13875    cx.run_until_parked();
13876    // After formatting the buffer, the trailing whitespace is stripped,
13877    // a newline is appended, and the edits provided by the language server
13878    // have been applied.
13879    format.await.unwrap();
13880
13881    cx.assert_editor_state(
13882        &[
13883            "one",   //
13884            "",      //
13885            "twoˇ",  //
13886            "",      //
13887            "three", //
13888            "four",  //
13889            "",      //
13890        ]
13891        .join("\n"),
13892    );
13893
13894    // Undoing the formatting undoes the trailing whitespace removal, the
13895    // trailing newline, and the LSP edits.
13896    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13897    cx.assert_editor_state(
13898        &[
13899            "one ",   //
13900            "twoˇ",   //
13901            "three ", //
13902            "four",   //
13903        ]
13904        .join("\n"),
13905    );
13906}
13907
13908#[gpui::test]
13909async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13910    cx: &mut TestAppContext,
13911) {
13912    init_test(cx, |_| {});
13913
13914    cx.update(|cx| {
13915        cx.update_global::<SettingsStore, _>(|settings, cx| {
13916            settings.update_user_settings(cx, |settings| {
13917                settings.editor.auto_signature_help = Some(true);
13918                settings.editor.hover_popover_delay = Some(DelayMs(300));
13919            });
13920        });
13921    });
13922
13923    let mut cx = EditorLspTestContext::new_rust(
13924        lsp::ServerCapabilities {
13925            signature_help_provider: Some(lsp::SignatureHelpOptions {
13926                ..Default::default()
13927            }),
13928            ..Default::default()
13929        },
13930        cx,
13931    )
13932    .await;
13933
13934    let language = Language::new(
13935        LanguageConfig {
13936            name: "Rust".into(),
13937            brackets: BracketPairConfig {
13938                pairs: vec![
13939                    BracketPair {
13940                        start: "{".to_string(),
13941                        end: "}".to_string(),
13942                        close: true,
13943                        surround: true,
13944                        newline: true,
13945                    },
13946                    BracketPair {
13947                        start: "(".to_string(),
13948                        end: ")".to_string(),
13949                        close: true,
13950                        surround: true,
13951                        newline: true,
13952                    },
13953                    BracketPair {
13954                        start: "/*".to_string(),
13955                        end: " */".to_string(),
13956                        close: true,
13957                        surround: true,
13958                        newline: true,
13959                    },
13960                    BracketPair {
13961                        start: "[".to_string(),
13962                        end: "]".to_string(),
13963                        close: false,
13964                        surround: false,
13965                        newline: true,
13966                    },
13967                    BracketPair {
13968                        start: "\"".to_string(),
13969                        end: "\"".to_string(),
13970                        close: true,
13971                        surround: true,
13972                        newline: false,
13973                    },
13974                    BracketPair {
13975                        start: "<".to_string(),
13976                        end: ">".to_string(),
13977                        close: false,
13978                        surround: true,
13979                        newline: true,
13980                    },
13981                ],
13982                ..Default::default()
13983            },
13984            autoclose_before: "})]".to_string(),
13985            ..Default::default()
13986        },
13987        Some(tree_sitter_rust::LANGUAGE.into()),
13988    );
13989    let language = Arc::new(language);
13990
13991    cx.language_registry().add(language.clone());
13992    cx.update_buffer(|buffer, cx| {
13993        buffer.set_language(Some(language), cx);
13994    });
13995
13996    cx.set_state(
13997        &r#"
13998            fn main() {
13999                sampleˇ
14000            }
14001        "#
14002        .unindent(),
14003    );
14004
14005    cx.update_editor(|editor, window, cx| {
14006        editor.handle_input("(", window, cx);
14007    });
14008    cx.assert_editor_state(
14009        &"
14010            fn main() {
14011                sample(ˇ)
14012            }
14013        "
14014        .unindent(),
14015    );
14016
14017    let mocked_response = lsp::SignatureHelp {
14018        signatures: vec![lsp::SignatureInformation {
14019            label: "fn sample(param1: u8, param2: u8)".to_string(),
14020            documentation: None,
14021            parameters: Some(vec![
14022                lsp::ParameterInformation {
14023                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14024                    documentation: None,
14025                },
14026                lsp::ParameterInformation {
14027                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14028                    documentation: None,
14029                },
14030            ]),
14031            active_parameter: None,
14032        }],
14033        active_signature: Some(0),
14034        active_parameter: Some(0),
14035    };
14036    handle_signature_help_request(&mut cx, mocked_response).await;
14037
14038    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14039        .await;
14040
14041    cx.editor(|editor, _, _| {
14042        let signature_help_state = editor.signature_help_state.popover().cloned();
14043        let signature = signature_help_state.unwrap();
14044        assert_eq!(
14045            signature.signatures[signature.current_signature].label,
14046            "fn sample(param1: u8, param2: u8)"
14047        );
14048    });
14049}
14050
14051#[gpui::test]
14052async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
14053    init_test(cx, |_| {});
14054
14055    let delay_ms = 500;
14056    cx.update(|cx| {
14057        cx.update_global::<SettingsStore, _>(|settings, cx| {
14058            settings.update_user_settings(cx, |settings| {
14059                settings.editor.auto_signature_help = Some(true);
14060                settings.editor.show_signature_help_after_edits = Some(false);
14061                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14062            });
14063        });
14064    });
14065
14066    let mut cx = EditorLspTestContext::new_rust(
14067        lsp::ServerCapabilities {
14068            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14069            ..lsp::ServerCapabilities::default()
14070        },
14071        cx,
14072    )
14073    .await;
14074
14075    let mocked_response = lsp::SignatureHelp {
14076        signatures: vec![lsp::SignatureInformation {
14077            label: "fn sample(param1: u8)".to_string(),
14078            documentation: None,
14079            parameters: Some(vec![lsp::ParameterInformation {
14080                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14081                documentation: None,
14082            }]),
14083            active_parameter: None,
14084        }],
14085        active_signature: Some(0),
14086        active_parameter: Some(0),
14087    };
14088
14089    cx.set_state(indoc! {"
14090        fn main() {
14091            sample(ˇ);
14092        }
14093
14094        fn sample(param1: u8) {}
14095    "});
14096
14097    // Manual trigger should show immediately without delay
14098    cx.update_editor(|editor, window, cx| {
14099        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14100    });
14101    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14102    cx.run_until_parked();
14103    cx.editor(|editor, _, _| {
14104        assert!(
14105            editor.signature_help_state.is_shown(),
14106            "Manual trigger should show signature help without delay"
14107        );
14108    });
14109
14110    cx.update_editor(|editor, _, cx| {
14111        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14112    });
14113    cx.run_until_parked();
14114    cx.editor(|editor, _, _| {
14115        assert!(!editor.signature_help_state.is_shown());
14116    });
14117
14118    // Auto trigger (cursor movement into brackets) should respect delay
14119    cx.set_state(indoc! {"
14120        fn main() {
14121            sampleˇ();
14122        }
14123
14124        fn sample(param1: u8) {}
14125    "});
14126    cx.update_editor(|editor, window, cx| {
14127        editor.move_right(&MoveRight, window, cx);
14128    });
14129    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14130    cx.run_until_parked();
14131    cx.editor(|editor, _, _| {
14132        assert!(
14133            !editor.signature_help_state.is_shown(),
14134            "Auto trigger should wait for delay before showing signature help"
14135        );
14136    });
14137
14138    cx.executor()
14139        .advance_clock(Duration::from_millis(delay_ms + 50));
14140    cx.run_until_parked();
14141    cx.editor(|editor, _, _| {
14142        assert!(
14143            editor.signature_help_state.is_shown(),
14144            "Auto trigger should show signature help after delay elapsed"
14145        );
14146    });
14147}
14148
14149#[gpui::test]
14150async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
14151    init_test(cx, |_| {});
14152
14153    let delay_ms = 500;
14154    cx.update(|cx| {
14155        cx.update_global::<SettingsStore, _>(|settings, cx| {
14156            settings.update_user_settings(cx, |settings| {
14157                settings.editor.auto_signature_help = Some(false);
14158                settings.editor.show_signature_help_after_edits = Some(true);
14159                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14160            });
14161        });
14162    });
14163
14164    let mut cx = EditorLspTestContext::new_rust(
14165        lsp::ServerCapabilities {
14166            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14167            ..lsp::ServerCapabilities::default()
14168        },
14169        cx,
14170    )
14171    .await;
14172
14173    let language = Arc::new(Language::new(
14174        LanguageConfig {
14175            name: "Rust".into(),
14176            brackets: BracketPairConfig {
14177                pairs: vec![BracketPair {
14178                    start: "(".to_string(),
14179                    end: ")".to_string(),
14180                    close: true,
14181                    surround: true,
14182                    newline: true,
14183                }],
14184                ..BracketPairConfig::default()
14185            },
14186            autoclose_before: "})".to_string(),
14187            ..LanguageConfig::default()
14188        },
14189        Some(tree_sitter_rust::LANGUAGE.into()),
14190    ));
14191    cx.language_registry().add(language.clone());
14192    cx.update_buffer(|buffer, cx| {
14193        buffer.set_language(Some(language), cx);
14194    });
14195
14196    let mocked_response = lsp::SignatureHelp {
14197        signatures: vec![lsp::SignatureInformation {
14198            label: "fn sample(param1: u8)".to_string(),
14199            documentation: None,
14200            parameters: Some(vec![lsp::ParameterInformation {
14201                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14202                documentation: None,
14203            }]),
14204            active_parameter: None,
14205        }],
14206        active_signature: Some(0),
14207        active_parameter: Some(0),
14208    };
14209
14210    cx.set_state(indoc! {"
14211        fn main() {
14212            sampleˇ
14213        }
14214    "});
14215
14216    // Typing bracket should show signature help immediately without delay
14217    cx.update_editor(|editor, window, cx| {
14218        editor.handle_input("(", window, cx);
14219    });
14220    handle_signature_help_request(&mut cx, mocked_response).await;
14221    cx.run_until_parked();
14222    cx.editor(|editor, _, _| {
14223        assert!(
14224            editor.signature_help_state.is_shown(),
14225            "show_signature_help_after_edits should show signature help without delay"
14226        );
14227    });
14228}
14229
14230#[gpui::test]
14231async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
14232    init_test(cx, |_| {});
14233
14234    cx.update(|cx| {
14235        cx.update_global::<SettingsStore, _>(|settings, cx| {
14236            settings.update_user_settings(cx, |settings| {
14237                settings.editor.auto_signature_help = Some(false);
14238                settings.editor.show_signature_help_after_edits = Some(false);
14239            });
14240        });
14241    });
14242
14243    let mut cx = EditorLspTestContext::new_rust(
14244        lsp::ServerCapabilities {
14245            signature_help_provider: Some(lsp::SignatureHelpOptions {
14246                ..Default::default()
14247            }),
14248            ..Default::default()
14249        },
14250        cx,
14251    )
14252    .await;
14253
14254    let language = Language::new(
14255        LanguageConfig {
14256            name: "Rust".into(),
14257            brackets: BracketPairConfig {
14258                pairs: vec![
14259                    BracketPair {
14260                        start: "{".to_string(),
14261                        end: "}".to_string(),
14262                        close: true,
14263                        surround: true,
14264                        newline: true,
14265                    },
14266                    BracketPair {
14267                        start: "(".to_string(),
14268                        end: ")".to_string(),
14269                        close: true,
14270                        surround: true,
14271                        newline: true,
14272                    },
14273                    BracketPair {
14274                        start: "/*".to_string(),
14275                        end: " */".to_string(),
14276                        close: true,
14277                        surround: true,
14278                        newline: true,
14279                    },
14280                    BracketPair {
14281                        start: "[".to_string(),
14282                        end: "]".to_string(),
14283                        close: false,
14284                        surround: false,
14285                        newline: true,
14286                    },
14287                    BracketPair {
14288                        start: "\"".to_string(),
14289                        end: "\"".to_string(),
14290                        close: true,
14291                        surround: true,
14292                        newline: false,
14293                    },
14294                    BracketPair {
14295                        start: "<".to_string(),
14296                        end: ">".to_string(),
14297                        close: false,
14298                        surround: true,
14299                        newline: true,
14300                    },
14301                ],
14302                ..Default::default()
14303            },
14304            autoclose_before: "})]".to_string(),
14305            ..Default::default()
14306        },
14307        Some(tree_sitter_rust::LANGUAGE.into()),
14308    );
14309    let language = Arc::new(language);
14310
14311    cx.language_registry().add(language.clone());
14312    cx.update_buffer(|buffer, cx| {
14313        buffer.set_language(Some(language), cx);
14314    });
14315
14316    // Ensure that signature_help is not called when no signature help is enabled.
14317    cx.set_state(
14318        &r#"
14319            fn main() {
14320                sampleˇ
14321            }
14322        "#
14323        .unindent(),
14324    );
14325    cx.update_editor(|editor, window, cx| {
14326        editor.handle_input("(", window, cx);
14327    });
14328    cx.assert_editor_state(
14329        &"
14330            fn main() {
14331                sample(ˇ)
14332            }
14333        "
14334        .unindent(),
14335    );
14336    cx.editor(|editor, _, _| {
14337        assert!(editor.signature_help_state.task().is_none());
14338    });
14339
14340    let mocked_response = lsp::SignatureHelp {
14341        signatures: vec![lsp::SignatureInformation {
14342            label: "fn sample(param1: u8, param2: u8)".to_string(),
14343            documentation: None,
14344            parameters: Some(vec![
14345                lsp::ParameterInformation {
14346                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14347                    documentation: None,
14348                },
14349                lsp::ParameterInformation {
14350                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14351                    documentation: None,
14352                },
14353            ]),
14354            active_parameter: None,
14355        }],
14356        active_signature: Some(0),
14357        active_parameter: Some(0),
14358    };
14359
14360    // Ensure that signature_help is called when enabled afte edits
14361    cx.update(|_, cx| {
14362        cx.update_global::<SettingsStore, _>(|settings, cx| {
14363            settings.update_user_settings(cx, |settings| {
14364                settings.editor.auto_signature_help = Some(false);
14365                settings.editor.show_signature_help_after_edits = Some(true);
14366            });
14367        });
14368    });
14369    cx.set_state(
14370        &r#"
14371            fn main() {
14372                sampleˇ
14373            }
14374        "#
14375        .unindent(),
14376    );
14377    cx.update_editor(|editor, window, cx| {
14378        editor.handle_input("(", window, cx);
14379    });
14380    cx.assert_editor_state(
14381        &"
14382            fn main() {
14383                sample(ˇ)
14384            }
14385        "
14386        .unindent(),
14387    );
14388    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14389    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14390        .await;
14391    cx.update_editor(|editor, _, _| {
14392        let signature_help_state = editor.signature_help_state.popover().cloned();
14393        assert!(signature_help_state.is_some());
14394        let signature = signature_help_state.unwrap();
14395        assert_eq!(
14396            signature.signatures[signature.current_signature].label,
14397            "fn sample(param1: u8, param2: u8)"
14398        );
14399        editor.signature_help_state = SignatureHelpState::default();
14400    });
14401
14402    // Ensure that signature_help is called when auto signature help override is enabled
14403    cx.update(|_, cx| {
14404        cx.update_global::<SettingsStore, _>(|settings, cx| {
14405            settings.update_user_settings(cx, |settings| {
14406                settings.editor.auto_signature_help = Some(true);
14407                settings.editor.show_signature_help_after_edits = Some(false);
14408            });
14409        });
14410    });
14411    cx.set_state(
14412        &r#"
14413            fn main() {
14414                sampleˇ
14415            }
14416        "#
14417        .unindent(),
14418    );
14419    cx.update_editor(|editor, window, cx| {
14420        editor.handle_input("(", window, cx);
14421    });
14422    cx.assert_editor_state(
14423        &"
14424            fn main() {
14425                sample(ˇ)
14426            }
14427        "
14428        .unindent(),
14429    );
14430    handle_signature_help_request(&mut cx, mocked_response).await;
14431    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14432        .await;
14433    cx.editor(|editor, _, _| {
14434        let signature_help_state = editor.signature_help_state.popover().cloned();
14435        assert!(signature_help_state.is_some());
14436        let signature = signature_help_state.unwrap();
14437        assert_eq!(
14438            signature.signatures[signature.current_signature].label,
14439            "fn sample(param1: u8, param2: u8)"
14440        );
14441    });
14442}
14443
14444#[gpui::test]
14445async fn test_signature_help(cx: &mut TestAppContext) {
14446    init_test(cx, |_| {});
14447    cx.update(|cx| {
14448        cx.update_global::<SettingsStore, _>(|settings, cx| {
14449            settings.update_user_settings(cx, |settings| {
14450                settings.editor.auto_signature_help = Some(true);
14451            });
14452        });
14453    });
14454
14455    let mut cx = EditorLspTestContext::new_rust(
14456        lsp::ServerCapabilities {
14457            signature_help_provider: Some(lsp::SignatureHelpOptions {
14458                ..Default::default()
14459            }),
14460            ..Default::default()
14461        },
14462        cx,
14463    )
14464    .await;
14465
14466    // A test that directly calls `show_signature_help`
14467    cx.update_editor(|editor, window, cx| {
14468        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14469    });
14470
14471    let mocked_response = lsp::SignatureHelp {
14472        signatures: vec![lsp::SignatureInformation {
14473            label: "fn sample(param1: u8, param2: u8)".to_string(),
14474            documentation: None,
14475            parameters: Some(vec![
14476                lsp::ParameterInformation {
14477                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14478                    documentation: None,
14479                },
14480                lsp::ParameterInformation {
14481                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14482                    documentation: None,
14483                },
14484            ]),
14485            active_parameter: None,
14486        }],
14487        active_signature: Some(0),
14488        active_parameter: Some(0),
14489    };
14490    handle_signature_help_request(&mut cx, mocked_response).await;
14491
14492    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14493        .await;
14494
14495    cx.editor(|editor, _, _| {
14496        let signature_help_state = editor.signature_help_state.popover().cloned();
14497        assert!(signature_help_state.is_some());
14498        let signature = signature_help_state.unwrap();
14499        assert_eq!(
14500            signature.signatures[signature.current_signature].label,
14501            "fn sample(param1: u8, param2: u8)"
14502        );
14503    });
14504
14505    // When exiting outside from inside the brackets, `signature_help` is closed.
14506    cx.set_state(indoc! {"
14507        fn main() {
14508            sample(ˇ);
14509        }
14510
14511        fn sample(param1: u8, param2: u8) {}
14512    "});
14513
14514    cx.update_editor(|editor, window, cx| {
14515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14516            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
14517        });
14518    });
14519
14520    let mocked_response = lsp::SignatureHelp {
14521        signatures: Vec::new(),
14522        active_signature: None,
14523        active_parameter: None,
14524    };
14525    handle_signature_help_request(&mut cx, mocked_response).await;
14526
14527    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14528        .await;
14529
14530    cx.editor(|editor, _, _| {
14531        assert!(!editor.signature_help_state.is_shown());
14532    });
14533
14534    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
14535    cx.set_state(indoc! {"
14536        fn main() {
14537            sample(ˇ);
14538        }
14539
14540        fn sample(param1: u8, param2: u8) {}
14541    "});
14542
14543    let mocked_response = lsp::SignatureHelp {
14544        signatures: vec![lsp::SignatureInformation {
14545            label: "fn sample(param1: u8, param2: u8)".to_string(),
14546            documentation: None,
14547            parameters: Some(vec![
14548                lsp::ParameterInformation {
14549                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14550                    documentation: None,
14551                },
14552                lsp::ParameterInformation {
14553                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14554                    documentation: None,
14555                },
14556            ]),
14557            active_parameter: None,
14558        }],
14559        active_signature: Some(0),
14560        active_parameter: Some(0),
14561    };
14562    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14563    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14564        .await;
14565    cx.editor(|editor, _, _| {
14566        assert!(editor.signature_help_state.is_shown());
14567    });
14568
14569    // Restore the popover with more parameter input
14570    cx.set_state(indoc! {"
14571        fn main() {
14572            sample(param1, param2ˇ);
14573        }
14574
14575        fn sample(param1: u8, param2: u8) {}
14576    "});
14577
14578    let mocked_response = lsp::SignatureHelp {
14579        signatures: vec![lsp::SignatureInformation {
14580            label: "fn sample(param1: u8, param2: u8)".to_string(),
14581            documentation: None,
14582            parameters: Some(vec![
14583                lsp::ParameterInformation {
14584                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14585                    documentation: None,
14586                },
14587                lsp::ParameterInformation {
14588                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14589                    documentation: None,
14590                },
14591            ]),
14592            active_parameter: None,
14593        }],
14594        active_signature: Some(0),
14595        active_parameter: Some(1),
14596    };
14597    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14598    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14599        .await;
14600
14601    // When selecting a range, the popover is gone.
14602    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14603    cx.update_editor(|editor, window, cx| {
14604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14605            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14606        })
14607    });
14608    cx.assert_editor_state(indoc! {"
14609        fn main() {
14610            sample(param1, «ˇparam2»);
14611        }
14612
14613        fn sample(param1: u8, param2: u8) {}
14614    "});
14615    cx.editor(|editor, _, _| {
14616        assert!(!editor.signature_help_state.is_shown());
14617    });
14618
14619    // When unselecting again, the popover is back if within the brackets.
14620    cx.update_editor(|editor, window, cx| {
14621        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14622            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14623        })
14624    });
14625    cx.assert_editor_state(indoc! {"
14626        fn main() {
14627            sample(param1, ˇparam2);
14628        }
14629
14630        fn sample(param1: u8, param2: u8) {}
14631    "});
14632    handle_signature_help_request(&mut cx, mocked_response).await;
14633    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14634        .await;
14635    cx.editor(|editor, _, _| {
14636        assert!(editor.signature_help_state.is_shown());
14637    });
14638
14639    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14640    cx.update_editor(|editor, window, cx| {
14641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14642            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14643            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14644        })
14645    });
14646    cx.assert_editor_state(indoc! {"
14647        fn main() {
14648            sample(param1, ˇparam2);
14649        }
14650
14651        fn sample(param1: u8, param2: u8) {}
14652    "});
14653
14654    let mocked_response = lsp::SignatureHelp {
14655        signatures: vec![lsp::SignatureInformation {
14656            label: "fn sample(param1: u8, param2: u8)".to_string(),
14657            documentation: None,
14658            parameters: Some(vec![
14659                lsp::ParameterInformation {
14660                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14661                    documentation: None,
14662                },
14663                lsp::ParameterInformation {
14664                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14665                    documentation: None,
14666                },
14667            ]),
14668            active_parameter: None,
14669        }],
14670        active_signature: Some(0),
14671        active_parameter: Some(1),
14672    };
14673    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14674    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14675        .await;
14676    cx.update_editor(|editor, _, cx| {
14677        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14678    });
14679    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14680        .await;
14681    cx.update_editor(|editor, window, cx| {
14682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14683            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14684        })
14685    });
14686    cx.assert_editor_state(indoc! {"
14687        fn main() {
14688            sample(param1, «ˇparam2»);
14689        }
14690
14691        fn sample(param1: u8, param2: u8) {}
14692    "});
14693    cx.update_editor(|editor, window, cx| {
14694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14695            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14696        })
14697    });
14698    cx.assert_editor_state(indoc! {"
14699        fn main() {
14700            sample(param1, ˇparam2);
14701        }
14702
14703        fn sample(param1: u8, param2: u8) {}
14704    "});
14705    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14706        .await;
14707}
14708
14709#[gpui::test]
14710async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14711    init_test(cx, |_| {});
14712
14713    let mut cx = EditorLspTestContext::new_rust(
14714        lsp::ServerCapabilities {
14715            signature_help_provider: Some(lsp::SignatureHelpOptions {
14716                ..Default::default()
14717            }),
14718            ..Default::default()
14719        },
14720        cx,
14721    )
14722    .await;
14723
14724    cx.set_state(indoc! {"
14725        fn main() {
14726            overloadedˇ
14727        }
14728    "});
14729
14730    cx.update_editor(|editor, window, cx| {
14731        editor.handle_input("(", window, cx);
14732        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14733    });
14734
14735    // Mock response with 3 signatures
14736    let mocked_response = lsp::SignatureHelp {
14737        signatures: vec![
14738            lsp::SignatureInformation {
14739                label: "fn overloaded(x: i32)".to_string(),
14740                documentation: None,
14741                parameters: Some(vec![lsp::ParameterInformation {
14742                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14743                    documentation: None,
14744                }]),
14745                active_parameter: None,
14746            },
14747            lsp::SignatureInformation {
14748                label: "fn overloaded(x: i32, y: i32)".to_string(),
14749                documentation: None,
14750                parameters: Some(vec![
14751                    lsp::ParameterInformation {
14752                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14753                        documentation: None,
14754                    },
14755                    lsp::ParameterInformation {
14756                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14757                        documentation: None,
14758                    },
14759                ]),
14760                active_parameter: None,
14761            },
14762            lsp::SignatureInformation {
14763                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14764                documentation: None,
14765                parameters: Some(vec![
14766                    lsp::ParameterInformation {
14767                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14768                        documentation: None,
14769                    },
14770                    lsp::ParameterInformation {
14771                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14772                        documentation: None,
14773                    },
14774                    lsp::ParameterInformation {
14775                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14776                        documentation: None,
14777                    },
14778                ]),
14779                active_parameter: None,
14780            },
14781        ],
14782        active_signature: Some(1),
14783        active_parameter: Some(0),
14784    };
14785    handle_signature_help_request(&mut cx, mocked_response).await;
14786
14787    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14788        .await;
14789
14790    // Verify we have multiple signatures and the right one is selected
14791    cx.editor(|editor, _, _| {
14792        let popover = editor.signature_help_state.popover().cloned().unwrap();
14793        assert_eq!(popover.signatures.len(), 3);
14794        // active_signature was 1, so that should be the current
14795        assert_eq!(popover.current_signature, 1);
14796        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14797        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14798        assert_eq!(
14799            popover.signatures[2].label,
14800            "fn overloaded(x: i32, y: i32, z: i32)"
14801        );
14802    });
14803
14804    // Test navigation functionality
14805    cx.update_editor(|editor, window, cx| {
14806        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14807    });
14808
14809    cx.editor(|editor, _, _| {
14810        let popover = editor.signature_help_state.popover().cloned().unwrap();
14811        assert_eq!(popover.current_signature, 2);
14812    });
14813
14814    // Test wrap around
14815    cx.update_editor(|editor, window, cx| {
14816        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14817    });
14818
14819    cx.editor(|editor, _, _| {
14820        let popover = editor.signature_help_state.popover().cloned().unwrap();
14821        assert_eq!(popover.current_signature, 0);
14822    });
14823
14824    // Test previous navigation
14825    cx.update_editor(|editor, window, cx| {
14826        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14827    });
14828
14829    cx.editor(|editor, _, _| {
14830        let popover = editor.signature_help_state.popover().cloned().unwrap();
14831        assert_eq!(popover.current_signature, 2);
14832    });
14833}
14834
14835#[gpui::test]
14836async fn test_completion_mode(cx: &mut TestAppContext) {
14837    init_test(cx, |_| {});
14838    let mut cx = EditorLspTestContext::new_rust(
14839        lsp::ServerCapabilities {
14840            completion_provider: Some(lsp::CompletionOptions {
14841                resolve_provider: Some(true),
14842                ..Default::default()
14843            }),
14844            ..Default::default()
14845        },
14846        cx,
14847    )
14848    .await;
14849
14850    struct Run {
14851        run_description: &'static str,
14852        initial_state: String,
14853        buffer_marked_text: String,
14854        completion_label: &'static str,
14855        completion_text: &'static str,
14856        expected_with_insert_mode: String,
14857        expected_with_replace_mode: String,
14858        expected_with_replace_subsequence_mode: String,
14859        expected_with_replace_suffix_mode: String,
14860    }
14861
14862    let runs = [
14863        Run {
14864            run_description: "Start of word matches completion text",
14865            initial_state: "before ediˇ after".into(),
14866            buffer_marked_text: "before <edi|> after".into(),
14867            completion_label: "editor",
14868            completion_text: "editor",
14869            expected_with_insert_mode: "before editorˇ after".into(),
14870            expected_with_replace_mode: "before editorˇ after".into(),
14871            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14872            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14873        },
14874        Run {
14875            run_description: "Accept same text at the middle of the word",
14876            initial_state: "before ediˇtor after".into(),
14877            buffer_marked_text: "before <edi|tor> after".into(),
14878            completion_label: "editor",
14879            completion_text: "editor",
14880            expected_with_insert_mode: "before editorˇtor after".into(),
14881            expected_with_replace_mode: "before editorˇ after".into(),
14882            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14883            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14884        },
14885        Run {
14886            run_description: "End of word matches completion text -- cursor at end",
14887            initial_state: "before torˇ after".into(),
14888            buffer_marked_text: "before <tor|> after".into(),
14889            completion_label: "editor",
14890            completion_text: "editor",
14891            expected_with_insert_mode: "before editorˇ after".into(),
14892            expected_with_replace_mode: "before editorˇ after".into(),
14893            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14894            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14895        },
14896        Run {
14897            run_description: "End of word matches completion text -- cursor at start",
14898            initial_state: "before ˇtor after".into(),
14899            buffer_marked_text: "before <|tor> after".into(),
14900            completion_label: "editor",
14901            completion_text: "editor",
14902            expected_with_insert_mode: "before editorˇtor after".into(),
14903            expected_with_replace_mode: "before editorˇ after".into(),
14904            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14905            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14906        },
14907        Run {
14908            run_description: "Prepend text containing whitespace",
14909            initial_state: "pˇfield: bool".into(),
14910            buffer_marked_text: "<p|field>: bool".into(),
14911            completion_label: "pub ",
14912            completion_text: "pub ",
14913            expected_with_insert_mode: "pub ˇfield: bool".into(),
14914            expected_with_replace_mode: "pub ˇ: bool".into(),
14915            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14916            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14917        },
14918        Run {
14919            run_description: "Add element to start of list",
14920            initial_state: "[element_ˇelement_2]".into(),
14921            buffer_marked_text: "[<element_|element_2>]".into(),
14922            completion_label: "element_1",
14923            completion_text: "element_1",
14924            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14925            expected_with_replace_mode: "[element_1ˇ]".into(),
14926            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14927            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14928        },
14929        Run {
14930            run_description: "Add element to start of list -- first and second elements are equal",
14931            initial_state: "[elˇelement]".into(),
14932            buffer_marked_text: "[<el|element>]".into(),
14933            completion_label: "element",
14934            completion_text: "element",
14935            expected_with_insert_mode: "[elementˇelement]".into(),
14936            expected_with_replace_mode: "[elementˇ]".into(),
14937            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14938            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14939        },
14940        Run {
14941            run_description: "Ends with matching suffix",
14942            initial_state: "SubˇError".into(),
14943            buffer_marked_text: "<Sub|Error>".into(),
14944            completion_label: "SubscriptionError",
14945            completion_text: "SubscriptionError",
14946            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14947            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14948            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14949            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14950        },
14951        Run {
14952            run_description: "Suffix is a subsequence -- contiguous",
14953            initial_state: "SubˇErr".into(),
14954            buffer_marked_text: "<Sub|Err>".into(),
14955            completion_label: "SubscriptionError",
14956            completion_text: "SubscriptionError",
14957            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14958            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14959            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14960            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14961        },
14962        Run {
14963            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14964            initial_state: "Suˇscrirr".into(),
14965            buffer_marked_text: "<Su|scrirr>".into(),
14966            completion_label: "SubscriptionError",
14967            completion_text: "SubscriptionError",
14968            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14969            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14970            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14971            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14972        },
14973        Run {
14974            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14975            initial_state: "foo(indˇix)".into(),
14976            buffer_marked_text: "foo(<ind|ix>)".into(),
14977            completion_label: "node_index",
14978            completion_text: "node_index",
14979            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14980            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14981            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14982            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14983        },
14984        Run {
14985            run_description: "Replace range ends before cursor - should extend to cursor",
14986            initial_state: "before editˇo after".into(),
14987            buffer_marked_text: "before <{ed}>it|o after".into(),
14988            completion_label: "editor",
14989            completion_text: "editor",
14990            expected_with_insert_mode: "before editorˇo after".into(),
14991            expected_with_replace_mode: "before editorˇo after".into(),
14992            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14993            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14994        },
14995        Run {
14996            run_description: "Uses label for suffix matching",
14997            initial_state: "before ediˇtor after".into(),
14998            buffer_marked_text: "before <edi|tor> after".into(),
14999            completion_label: "editor",
15000            completion_text: "editor()",
15001            expected_with_insert_mode: "before editor()ˇtor after".into(),
15002            expected_with_replace_mode: "before editor()ˇ after".into(),
15003            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
15004            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
15005        },
15006        Run {
15007            run_description: "Case insensitive subsequence and suffix matching",
15008            initial_state: "before EDiˇtoR after".into(),
15009            buffer_marked_text: "before <EDi|toR> after".into(),
15010            completion_label: "editor",
15011            completion_text: "editor",
15012            expected_with_insert_mode: "before editorˇtoR after".into(),
15013            expected_with_replace_mode: "before editorˇ after".into(),
15014            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15015            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15016        },
15017    ];
15018
15019    for run in runs {
15020        let run_variations = [
15021            (LspInsertMode::Insert, run.expected_with_insert_mode),
15022            (LspInsertMode::Replace, run.expected_with_replace_mode),
15023            (
15024                LspInsertMode::ReplaceSubsequence,
15025                run.expected_with_replace_subsequence_mode,
15026            ),
15027            (
15028                LspInsertMode::ReplaceSuffix,
15029                run.expected_with_replace_suffix_mode,
15030            ),
15031        ];
15032
15033        for (lsp_insert_mode, expected_text) in run_variations {
15034            eprintln!(
15035                "run = {:?}, mode = {lsp_insert_mode:.?}",
15036                run.run_description,
15037            );
15038
15039            update_test_language_settings(&mut cx, |settings| {
15040                settings.defaults.completions = Some(CompletionSettingsContent {
15041                    lsp_insert_mode: Some(lsp_insert_mode),
15042                    words: Some(WordsCompletionMode::Disabled),
15043                    words_min_length: Some(0),
15044                    ..Default::default()
15045                });
15046            });
15047
15048            cx.set_state(&run.initial_state);
15049
15050            // Set up resolve handler before showing completions, since resolve may be
15051            // triggered when menu becomes visible (for documentation), not just on confirm.
15052            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
15053                move |_, _, _| async move {
15054                    Ok(lsp::CompletionItem {
15055                        additional_text_edits: None,
15056                        ..Default::default()
15057                    })
15058                },
15059            );
15060
15061            cx.update_editor(|editor, window, cx| {
15062                editor.show_completions(&ShowCompletions, window, cx);
15063            });
15064
15065            let counter = Arc::new(AtomicUsize::new(0));
15066            handle_completion_request_with_insert_and_replace(
15067                &mut cx,
15068                &run.buffer_marked_text,
15069                vec![(run.completion_label, run.completion_text)],
15070                counter.clone(),
15071            )
15072            .await;
15073            cx.condition(|editor, _| editor.context_menu_visible())
15074                .await;
15075            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15076
15077            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15078                editor
15079                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
15080                    .unwrap()
15081            });
15082            cx.assert_editor_state(&expected_text);
15083            apply_additional_edits.await.unwrap();
15084        }
15085    }
15086}
15087
15088#[gpui::test]
15089async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
15090    init_test(cx, |_| {});
15091    let mut cx = EditorLspTestContext::new_rust(
15092        lsp::ServerCapabilities {
15093            completion_provider: Some(lsp::CompletionOptions {
15094                resolve_provider: Some(true),
15095                ..Default::default()
15096            }),
15097            ..Default::default()
15098        },
15099        cx,
15100    )
15101    .await;
15102
15103    let initial_state = "SubˇError";
15104    let buffer_marked_text = "<Sub|Error>";
15105    let completion_text = "SubscriptionError";
15106    let expected_with_insert_mode = "SubscriptionErrorˇError";
15107    let expected_with_replace_mode = "SubscriptionErrorˇ";
15108
15109    update_test_language_settings(&mut cx, |settings| {
15110        settings.defaults.completions = Some(CompletionSettingsContent {
15111            words: Some(WordsCompletionMode::Disabled),
15112            words_min_length: Some(0),
15113            // set the opposite here to ensure that the action is overriding the default behavior
15114            lsp_insert_mode: Some(LspInsertMode::Insert),
15115            ..Default::default()
15116        });
15117    });
15118
15119    cx.set_state(initial_state);
15120    cx.update_editor(|editor, window, cx| {
15121        editor.show_completions(&ShowCompletions, window, cx);
15122    });
15123
15124    let counter = Arc::new(AtomicUsize::new(0));
15125    handle_completion_request_with_insert_and_replace(
15126        &mut cx,
15127        buffer_marked_text,
15128        vec![(completion_text, completion_text)],
15129        counter.clone(),
15130    )
15131    .await;
15132    cx.condition(|editor, _| editor.context_menu_visible())
15133        .await;
15134    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15135
15136    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15137        editor
15138            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15139            .unwrap()
15140    });
15141    cx.assert_editor_state(expected_with_replace_mode);
15142    handle_resolve_completion_request(&mut cx, None).await;
15143    apply_additional_edits.await.unwrap();
15144
15145    update_test_language_settings(&mut cx, |settings| {
15146        settings.defaults.completions = Some(CompletionSettingsContent {
15147            words: Some(WordsCompletionMode::Disabled),
15148            words_min_length: Some(0),
15149            // set the opposite here to ensure that the action is overriding the default behavior
15150            lsp_insert_mode: Some(LspInsertMode::Replace),
15151            ..Default::default()
15152        });
15153    });
15154
15155    cx.set_state(initial_state);
15156    cx.update_editor(|editor, window, cx| {
15157        editor.show_completions(&ShowCompletions, window, cx);
15158    });
15159    handle_completion_request_with_insert_and_replace(
15160        &mut cx,
15161        buffer_marked_text,
15162        vec![(completion_text, completion_text)],
15163        counter.clone(),
15164    )
15165    .await;
15166    cx.condition(|editor, _| editor.context_menu_visible())
15167        .await;
15168    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15169
15170    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15171        editor
15172            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
15173            .unwrap()
15174    });
15175    cx.assert_editor_state(expected_with_insert_mode);
15176    handle_resolve_completion_request(&mut cx, None).await;
15177    apply_additional_edits.await.unwrap();
15178}
15179
15180#[gpui::test]
15181async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
15182    init_test(cx, |_| {});
15183    let mut cx = EditorLspTestContext::new_rust(
15184        lsp::ServerCapabilities {
15185            completion_provider: Some(lsp::CompletionOptions {
15186                resolve_provider: Some(true),
15187                ..Default::default()
15188            }),
15189            ..Default::default()
15190        },
15191        cx,
15192    )
15193    .await;
15194
15195    // scenario: surrounding text matches completion text
15196    let completion_text = "to_offset";
15197    let initial_state = indoc! {"
15198        1. buf.to_offˇsuffix
15199        2. buf.to_offˇsuf
15200        3. buf.to_offˇfix
15201        4. buf.to_offˇ
15202        5. into_offˇensive
15203        6. ˇsuffix
15204        7. let ˇ //
15205        8. aaˇzz
15206        9. buf.to_off«zzzzzˇ»suffix
15207        10. buf.«ˇzzzzz»suffix
15208        11. to_off«ˇzzzzz»
15209
15210        buf.to_offˇsuffix  // newest cursor
15211    "};
15212    let completion_marked_buffer = indoc! {"
15213        1. buf.to_offsuffix
15214        2. buf.to_offsuf
15215        3. buf.to_offfix
15216        4. buf.to_off
15217        5. into_offensive
15218        6. suffix
15219        7. let  //
15220        8. aazz
15221        9. buf.to_offzzzzzsuffix
15222        10. buf.zzzzzsuffix
15223        11. to_offzzzzz
15224
15225        buf.<to_off|suffix>  // newest cursor
15226    "};
15227    let expected = indoc! {"
15228        1. buf.to_offsetˇ
15229        2. buf.to_offsetˇsuf
15230        3. buf.to_offsetˇfix
15231        4. buf.to_offsetˇ
15232        5. into_offsetˇensive
15233        6. to_offsetˇsuffix
15234        7. let to_offsetˇ //
15235        8. aato_offsetˇzz
15236        9. buf.to_offsetˇ
15237        10. buf.to_offsetˇsuffix
15238        11. to_offsetˇ
15239
15240        buf.to_offsetˇ  // newest cursor
15241    "};
15242    cx.set_state(initial_state);
15243    cx.update_editor(|editor, window, cx| {
15244        editor.show_completions(&ShowCompletions, window, cx);
15245    });
15246    handle_completion_request_with_insert_and_replace(
15247        &mut cx,
15248        completion_marked_buffer,
15249        vec![(completion_text, completion_text)],
15250        Arc::new(AtomicUsize::new(0)),
15251    )
15252    .await;
15253    cx.condition(|editor, _| editor.context_menu_visible())
15254        .await;
15255    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15256        editor
15257            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15258            .unwrap()
15259    });
15260    cx.assert_editor_state(expected);
15261    handle_resolve_completion_request(&mut cx, None).await;
15262    apply_additional_edits.await.unwrap();
15263
15264    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
15265    let completion_text = "foo_and_bar";
15266    let initial_state = indoc! {"
15267        1. ooanbˇ
15268        2. zooanbˇ
15269        3. ooanbˇz
15270        4. zooanbˇz
15271        5. ooanˇ
15272        6. oanbˇ
15273
15274        ooanbˇ
15275    "};
15276    let completion_marked_buffer = indoc! {"
15277        1. ooanb
15278        2. zooanb
15279        3. ooanbz
15280        4. zooanbz
15281        5. ooan
15282        6. oanb
15283
15284        <ooanb|>
15285    "};
15286    let expected = indoc! {"
15287        1. foo_and_barˇ
15288        2. zfoo_and_barˇ
15289        3. foo_and_barˇz
15290        4. zfoo_and_barˇz
15291        5. ooanfoo_and_barˇ
15292        6. oanbfoo_and_barˇ
15293
15294        foo_and_barˇ
15295    "};
15296    cx.set_state(initial_state);
15297    cx.update_editor(|editor, window, cx| {
15298        editor.show_completions(&ShowCompletions, window, cx);
15299    });
15300    handle_completion_request_with_insert_and_replace(
15301        &mut cx,
15302        completion_marked_buffer,
15303        vec![(completion_text, completion_text)],
15304        Arc::new(AtomicUsize::new(0)),
15305    )
15306    .await;
15307    cx.condition(|editor, _| editor.context_menu_visible())
15308        .await;
15309    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15310        editor
15311            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15312            .unwrap()
15313    });
15314    cx.assert_editor_state(expected);
15315    handle_resolve_completion_request(&mut cx, None).await;
15316    apply_additional_edits.await.unwrap();
15317
15318    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
15319    // (expects the same as if it was inserted at the end)
15320    let completion_text = "foo_and_bar";
15321    let initial_state = indoc! {"
15322        1. ooˇanb
15323        2. zooˇanb
15324        3. ooˇanbz
15325        4. zooˇanbz
15326
15327        ooˇanb
15328    "};
15329    let completion_marked_buffer = indoc! {"
15330        1. ooanb
15331        2. zooanb
15332        3. ooanbz
15333        4. zooanbz
15334
15335        <oo|anb>
15336    "};
15337    let expected = indoc! {"
15338        1. foo_and_barˇ
15339        2. zfoo_and_barˇ
15340        3. foo_and_barˇz
15341        4. zfoo_and_barˇz
15342
15343        foo_and_barˇ
15344    "};
15345    cx.set_state(initial_state);
15346    cx.update_editor(|editor, window, cx| {
15347        editor.show_completions(&ShowCompletions, window, cx);
15348    });
15349    handle_completion_request_with_insert_and_replace(
15350        &mut cx,
15351        completion_marked_buffer,
15352        vec![(completion_text, completion_text)],
15353        Arc::new(AtomicUsize::new(0)),
15354    )
15355    .await;
15356    cx.condition(|editor, _| editor.context_menu_visible())
15357        .await;
15358    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15359        editor
15360            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15361            .unwrap()
15362    });
15363    cx.assert_editor_state(expected);
15364    handle_resolve_completion_request(&mut cx, None).await;
15365    apply_additional_edits.await.unwrap();
15366}
15367
15368// This used to crash
15369#[gpui::test]
15370async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
15371    init_test(cx, |_| {});
15372
15373    let buffer_text = indoc! {"
15374        fn main() {
15375            10.satu;
15376
15377            //
15378            // separate cursors so they open in different excerpts (manually reproducible)
15379            //
15380
15381            10.satu20;
15382        }
15383    "};
15384    let multibuffer_text_with_selections = indoc! {"
15385        fn main() {
15386            10.satuˇ;
15387
15388            //
15389
15390            //
15391
15392            10.satuˇ20;
15393        }
15394    "};
15395    let expected_multibuffer = indoc! {"
15396        fn main() {
15397            10.saturating_sub()ˇ;
15398
15399            //
15400
15401            //
15402
15403            10.saturating_sub()ˇ;
15404        }
15405    "};
15406
15407    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
15408    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
15409
15410    let fs = FakeFs::new(cx.executor());
15411    fs.insert_tree(
15412        path!("/a"),
15413        json!({
15414            "main.rs": buffer_text,
15415        }),
15416    )
15417    .await;
15418
15419    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15420    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15421    language_registry.add(rust_lang());
15422    let mut fake_servers = language_registry.register_fake_lsp(
15423        "Rust",
15424        FakeLspAdapter {
15425            capabilities: lsp::ServerCapabilities {
15426                completion_provider: Some(lsp::CompletionOptions {
15427                    resolve_provider: None,
15428                    ..lsp::CompletionOptions::default()
15429                }),
15430                ..lsp::ServerCapabilities::default()
15431            },
15432            ..FakeLspAdapter::default()
15433        },
15434    );
15435    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
15436    let workspace = window
15437        .read_with(cx, |mw, _| mw.workspace().clone())
15438        .unwrap();
15439    let cx = &mut VisualTestContext::from_window(*window, cx);
15440    let buffer = project
15441        .update(cx, |project, cx| {
15442            project.open_local_buffer(path!("/a/main.rs"), cx)
15443        })
15444        .await
15445        .unwrap();
15446
15447    let multi_buffer = cx.new(|cx| {
15448        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
15449        multi_buffer.push_excerpts(
15450            buffer.clone(),
15451            [ExcerptRange::new(0..first_excerpt_end)],
15452            cx,
15453        );
15454        multi_buffer.push_excerpts(
15455            buffer.clone(),
15456            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
15457            cx,
15458        );
15459        multi_buffer
15460    });
15461
15462    let editor = workspace.update_in(cx, |_, window, cx| {
15463        cx.new(|cx| {
15464            Editor::new(
15465                EditorMode::Full {
15466                    scale_ui_elements_with_buffer_font_size: false,
15467                    show_active_line_background: false,
15468                    sizing_behavior: SizingBehavior::Default,
15469                },
15470                multi_buffer.clone(),
15471                Some(project.clone()),
15472                window,
15473                cx,
15474            )
15475        })
15476    });
15477
15478    let pane = workspace.update_in(cx, |workspace, _, _| workspace.active_pane().clone());
15479    pane.update_in(cx, |pane, window, cx| {
15480        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
15481    });
15482
15483    let fake_server = fake_servers.next().await.unwrap();
15484    cx.run_until_parked();
15485
15486    editor.update_in(cx, |editor, window, cx| {
15487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15488            s.select_ranges([
15489                Point::new(1, 11)..Point::new(1, 11),
15490                Point::new(7, 11)..Point::new(7, 11),
15491            ])
15492        });
15493
15494        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
15495    });
15496
15497    editor.update_in(cx, |editor, window, cx| {
15498        editor.show_completions(&ShowCompletions, window, cx);
15499    });
15500
15501    fake_server
15502        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15503            let completion_item = lsp::CompletionItem {
15504                label: "saturating_sub()".into(),
15505                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15506                    lsp::InsertReplaceEdit {
15507                        new_text: "saturating_sub()".to_owned(),
15508                        insert: lsp::Range::new(
15509                            lsp::Position::new(7, 7),
15510                            lsp::Position::new(7, 11),
15511                        ),
15512                        replace: lsp::Range::new(
15513                            lsp::Position::new(7, 7),
15514                            lsp::Position::new(7, 13),
15515                        ),
15516                    },
15517                )),
15518                ..lsp::CompletionItem::default()
15519            };
15520
15521            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
15522        })
15523        .next()
15524        .await
15525        .unwrap();
15526
15527    cx.condition(&editor, |editor, _| editor.context_menu_visible())
15528        .await;
15529
15530    editor
15531        .update_in(cx, |editor, window, cx| {
15532            editor
15533                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15534                .unwrap()
15535        })
15536        .await
15537        .unwrap();
15538
15539    editor.update(cx, |editor, cx| {
15540        assert_text_with_selections(editor, expected_multibuffer, cx);
15541    })
15542}
15543
15544#[gpui::test]
15545async fn test_completion(cx: &mut TestAppContext) {
15546    init_test(cx, |_| {});
15547
15548    let mut cx = EditorLspTestContext::new_rust(
15549        lsp::ServerCapabilities {
15550            completion_provider: Some(lsp::CompletionOptions {
15551                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15552                resolve_provider: Some(true),
15553                ..Default::default()
15554            }),
15555            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15556            ..Default::default()
15557        },
15558        cx,
15559    )
15560    .await;
15561    let counter = Arc::new(AtomicUsize::new(0));
15562
15563    cx.set_state(indoc! {"
15564        oneˇ
15565        two
15566        three
15567    "});
15568    cx.simulate_keystroke(".");
15569    handle_completion_request(
15570        indoc! {"
15571            one.|<>
15572            two
15573            three
15574        "},
15575        vec!["first_completion", "second_completion"],
15576        true,
15577        counter.clone(),
15578        &mut cx,
15579    )
15580    .await;
15581    cx.condition(|editor, _| editor.context_menu_visible())
15582        .await;
15583    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15584
15585    let _handler = handle_signature_help_request(
15586        &mut cx,
15587        lsp::SignatureHelp {
15588            signatures: vec![lsp::SignatureInformation {
15589                label: "test signature".to_string(),
15590                documentation: None,
15591                parameters: Some(vec![lsp::ParameterInformation {
15592                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15593                    documentation: None,
15594                }]),
15595                active_parameter: None,
15596            }],
15597            active_signature: None,
15598            active_parameter: None,
15599        },
15600    );
15601    cx.update_editor(|editor, window, cx| {
15602        assert!(
15603            !editor.signature_help_state.is_shown(),
15604            "No signature help was called for"
15605        );
15606        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15607    });
15608    cx.run_until_parked();
15609    cx.update_editor(|editor, _, _| {
15610        assert!(
15611            !editor.signature_help_state.is_shown(),
15612            "No signature help should be shown when completions menu is open"
15613        );
15614    });
15615
15616    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15617        editor.context_menu_next(&Default::default(), window, cx);
15618        editor
15619            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15620            .unwrap()
15621    });
15622    cx.assert_editor_state(indoc! {"
15623        one.second_completionˇ
15624        two
15625        three
15626    "});
15627
15628    handle_resolve_completion_request(
15629        &mut cx,
15630        Some(vec![
15631            (
15632                //This overlaps with the primary completion edit which is
15633                //misbehavior from the LSP spec, test that we filter it out
15634                indoc! {"
15635                    one.second_ˇcompletion
15636                    two
15637                    threeˇ
15638                "},
15639                "overlapping additional edit",
15640            ),
15641            (
15642                indoc! {"
15643                    one.second_completion
15644                    two
15645                    threeˇ
15646                "},
15647                "\nadditional edit",
15648            ),
15649        ]),
15650    )
15651    .await;
15652    apply_additional_edits.await.unwrap();
15653    cx.assert_editor_state(indoc! {"
15654        one.second_completionˇ
15655        two
15656        three
15657        additional edit
15658    "});
15659
15660    cx.set_state(indoc! {"
15661        one.second_completion
15662        twoˇ
15663        threeˇ
15664        additional edit
15665    "});
15666    cx.simulate_keystroke(" ");
15667    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15668    cx.simulate_keystroke("s");
15669    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15670
15671    cx.assert_editor_state(indoc! {"
15672        one.second_completion
15673        two sˇ
15674        three sˇ
15675        additional edit
15676    "});
15677    handle_completion_request(
15678        indoc! {"
15679            one.second_completion
15680            two s
15681            three <s|>
15682            additional edit
15683        "},
15684        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15685        true,
15686        counter.clone(),
15687        &mut cx,
15688    )
15689    .await;
15690    cx.condition(|editor, _| editor.context_menu_visible())
15691        .await;
15692    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15693
15694    cx.simulate_keystroke("i");
15695
15696    handle_completion_request(
15697        indoc! {"
15698            one.second_completion
15699            two si
15700            three <si|>
15701            additional edit
15702        "},
15703        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15704        true,
15705        counter.clone(),
15706        &mut cx,
15707    )
15708    .await;
15709    cx.condition(|editor, _| editor.context_menu_visible())
15710        .await;
15711    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15712
15713    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15714        editor
15715            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15716            .unwrap()
15717    });
15718    cx.assert_editor_state(indoc! {"
15719        one.second_completion
15720        two sixth_completionˇ
15721        three sixth_completionˇ
15722        additional edit
15723    "});
15724
15725    apply_additional_edits.await.unwrap();
15726
15727    update_test_language_settings(&mut cx, |settings| {
15728        settings.defaults.show_completions_on_input = Some(false);
15729    });
15730    cx.set_state("editorˇ");
15731    cx.simulate_keystroke(".");
15732    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15733    cx.simulate_keystrokes("c l o");
15734    cx.assert_editor_state("editor.cloˇ");
15735    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15736    cx.update_editor(|editor, window, cx| {
15737        editor.show_completions(&ShowCompletions, window, cx);
15738    });
15739    handle_completion_request(
15740        "editor.<clo|>",
15741        vec!["close", "clobber"],
15742        true,
15743        counter.clone(),
15744        &mut cx,
15745    )
15746    .await;
15747    cx.condition(|editor, _| editor.context_menu_visible())
15748        .await;
15749    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15750
15751    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15752        editor
15753            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15754            .unwrap()
15755    });
15756    cx.assert_editor_state("editor.clobberˇ");
15757    handle_resolve_completion_request(&mut cx, None).await;
15758    apply_additional_edits.await.unwrap();
15759}
15760
15761#[gpui::test]
15762async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15763    init_test(cx, |_| {});
15764
15765    let fs = FakeFs::new(cx.executor());
15766    fs.insert_tree(
15767        path!("/a"),
15768        json!({
15769            "main.rs": "",
15770        }),
15771    )
15772    .await;
15773
15774    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15775    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15776    language_registry.add(rust_lang());
15777    let command_calls = Arc::new(AtomicUsize::new(0));
15778    let registered_command = "_the/command";
15779
15780    let closure_command_calls = command_calls.clone();
15781    let mut fake_servers = language_registry.register_fake_lsp(
15782        "Rust",
15783        FakeLspAdapter {
15784            capabilities: lsp::ServerCapabilities {
15785                completion_provider: Some(lsp::CompletionOptions {
15786                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15787                    ..lsp::CompletionOptions::default()
15788                }),
15789                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15790                    commands: vec![registered_command.to_owned()],
15791                    ..lsp::ExecuteCommandOptions::default()
15792                }),
15793                ..lsp::ServerCapabilities::default()
15794            },
15795            initializer: Some(Box::new(move |fake_server| {
15796                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15797                    move |params, _| async move {
15798                        Ok(Some(lsp::CompletionResponse::Array(vec![
15799                            lsp::CompletionItem {
15800                                label: "registered_command".to_owned(),
15801                                text_edit: gen_text_edit(&params, ""),
15802                                command: Some(lsp::Command {
15803                                    title: registered_command.to_owned(),
15804                                    command: "_the/command".to_owned(),
15805                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15806                                }),
15807                                ..lsp::CompletionItem::default()
15808                            },
15809                            lsp::CompletionItem {
15810                                label: "unregistered_command".to_owned(),
15811                                text_edit: gen_text_edit(&params, ""),
15812                                command: Some(lsp::Command {
15813                                    title: "????????????".to_owned(),
15814                                    command: "????????????".to_owned(),
15815                                    arguments: Some(vec![serde_json::Value::Null]),
15816                                }),
15817                                ..lsp::CompletionItem::default()
15818                            },
15819                        ])))
15820                    },
15821                );
15822                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15823                    let command_calls = closure_command_calls.clone();
15824                    move |params, _| {
15825                        assert_eq!(params.command, registered_command);
15826                        let command_calls = command_calls.clone();
15827                        async move {
15828                            command_calls.fetch_add(1, atomic::Ordering::Release);
15829                            Ok(Some(json!(null)))
15830                        }
15831                    }
15832                });
15833            })),
15834            ..FakeLspAdapter::default()
15835        },
15836    );
15837    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
15838    let workspace = window
15839        .read_with(cx, |mw, _| mw.workspace().clone())
15840        .unwrap();
15841    let cx = &mut VisualTestContext::from_window(*window, cx);
15842    let editor = workspace
15843        .update_in(cx, |workspace, window, cx| {
15844            workspace.open_abs_path(
15845                PathBuf::from(path!("/a/main.rs")),
15846                OpenOptions::default(),
15847                window,
15848                cx,
15849            )
15850        })
15851        .await
15852        .unwrap()
15853        .downcast::<Editor>()
15854        .unwrap();
15855    let _fake_server = fake_servers.next().await.unwrap();
15856    cx.run_until_parked();
15857
15858    editor.update_in(cx, |editor, window, cx| {
15859        cx.focus_self(window);
15860        editor.move_to_end(&MoveToEnd, window, cx);
15861        editor.handle_input(".", window, cx);
15862    });
15863    cx.run_until_parked();
15864    editor.update(cx, |editor, _| {
15865        assert!(editor.context_menu_visible());
15866        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15867        {
15868            let completion_labels = menu
15869                .completions
15870                .borrow()
15871                .iter()
15872                .map(|c| c.label.text.clone())
15873                .collect::<Vec<_>>();
15874            assert_eq!(
15875                completion_labels,
15876                &["registered_command", "unregistered_command",],
15877            );
15878        } else {
15879            panic!("expected completion menu to be open");
15880        }
15881    });
15882
15883    editor
15884        .update_in(cx, |editor, window, cx| {
15885            editor
15886                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15887                .unwrap()
15888        })
15889        .await
15890        .unwrap();
15891    cx.run_until_parked();
15892    assert_eq!(
15893        command_calls.load(atomic::Ordering::Acquire),
15894        1,
15895        "For completion with a registered command, Zed should send a command execution request",
15896    );
15897
15898    editor.update_in(cx, |editor, window, cx| {
15899        cx.focus_self(window);
15900        editor.handle_input(".", window, cx);
15901    });
15902    cx.run_until_parked();
15903    editor.update(cx, |editor, _| {
15904        assert!(editor.context_menu_visible());
15905        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15906        {
15907            let completion_labels = menu
15908                .completions
15909                .borrow()
15910                .iter()
15911                .map(|c| c.label.text.clone())
15912                .collect::<Vec<_>>();
15913            assert_eq!(
15914                completion_labels,
15915                &["registered_command", "unregistered_command",],
15916            );
15917        } else {
15918            panic!("expected completion menu to be open");
15919        }
15920    });
15921    editor
15922        .update_in(cx, |editor, window, cx| {
15923            editor.context_menu_next(&Default::default(), window, cx);
15924            editor
15925                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15926                .unwrap()
15927        })
15928        .await
15929        .unwrap();
15930    cx.run_until_parked();
15931    assert_eq!(
15932        command_calls.load(atomic::Ordering::Acquire),
15933        1,
15934        "For completion with an unregistered command, Zed should not send a command execution request",
15935    );
15936}
15937
15938#[gpui::test]
15939async fn test_completion_reuse(cx: &mut TestAppContext) {
15940    init_test(cx, |_| {});
15941
15942    let mut cx = EditorLspTestContext::new_rust(
15943        lsp::ServerCapabilities {
15944            completion_provider: Some(lsp::CompletionOptions {
15945                trigger_characters: Some(vec![".".to_string()]),
15946                ..Default::default()
15947            }),
15948            ..Default::default()
15949        },
15950        cx,
15951    )
15952    .await;
15953
15954    let counter = Arc::new(AtomicUsize::new(0));
15955    cx.set_state("objˇ");
15956    cx.simulate_keystroke(".");
15957
15958    // Initial completion request returns complete results
15959    let is_incomplete = false;
15960    handle_completion_request(
15961        "obj.|<>",
15962        vec!["a", "ab", "abc"],
15963        is_incomplete,
15964        counter.clone(),
15965        &mut cx,
15966    )
15967    .await;
15968    cx.run_until_parked();
15969    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15970    cx.assert_editor_state("obj.ˇ");
15971    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15972
15973    // Type "a" - filters existing completions
15974    cx.simulate_keystroke("a");
15975    cx.run_until_parked();
15976    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15977    cx.assert_editor_state("obj.aˇ");
15978    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15979
15980    // Type "b" - filters existing completions
15981    cx.simulate_keystroke("b");
15982    cx.run_until_parked();
15983    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15984    cx.assert_editor_state("obj.abˇ");
15985    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15986
15987    // Type "c" - filters existing completions
15988    cx.simulate_keystroke("c");
15989    cx.run_until_parked();
15990    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15991    cx.assert_editor_state("obj.abcˇ");
15992    check_displayed_completions(vec!["abc"], &mut cx);
15993
15994    // Backspace to delete "c" - filters existing completions
15995    cx.update_editor(|editor, window, cx| {
15996        editor.backspace(&Backspace, window, cx);
15997    });
15998    cx.run_until_parked();
15999    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16000    cx.assert_editor_state("obj.abˇ");
16001    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16002
16003    // Moving cursor to the left dismisses menu.
16004    cx.update_editor(|editor, window, cx| {
16005        editor.move_left(&MoveLeft, window, cx);
16006    });
16007    cx.run_until_parked();
16008    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16009    cx.assert_editor_state("obj.aˇb");
16010    cx.update_editor(|editor, _, _| {
16011        assert_eq!(editor.context_menu_visible(), false);
16012    });
16013
16014    // Type "b" - new request
16015    cx.simulate_keystroke("b");
16016    let is_incomplete = false;
16017    handle_completion_request(
16018        "obj.<ab|>a",
16019        vec!["ab", "abc"],
16020        is_incomplete,
16021        counter.clone(),
16022        &mut cx,
16023    )
16024    .await;
16025    cx.run_until_parked();
16026    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16027    cx.assert_editor_state("obj.abˇb");
16028    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16029
16030    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
16031    cx.update_editor(|editor, window, cx| {
16032        editor.backspace(&Backspace, window, cx);
16033    });
16034    let is_incomplete = false;
16035    handle_completion_request(
16036        "obj.<a|>b",
16037        vec!["a", "ab", "abc"],
16038        is_incomplete,
16039        counter.clone(),
16040        &mut cx,
16041    )
16042    .await;
16043    cx.run_until_parked();
16044    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16045    cx.assert_editor_state("obj.aˇb");
16046    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16047
16048    // Backspace to delete "a" - dismisses menu.
16049    cx.update_editor(|editor, window, cx| {
16050        editor.backspace(&Backspace, window, cx);
16051    });
16052    cx.run_until_parked();
16053    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16054    cx.assert_editor_state("obj.ˇb");
16055    cx.update_editor(|editor, _, _| {
16056        assert_eq!(editor.context_menu_visible(), false);
16057    });
16058}
16059
16060#[gpui::test]
16061async fn test_word_completion(cx: &mut TestAppContext) {
16062    let lsp_fetch_timeout_ms = 10;
16063    init_test(cx, |language_settings| {
16064        language_settings.defaults.completions = Some(CompletionSettingsContent {
16065            words_min_length: Some(0),
16066            lsp_fetch_timeout_ms: Some(10),
16067            lsp_insert_mode: Some(LspInsertMode::Insert),
16068            ..Default::default()
16069        });
16070    });
16071
16072    let mut cx = EditorLspTestContext::new_rust(
16073        lsp::ServerCapabilities {
16074            completion_provider: Some(lsp::CompletionOptions {
16075                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16076                ..lsp::CompletionOptions::default()
16077            }),
16078            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16079            ..lsp::ServerCapabilities::default()
16080        },
16081        cx,
16082    )
16083    .await;
16084
16085    let throttle_completions = Arc::new(AtomicBool::new(false));
16086
16087    let lsp_throttle_completions = throttle_completions.clone();
16088    let _completion_requests_handler =
16089        cx.lsp
16090            .server
16091            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
16092                let lsp_throttle_completions = lsp_throttle_completions.clone();
16093                let cx = cx.clone();
16094                async move {
16095                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
16096                        cx.background_executor()
16097                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
16098                            .await;
16099                    }
16100                    Ok(Some(lsp::CompletionResponse::Array(vec![
16101                        lsp::CompletionItem {
16102                            label: "first".into(),
16103                            ..lsp::CompletionItem::default()
16104                        },
16105                        lsp::CompletionItem {
16106                            label: "last".into(),
16107                            ..lsp::CompletionItem::default()
16108                        },
16109                    ])))
16110                }
16111            });
16112
16113    cx.set_state(indoc! {"
16114        oneˇ
16115        two
16116        three
16117    "});
16118    cx.simulate_keystroke(".");
16119    cx.executor().run_until_parked();
16120    cx.condition(|editor, _| editor.context_menu_visible())
16121        .await;
16122    cx.update_editor(|editor, window, cx| {
16123        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16124        {
16125            assert_eq!(
16126                completion_menu_entries(menu),
16127                &["first", "last"],
16128                "When LSP server is fast to reply, no fallback word completions are used"
16129            );
16130        } else {
16131            panic!("expected completion menu to be open");
16132        }
16133        editor.cancel(&Cancel, window, cx);
16134    });
16135    cx.executor().run_until_parked();
16136    cx.condition(|editor, _| !editor.context_menu_visible())
16137        .await;
16138
16139    throttle_completions.store(true, atomic::Ordering::Release);
16140    cx.simulate_keystroke(".");
16141    cx.executor()
16142        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
16143    cx.executor().run_until_parked();
16144    cx.condition(|editor, _| editor.context_menu_visible())
16145        .await;
16146    cx.update_editor(|editor, _, _| {
16147        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16148        {
16149            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
16150                "When LSP server is slow, document words can be shown instead, if configured accordingly");
16151        } else {
16152            panic!("expected completion menu to be open");
16153        }
16154    });
16155}
16156
16157#[gpui::test]
16158async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
16159    init_test(cx, |language_settings| {
16160        language_settings.defaults.completions = Some(CompletionSettingsContent {
16161            words: Some(WordsCompletionMode::Enabled),
16162            words_min_length: Some(0),
16163            lsp_insert_mode: Some(LspInsertMode::Insert),
16164            ..Default::default()
16165        });
16166    });
16167
16168    let mut cx = EditorLspTestContext::new_rust(
16169        lsp::ServerCapabilities {
16170            completion_provider: Some(lsp::CompletionOptions {
16171                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16172                ..lsp::CompletionOptions::default()
16173            }),
16174            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16175            ..lsp::ServerCapabilities::default()
16176        },
16177        cx,
16178    )
16179    .await;
16180
16181    let _completion_requests_handler =
16182        cx.lsp
16183            .server
16184            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16185                Ok(Some(lsp::CompletionResponse::Array(vec![
16186                    lsp::CompletionItem {
16187                        label: "first".into(),
16188                        ..lsp::CompletionItem::default()
16189                    },
16190                    lsp::CompletionItem {
16191                        label: "last".into(),
16192                        ..lsp::CompletionItem::default()
16193                    },
16194                ])))
16195            });
16196
16197    cx.set_state(indoc! {"ˇ
16198        first
16199        last
16200        second
16201    "});
16202    cx.simulate_keystroke(".");
16203    cx.executor().run_until_parked();
16204    cx.condition(|editor, _| editor.context_menu_visible())
16205        .await;
16206    cx.update_editor(|editor, _, _| {
16207        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16208        {
16209            assert_eq!(
16210                completion_menu_entries(menu),
16211                &["first", "last", "second"],
16212                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
16213            );
16214        } else {
16215            panic!("expected completion menu to be open");
16216        }
16217    });
16218}
16219
16220#[gpui::test]
16221async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
16222    init_test(cx, |language_settings| {
16223        language_settings.defaults.completions = Some(CompletionSettingsContent {
16224            words: Some(WordsCompletionMode::Disabled),
16225            words_min_length: Some(0),
16226            lsp_insert_mode: Some(LspInsertMode::Insert),
16227            ..Default::default()
16228        });
16229    });
16230
16231    let mut cx = EditorLspTestContext::new_rust(
16232        lsp::ServerCapabilities {
16233            completion_provider: Some(lsp::CompletionOptions {
16234                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16235                ..lsp::CompletionOptions::default()
16236            }),
16237            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16238            ..lsp::ServerCapabilities::default()
16239        },
16240        cx,
16241    )
16242    .await;
16243
16244    let _completion_requests_handler =
16245        cx.lsp
16246            .server
16247            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16248                panic!("LSP completions should not be queried when dealing with word completions")
16249            });
16250
16251    cx.set_state(indoc! {"ˇ
16252        first
16253        last
16254        second
16255    "});
16256    cx.update_editor(|editor, window, cx| {
16257        editor.show_word_completions(&ShowWordCompletions, window, cx);
16258    });
16259    cx.executor().run_until_parked();
16260    cx.condition(|editor, _| editor.context_menu_visible())
16261        .await;
16262    cx.update_editor(|editor, _, _| {
16263        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16264        {
16265            assert_eq!(
16266                completion_menu_entries(menu),
16267                &["first", "last", "second"],
16268                "`ShowWordCompletions` action should show word completions"
16269            );
16270        } else {
16271            panic!("expected completion menu to be open");
16272        }
16273    });
16274
16275    cx.simulate_keystroke("l");
16276    cx.executor().run_until_parked();
16277    cx.condition(|editor, _| editor.context_menu_visible())
16278        .await;
16279    cx.update_editor(|editor, _, _| {
16280        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16281        {
16282            assert_eq!(
16283                completion_menu_entries(menu),
16284                &["last"],
16285                "After showing word completions, further editing should filter them and not query the LSP"
16286            );
16287        } else {
16288            panic!("expected completion menu to be open");
16289        }
16290    });
16291}
16292
16293#[gpui::test]
16294async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
16295    init_test(cx, |language_settings| {
16296        language_settings.defaults.completions = Some(CompletionSettingsContent {
16297            words_min_length: Some(0),
16298            lsp: Some(false),
16299            lsp_insert_mode: Some(LspInsertMode::Insert),
16300            ..Default::default()
16301        });
16302    });
16303
16304    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16305
16306    cx.set_state(indoc! {"ˇ
16307        0_usize
16308        let
16309        33
16310        4.5f32
16311    "});
16312    cx.update_editor(|editor, window, cx| {
16313        editor.show_completions(&ShowCompletions, window, cx);
16314    });
16315    cx.executor().run_until_parked();
16316    cx.condition(|editor, _| editor.context_menu_visible())
16317        .await;
16318    cx.update_editor(|editor, window, cx| {
16319        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16320        {
16321            assert_eq!(
16322                completion_menu_entries(menu),
16323                &["let"],
16324                "With no digits in the completion query, no digits should be in the word completions"
16325            );
16326        } else {
16327            panic!("expected completion menu to be open");
16328        }
16329        editor.cancel(&Cancel, window, cx);
16330    });
16331
16332    cx.set_state(indoc! {"16333        0_usize
16334        let
16335        3
16336        33.35f32
16337    "});
16338    cx.update_editor(|editor, window, cx| {
16339        editor.show_completions(&ShowCompletions, window, cx);
16340    });
16341    cx.executor().run_until_parked();
16342    cx.condition(|editor, _| editor.context_menu_visible())
16343        .await;
16344    cx.update_editor(|editor, _, _| {
16345        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16346        {
16347            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
16348                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
16349        } else {
16350            panic!("expected completion menu to be open");
16351        }
16352    });
16353}
16354
16355#[gpui::test]
16356async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
16357    init_test(cx, |language_settings| {
16358        language_settings.defaults.completions = Some(CompletionSettingsContent {
16359            words: Some(WordsCompletionMode::Enabled),
16360            words_min_length: Some(3),
16361            lsp_insert_mode: Some(LspInsertMode::Insert),
16362            ..Default::default()
16363        });
16364    });
16365
16366    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16367    cx.set_state(indoc! {"ˇ
16368        wow
16369        wowen
16370        wowser
16371    "});
16372    cx.simulate_keystroke("w");
16373    cx.executor().run_until_parked();
16374    cx.update_editor(|editor, _, _| {
16375        if editor.context_menu.borrow_mut().is_some() {
16376            panic!(
16377                "expected completion menu to be hidden, as words completion threshold is not met"
16378            );
16379        }
16380    });
16381
16382    cx.update_editor(|editor, window, cx| {
16383        editor.show_word_completions(&ShowWordCompletions, window, cx);
16384    });
16385    cx.executor().run_until_parked();
16386    cx.update_editor(|editor, window, cx| {
16387        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16388        {
16389            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");
16390        } else {
16391            panic!("expected completion menu to be open after the word completions are called with an action");
16392        }
16393
16394        editor.cancel(&Cancel, window, cx);
16395    });
16396    cx.update_editor(|editor, _, _| {
16397        if editor.context_menu.borrow_mut().is_some() {
16398            panic!("expected completion menu to be hidden after canceling");
16399        }
16400    });
16401
16402    cx.simulate_keystroke("o");
16403    cx.executor().run_until_parked();
16404    cx.update_editor(|editor, _, _| {
16405        if editor.context_menu.borrow_mut().is_some() {
16406            panic!(
16407                "expected completion menu to be hidden, as words completion threshold is not met still"
16408            );
16409        }
16410    });
16411
16412    cx.simulate_keystroke("w");
16413    cx.executor().run_until_parked();
16414    cx.update_editor(|editor, _, _| {
16415        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16416        {
16417            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
16418        } else {
16419            panic!("expected completion menu to be open after the word completions threshold is met");
16420        }
16421    });
16422}
16423
16424#[gpui::test]
16425async fn test_word_completions_disabled(cx: &mut TestAppContext) {
16426    init_test(cx, |language_settings| {
16427        language_settings.defaults.completions = Some(CompletionSettingsContent {
16428            words: Some(WordsCompletionMode::Enabled),
16429            words_min_length: Some(0),
16430            lsp_insert_mode: Some(LspInsertMode::Insert),
16431            ..Default::default()
16432        });
16433    });
16434
16435    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16436    cx.update_editor(|editor, _, _| {
16437        editor.disable_word_completions();
16438    });
16439    cx.set_state(indoc! {"ˇ
16440        wow
16441        wowen
16442        wowser
16443    "});
16444    cx.simulate_keystroke("w");
16445    cx.executor().run_until_parked();
16446    cx.update_editor(|editor, _, _| {
16447        if editor.context_menu.borrow_mut().is_some() {
16448            panic!(
16449                "expected completion menu to be hidden, as words completion are disabled for this editor"
16450            );
16451        }
16452    });
16453
16454    cx.update_editor(|editor, window, cx| {
16455        editor.show_word_completions(&ShowWordCompletions, window, cx);
16456    });
16457    cx.executor().run_until_parked();
16458    cx.update_editor(|editor, _, _| {
16459        if editor.context_menu.borrow_mut().is_some() {
16460            panic!(
16461                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
16462            );
16463        }
16464    });
16465}
16466
16467#[gpui::test]
16468async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
16469    init_test(cx, |language_settings| {
16470        language_settings.defaults.completions = Some(CompletionSettingsContent {
16471            words: Some(WordsCompletionMode::Disabled),
16472            words_min_length: Some(0),
16473            lsp_insert_mode: Some(LspInsertMode::Insert),
16474            ..Default::default()
16475        });
16476    });
16477
16478    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16479    cx.update_editor(|editor, _, _| {
16480        editor.set_completion_provider(None);
16481    });
16482    cx.set_state(indoc! {"ˇ
16483        wow
16484        wowen
16485        wowser
16486    "});
16487    cx.simulate_keystroke("w");
16488    cx.executor().run_until_parked();
16489    cx.update_editor(|editor, _, _| {
16490        if editor.context_menu.borrow_mut().is_some() {
16491            panic!("expected completion menu to be hidden, as disabled in settings");
16492        }
16493    });
16494}
16495
16496fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
16497    let position = || lsp::Position {
16498        line: params.text_document_position.position.line,
16499        character: params.text_document_position.position.character,
16500    };
16501    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16502        range: lsp::Range {
16503            start: position(),
16504            end: position(),
16505        },
16506        new_text: text.to_string(),
16507    }))
16508}
16509
16510#[gpui::test]
16511async fn test_multiline_completion(cx: &mut TestAppContext) {
16512    init_test(cx, |_| {});
16513
16514    let fs = FakeFs::new(cx.executor());
16515    fs.insert_tree(
16516        path!("/a"),
16517        json!({
16518            "main.ts": "a",
16519        }),
16520    )
16521    .await;
16522
16523    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16524    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16525    let typescript_language = Arc::new(Language::new(
16526        LanguageConfig {
16527            name: "TypeScript".into(),
16528            matcher: LanguageMatcher {
16529                path_suffixes: vec!["ts".to_string()],
16530                ..LanguageMatcher::default()
16531            },
16532            line_comments: vec!["// ".into()],
16533            ..LanguageConfig::default()
16534        },
16535        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16536    ));
16537    language_registry.add(typescript_language.clone());
16538    let mut fake_servers = language_registry.register_fake_lsp(
16539        "TypeScript",
16540        FakeLspAdapter {
16541            capabilities: lsp::ServerCapabilities {
16542                completion_provider: Some(lsp::CompletionOptions {
16543                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16544                    ..lsp::CompletionOptions::default()
16545                }),
16546                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16547                ..lsp::ServerCapabilities::default()
16548            },
16549            // Emulate vtsls label generation
16550            label_for_completion: Some(Box::new(|item, _| {
16551                let text = if let Some(description) = item
16552                    .label_details
16553                    .as_ref()
16554                    .and_then(|label_details| label_details.description.as_ref())
16555                {
16556                    format!("{} {}", item.label, description)
16557                } else if let Some(detail) = &item.detail {
16558                    format!("{} {}", item.label, detail)
16559                } else {
16560                    item.label.clone()
16561                };
16562                Some(language::CodeLabel::plain(text, None))
16563            })),
16564            ..FakeLspAdapter::default()
16565        },
16566    );
16567    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16568    let workspace = window
16569        .read_with(cx, |mw, _| mw.workspace().clone())
16570        .unwrap();
16571    let cx = &mut VisualTestContext::from_window(*window, cx);
16572    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
16573        workspace.project().update(cx, |project, cx| {
16574            project.worktrees(cx).next().unwrap().read(cx).id()
16575        })
16576    });
16577
16578    let _buffer = project
16579        .update(cx, |project, cx| {
16580            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16581        })
16582        .await
16583        .unwrap();
16584    let editor = workspace
16585        .update_in(cx, |workspace, window, cx| {
16586            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16587        })
16588        .await
16589        .unwrap()
16590        .downcast::<Editor>()
16591        .unwrap();
16592    let fake_server = fake_servers.next().await.unwrap();
16593    cx.run_until_parked();
16594
16595    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16596    let multiline_label_2 = "a\nb\nc\n";
16597    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16598    let multiline_description = "d\ne\nf\n";
16599    let multiline_detail_2 = "g\nh\ni\n";
16600
16601    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16602        move |params, _| async move {
16603            Ok(Some(lsp::CompletionResponse::Array(vec![
16604                lsp::CompletionItem {
16605                    label: multiline_label.to_string(),
16606                    text_edit: gen_text_edit(&params, "new_text_1"),
16607                    ..lsp::CompletionItem::default()
16608                },
16609                lsp::CompletionItem {
16610                    label: "single line label 1".to_string(),
16611                    detail: Some(multiline_detail.to_string()),
16612                    text_edit: gen_text_edit(&params, "new_text_2"),
16613                    ..lsp::CompletionItem::default()
16614                },
16615                lsp::CompletionItem {
16616                    label: "single line label 2".to_string(),
16617                    label_details: Some(lsp::CompletionItemLabelDetails {
16618                        description: Some(multiline_description.to_string()),
16619                        detail: None,
16620                    }),
16621                    text_edit: gen_text_edit(&params, "new_text_2"),
16622                    ..lsp::CompletionItem::default()
16623                },
16624                lsp::CompletionItem {
16625                    label: multiline_label_2.to_string(),
16626                    detail: Some(multiline_detail_2.to_string()),
16627                    text_edit: gen_text_edit(&params, "new_text_3"),
16628                    ..lsp::CompletionItem::default()
16629                },
16630                lsp::CompletionItem {
16631                    label: "Label with many     spaces and \t but without newlines".to_string(),
16632                    detail: Some(
16633                        "Details with many     spaces and \t but without newlines".to_string(),
16634                    ),
16635                    text_edit: gen_text_edit(&params, "new_text_4"),
16636                    ..lsp::CompletionItem::default()
16637                },
16638            ])))
16639        },
16640    );
16641
16642    editor.update_in(cx, |editor, window, cx| {
16643        cx.focus_self(window);
16644        editor.move_to_end(&MoveToEnd, window, cx);
16645        editor.handle_input(".", window, cx);
16646    });
16647    cx.run_until_parked();
16648    completion_handle.next().await.unwrap();
16649
16650    editor.update(cx, |editor, _| {
16651        assert!(editor.context_menu_visible());
16652        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16653        {
16654            let completion_labels = menu
16655                .completions
16656                .borrow()
16657                .iter()
16658                .map(|c| c.label.text.clone())
16659                .collect::<Vec<_>>();
16660            assert_eq!(
16661                completion_labels,
16662                &[
16663                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16664                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16665                    "single line label 2 d e f ",
16666                    "a b c g h i ",
16667                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16668                ],
16669                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16670            );
16671
16672            for completion in menu
16673                .completions
16674                .borrow()
16675                .iter() {
16676                    assert_eq!(
16677                        completion.label.filter_range,
16678                        0..completion.label.text.len(),
16679                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16680                    );
16681                }
16682        } else {
16683            panic!("expected completion menu to be open");
16684        }
16685    });
16686}
16687
16688#[gpui::test]
16689async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16690    init_test(cx, |_| {});
16691    let mut cx = EditorLspTestContext::new_rust(
16692        lsp::ServerCapabilities {
16693            completion_provider: Some(lsp::CompletionOptions {
16694                trigger_characters: Some(vec![".".to_string()]),
16695                ..Default::default()
16696            }),
16697            ..Default::default()
16698        },
16699        cx,
16700    )
16701    .await;
16702    cx.lsp
16703        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16704            Ok(Some(lsp::CompletionResponse::Array(vec![
16705                lsp::CompletionItem {
16706                    label: "first".into(),
16707                    ..Default::default()
16708                },
16709                lsp::CompletionItem {
16710                    label: "last".into(),
16711                    ..Default::default()
16712                },
16713            ])))
16714        });
16715    cx.set_state("variableˇ");
16716    cx.simulate_keystroke(".");
16717    cx.executor().run_until_parked();
16718
16719    cx.update_editor(|editor, _, _| {
16720        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16721        {
16722            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16723        } else {
16724            panic!("expected completion menu to be open");
16725        }
16726    });
16727
16728    cx.update_editor(|editor, window, cx| {
16729        editor.move_page_down(&MovePageDown::default(), window, cx);
16730        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16731        {
16732            assert!(
16733                menu.selected_item == 1,
16734                "expected PageDown to select the last item from the context menu"
16735            );
16736        } else {
16737            panic!("expected completion menu to stay open after PageDown");
16738        }
16739    });
16740
16741    cx.update_editor(|editor, window, cx| {
16742        editor.move_page_up(&MovePageUp::default(), window, cx);
16743        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16744        {
16745            assert!(
16746                menu.selected_item == 0,
16747                "expected PageUp to select the first item from the context menu"
16748            );
16749        } else {
16750            panic!("expected completion menu to stay open after PageUp");
16751        }
16752    });
16753}
16754
16755#[gpui::test]
16756async fn test_as_is_completions(cx: &mut TestAppContext) {
16757    init_test(cx, |_| {});
16758    let mut cx = EditorLspTestContext::new_rust(
16759        lsp::ServerCapabilities {
16760            completion_provider: Some(lsp::CompletionOptions {
16761                ..Default::default()
16762            }),
16763            ..Default::default()
16764        },
16765        cx,
16766    )
16767    .await;
16768    cx.lsp
16769        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16770            Ok(Some(lsp::CompletionResponse::Array(vec![
16771                lsp::CompletionItem {
16772                    label: "unsafe".into(),
16773                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16774                        range: lsp::Range {
16775                            start: lsp::Position {
16776                                line: 1,
16777                                character: 2,
16778                            },
16779                            end: lsp::Position {
16780                                line: 1,
16781                                character: 3,
16782                            },
16783                        },
16784                        new_text: "unsafe".to_string(),
16785                    })),
16786                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16787                    ..Default::default()
16788                },
16789            ])))
16790        });
16791    cx.set_state("fn a() {}\n");
16792    cx.executor().run_until_parked();
16793    cx.update_editor(|editor, window, cx| {
16794        editor.trigger_completion_on_input("n", true, window, cx)
16795    });
16796    cx.executor().run_until_parked();
16797
16798    cx.update_editor(|editor, window, cx| {
16799        editor.confirm_completion(&Default::default(), window, cx)
16800    });
16801    cx.executor().run_until_parked();
16802    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16803}
16804
16805#[gpui::test]
16806async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16807    init_test(cx, |_| {});
16808    let language =
16809        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16810    let mut cx = EditorLspTestContext::new(
16811        language,
16812        lsp::ServerCapabilities {
16813            completion_provider: Some(lsp::CompletionOptions {
16814                ..lsp::CompletionOptions::default()
16815            }),
16816            ..lsp::ServerCapabilities::default()
16817        },
16818        cx,
16819    )
16820    .await;
16821
16822    cx.set_state(
16823        "#ifndef BAR_H
16824#define BAR_H
16825
16826#include <stdbool.h>
16827
16828int fn_branch(bool do_branch1, bool do_branch2);
16829
16830#endif // BAR_H
16831ˇ",
16832    );
16833    cx.executor().run_until_parked();
16834    cx.update_editor(|editor, window, cx| {
16835        editor.handle_input("#", window, cx);
16836    });
16837    cx.executor().run_until_parked();
16838    cx.update_editor(|editor, window, cx| {
16839        editor.handle_input("i", window, cx);
16840    });
16841    cx.executor().run_until_parked();
16842    cx.update_editor(|editor, window, cx| {
16843        editor.handle_input("n", window, cx);
16844    });
16845    cx.executor().run_until_parked();
16846    cx.assert_editor_state(
16847        "#ifndef BAR_H
16848#define BAR_H
16849
16850#include <stdbool.h>
16851
16852int fn_branch(bool do_branch1, bool do_branch2);
16853
16854#endif // BAR_H
16855#inˇ",
16856    );
16857
16858    cx.lsp
16859        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16860            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16861                is_incomplete: false,
16862                item_defaults: None,
16863                items: vec![lsp::CompletionItem {
16864                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16865                    label_details: Some(lsp::CompletionItemLabelDetails {
16866                        detail: Some("header".to_string()),
16867                        description: None,
16868                    }),
16869                    label: " include".to_string(),
16870                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16871                        range: lsp::Range {
16872                            start: lsp::Position {
16873                                line: 8,
16874                                character: 1,
16875                            },
16876                            end: lsp::Position {
16877                                line: 8,
16878                                character: 1,
16879                            },
16880                        },
16881                        new_text: "include \"$0\"".to_string(),
16882                    })),
16883                    sort_text: Some("40b67681include".to_string()),
16884                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16885                    filter_text: Some("include".to_string()),
16886                    insert_text: Some("include \"$0\"".to_string()),
16887                    ..lsp::CompletionItem::default()
16888                }],
16889            })))
16890        });
16891    cx.update_editor(|editor, window, cx| {
16892        editor.show_completions(&ShowCompletions, window, cx);
16893    });
16894    cx.executor().run_until_parked();
16895    cx.update_editor(|editor, window, cx| {
16896        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16897    });
16898    cx.executor().run_until_parked();
16899    cx.assert_editor_state(
16900        "#ifndef BAR_H
16901#define BAR_H
16902
16903#include <stdbool.h>
16904
16905int fn_branch(bool do_branch1, bool do_branch2);
16906
16907#endif // BAR_H
16908#include \"ˇ\"",
16909    );
16910
16911    cx.lsp
16912        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16913            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16914                is_incomplete: true,
16915                item_defaults: None,
16916                items: vec![lsp::CompletionItem {
16917                    kind: Some(lsp::CompletionItemKind::FILE),
16918                    label: "AGL/".to_string(),
16919                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16920                        range: lsp::Range {
16921                            start: lsp::Position {
16922                                line: 8,
16923                                character: 10,
16924                            },
16925                            end: lsp::Position {
16926                                line: 8,
16927                                character: 11,
16928                            },
16929                        },
16930                        new_text: "AGL/".to_string(),
16931                    })),
16932                    sort_text: Some("40b67681AGL/".to_string()),
16933                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16934                    filter_text: Some("AGL/".to_string()),
16935                    insert_text: Some("AGL/".to_string()),
16936                    ..lsp::CompletionItem::default()
16937                }],
16938            })))
16939        });
16940    cx.update_editor(|editor, window, cx| {
16941        editor.show_completions(&ShowCompletions, window, cx);
16942    });
16943    cx.executor().run_until_parked();
16944    cx.update_editor(|editor, window, cx| {
16945        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16946    });
16947    cx.executor().run_until_parked();
16948    cx.assert_editor_state(
16949        r##"#ifndef BAR_H
16950#define BAR_H
16951
16952#include <stdbool.h>
16953
16954int fn_branch(bool do_branch1, bool do_branch2);
16955
16956#endif // BAR_H
16957#include "AGL/ˇ"##,
16958    );
16959
16960    cx.update_editor(|editor, window, cx| {
16961        editor.handle_input("\"", window, cx);
16962    });
16963    cx.executor().run_until_parked();
16964    cx.assert_editor_state(
16965        r##"#ifndef BAR_H
16966#define BAR_H
16967
16968#include <stdbool.h>
16969
16970int fn_branch(bool do_branch1, bool do_branch2);
16971
16972#endif // BAR_H
16973#include "AGL/"ˇ"##,
16974    );
16975}
16976
16977#[gpui::test]
16978async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16979    init_test(cx, |_| {});
16980
16981    let mut cx = EditorLspTestContext::new_rust(
16982        lsp::ServerCapabilities {
16983            completion_provider: Some(lsp::CompletionOptions {
16984                trigger_characters: Some(vec![".".to_string()]),
16985                resolve_provider: Some(false),
16986                ..lsp::CompletionOptions::default()
16987            }),
16988            ..lsp::ServerCapabilities::default()
16989        },
16990        cx,
16991    )
16992    .await;
16993
16994    cx.set_state("fn main() { let a = 2ˇ; }");
16995    cx.simulate_keystroke(".");
16996    let completion_item = lsp::CompletionItem {
16997        label: "Some".into(),
16998        kind: Some(lsp::CompletionItemKind::SNIPPET),
16999        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17000        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17001            kind: lsp::MarkupKind::Markdown,
17002            value: "```rust\nSome(2)\n```".to_string(),
17003        })),
17004        deprecated: Some(false),
17005        sort_text: Some("Some".to_string()),
17006        filter_text: Some("Some".to_string()),
17007        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17008        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17009            range: lsp::Range {
17010                start: lsp::Position {
17011                    line: 0,
17012                    character: 22,
17013                },
17014                end: lsp::Position {
17015                    line: 0,
17016                    character: 22,
17017                },
17018            },
17019            new_text: "Some(2)".to_string(),
17020        })),
17021        additional_text_edits: Some(vec![lsp::TextEdit {
17022            range: lsp::Range {
17023                start: lsp::Position {
17024                    line: 0,
17025                    character: 20,
17026                },
17027                end: lsp::Position {
17028                    line: 0,
17029                    character: 22,
17030                },
17031            },
17032            new_text: "".to_string(),
17033        }]),
17034        ..Default::default()
17035    };
17036
17037    let closure_completion_item = completion_item.clone();
17038    let counter = Arc::new(AtomicUsize::new(0));
17039    let counter_clone = counter.clone();
17040    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17041        let task_completion_item = closure_completion_item.clone();
17042        counter_clone.fetch_add(1, atomic::Ordering::Release);
17043        async move {
17044            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17045                is_incomplete: true,
17046                item_defaults: None,
17047                items: vec![task_completion_item],
17048            })))
17049        }
17050    });
17051
17052    cx.condition(|editor, _| editor.context_menu_visible())
17053        .await;
17054    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
17055    assert!(request.next().await.is_some());
17056    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17057
17058    cx.simulate_keystrokes("S o m");
17059    cx.condition(|editor, _| editor.context_menu_visible())
17060        .await;
17061    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
17062    assert!(request.next().await.is_some());
17063    assert!(request.next().await.is_some());
17064    assert!(request.next().await.is_some());
17065    request.close();
17066    assert!(request.next().await.is_none());
17067    assert_eq!(
17068        counter.load(atomic::Ordering::Acquire),
17069        4,
17070        "With the completions menu open, only one LSP request should happen per input"
17071    );
17072}
17073
17074#[gpui::test]
17075async fn test_toggle_comment(cx: &mut TestAppContext) {
17076    init_test(cx, |_| {});
17077    let mut cx = EditorTestContext::new(cx).await;
17078    let language = Arc::new(Language::new(
17079        LanguageConfig {
17080            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17081            ..Default::default()
17082        },
17083        Some(tree_sitter_rust::LANGUAGE.into()),
17084    ));
17085    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17086
17087    // If multiple selections intersect a line, the line is only toggled once.
17088    cx.set_state(indoc! {"
17089        fn a() {
17090            «//b();
17091            ˇ»// «c();
17092            //ˇ»  d();
17093        }
17094    "});
17095
17096    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17097
17098    cx.assert_editor_state(indoc! {"
17099        fn a() {
17100            «b();
17101            ˇ»«c();
17102            ˇ» d();
17103        }
17104    "});
17105
17106    // The comment prefix is inserted at the same column for every line in a
17107    // selection.
17108    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17109
17110    cx.assert_editor_state(indoc! {"
17111        fn a() {
17112            // «b();
17113            ˇ»// «c();
17114            ˇ» // d();
17115        }
17116    "});
17117
17118    // If a selection ends at the beginning of a line, that line is not toggled.
17119    cx.set_selections_state(indoc! {"
17120        fn a() {
17121            // b();
17122            «// c();
17123        ˇ»     // d();
17124        }
17125    "});
17126
17127    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17128
17129    cx.assert_editor_state(indoc! {"
17130        fn a() {
17131            // b();
17132            «c();
17133        ˇ»     // d();
17134        }
17135    "});
17136
17137    // If a selection span a single line and is empty, the line is toggled.
17138    cx.set_state(indoc! {"
17139        fn a() {
17140            a();
17141            b();
17142        ˇ
17143        }
17144    "});
17145
17146    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17147
17148    cx.assert_editor_state(indoc! {"
17149        fn a() {
17150            a();
17151            b();
17152        //•ˇ
17153        }
17154    "});
17155
17156    // If a selection span multiple lines, empty lines are not toggled.
17157    cx.set_state(indoc! {"
17158        fn a() {
17159            «a();
17160
17161            c();ˇ»
17162        }
17163    "});
17164
17165    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17166
17167    cx.assert_editor_state(indoc! {"
17168        fn a() {
17169            // «a();
17170
17171            // c();ˇ»
17172        }
17173    "});
17174
17175    // If a selection includes multiple comment prefixes, all lines are uncommented.
17176    cx.set_state(indoc! {"
17177        fn a() {
17178            «// a();
17179            /// b();
17180            //! c();ˇ»
17181        }
17182    "});
17183
17184    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17185
17186    cx.assert_editor_state(indoc! {"
17187        fn a() {
17188            «a();
17189            b();
17190            c();ˇ»
17191        }
17192    "});
17193}
17194
17195#[gpui::test]
17196async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
17197    init_test(cx, |_| {});
17198    let mut cx = EditorTestContext::new(cx).await;
17199    let language = Arc::new(Language::new(
17200        LanguageConfig {
17201            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17202            ..Default::default()
17203        },
17204        Some(tree_sitter_rust::LANGUAGE.into()),
17205    ));
17206    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17207
17208    let toggle_comments = &ToggleComments {
17209        advance_downwards: false,
17210        ignore_indent: true,
17211    };
17212
17213    // If multiple selections intersect a line, the line is only toggled once.
17214    cx.set_state(indoc! {"
17215        fn a() {
17216        //    «b();
17217        //    c();
17218        //    ˇ» d();
17219        }
17220    "});
17221
17222    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17223
17224    cx.assert_editor_state(indoc! {"
17225        fn a() {
17226            «b();
17227            c();
17228            ˇ» d();
17229        }
17230    "});
17231
17232    // The comment prefix is inserted at the beginning of each line
17233    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17234
17235    cx.assert_editor_state(indoc! {"
17236        fn a() {
17237        //    «b();
17238        //    c();
17239        //    ˇ» d();
17240        }
17241    "});
17242
17243    // If a selection ends at the beginning of a line, that line is not toggled.
17244    cx.set_selections_state(indoc! {"
17245        fn a() {
17246        //    b();
17247        //    «c();
17248        ˇ»//     d();
17249        }
17250    "});
17251
17252    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17253
17254    cx.assert_editor_state(indoc! {"
17255        fn a() {
17256        //    b();
17257            «c();
17258        ˇ»//     d();
17259        }
17260    "});
17261
17262    // If a selection span a single line and is empty, the line is toggled.
17263    cx.set_state(indoc! {"
17264        fn a() {
17265            a();
17266            b();
17267        ˇ
17268        }
17269    "});
17270
17271    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17272
17273    cx.assert_editor_state(indoc! {"
17274        fn a() {
17275            a();
17276            b();
17277        //ˇ
17278        }
17279    "});
17280
17281    // If a selection span multiple lines, empty lines are not toggled.
17282    cx.set_state(indoc! {"
17283        fn a() {
17284            «a();
17285
17286            c();ˇ»
17287        }
17288    "});
17289
17290    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17291
17292    cx.assert_editor_state(indoc! {"
17293        fn a() {
17294        //    «a();
17295
17296        //    c();ˇ»
17297        }
17298    "});
17299
17300    // If a selection includes multiple comment prefixes, all lines are uncommented.
17301    cx.set_state(indoc! {"
17302        fn a() {
17303        //    «a();
17304        ///    b();
17305        //!    c();ˇ»
17306        }
17307    "});
17308
17309    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17310
17311    cx.assert_editor_state(indoc! {"
17312        fn a() {
17313            «a();
17314            b();
17315            c();ˇ»
17316        }
17317    "});
17318}
17319
17320#[gpui::test]
17321async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
17322    init_test(cx, |_| {});
17323
17324    let language = Arc::new(Language::new(
17325        LanguageConfig {
17326            line_comments: vec!["// ".into()],
17327            ..Default::default()
17328        },
17329        Some(tree_sitter_rust::LANGUAGE.into()),
17330    ));
17331
17332    let mut cx = EditorTestContext::new(cx).await;
17333
17334    cx.language_registry().add(language.clone());
17335    cx.update_buffer(|buffer, cx| {
17336        buffer.set_language(Some(language), cx);
17337    });
17338
17339    let toggle_comments = &ToggleComments {
17340        advance_downwards: true,
17341        ignore_indent: false,
17342    };
17343
17344    // Single cursor on one line -> advance
17345    // Cursor moves horizontally 3 characters as well on non-blank line
17346    cx.set_state(indoc!(
17347        "fn a() {
17348             ˇdog();
17349             cat();
17350        }"
17351    ));
17352    cx.update_editor(|editor, window, cx| {
17353        editor.toggle_comments(toggle_comments, window, cx);
17354    });
17355    cx.assert_editor_state(indoc!(
17356        "fn a() {
17357             // dog();
17358             catˇ();
17359        }"
17360    ));
17361
17362    // Single selection on one line -> don't advance
17363    cx.set_state(indoc!(
17364        "fn a() {
17365             «dog()ˇ»;
17366             cat();
17367        }"
17368    ));
17369    cx.update_editor(|editor, window, cx| {
17370        editor.toggle_comments(toggle_comments, window, cx);
17371    });
17372    cx.assert_editor_state(indoc!(
17373        "fn a() {
17374             // «dog()ˇ»;
17375             cat();
17376        }"
17377    ));
17378
17379    // Multiple cursors on one line -> advance
17380    cx.set_state(indoc!(
17381        "fn a() {
17382             ˇdˇog();
17383             cat();
17384        }"
17385    ));
17386    cx.update_editor(|editor, window, cx| {
17387        editor.toggle_comments(toggle_comments, window, cx);
17388    });
17389    cx.assert_editor_state(indoc!(
17390        "fn a() {
17391             // dog();
17392             catˇ(ˇ);
17393        }"
17394    ));
17395
17396    // Multiple cursors on one line, with selection -> don't advance
17397    cx.set_state(indoc!(
17398        "fn a() {
17399             ˇdˇog«()ˇ»;
17400             cat();
17401        }"
17402    ));
17403    cx.update_editor(|editor, window, cx| {
17404        editor.toggle_comments(toggle_comments, window, cx);
17405    });
17406    cx.assert_editor_state(indoc!(
17407        "fn a() {
17408             // ˇdˇog«()ˇ»;
17409             cat();
17410        }"
17411    ));
17412
17413    // Single cursor on one line -> advance
17414    // Cursor moves to column 0 on blank line
17415    cx.set_state(indoc!(
17416        "fn a() {
17417             ˇdog();
17418
17419             cat();
17420        }"
17421    ));
17422    cx.update_editor(|editor, window, cx| {
17423        editor.toggle_comments(toggle_comments, window, cx);
17424    });
17425    cx.assert_editor_state(indoc!(
17426        "fn a() {
17427             // dog();
17428        ˇ
17429             cat();
17430        }"
17431    ));
17432
17433    // Single cursor on one line -> advance
17434    // Cursor starts and ends at column 0
17435    cx.set_state(indoc!(
17436        "fn a() {
17437         ˇ    dog();
17438             cat();
17439        }"
17440    ));
17441    cx.update_editor(|editor, window, cx| {
17442        editor.toggle_comments(toggle_comments, window, cx);
17443    });
17444    cx.assert_editor_state(indoc!(
17445        "fn a() {
17446             // dog();
17447         ˇ    cat();
17448        }"
17449    ));
17450}
17451
17452#[gpui::test]
17453async fn test_toggle_block_comment(cx: &mut TestAppContext) {
17454    init_test(cx, |_| {});
17455
17456    let mut cx = EditorTestContext::new(cx).await;
17457
17458    let html_language = Arc::new(
17459        Language::new(
17460            LanguageConfig {
17461                name: "HTML".into(),
17462                block_comment: Some(BlockCommentConfig {
17463                    start: "<!-- ".into(),
17464                    prefix: "".into(),
17465                    end: " -->".into(),
17466                    tab_size: 0,
17467                }),
17468                ..Default::default()
17469            },
17470            Some(tree_sitter_html::LANGUAGE.into()),
17471        )
17472        .with_injection_query(
17473            r#"
17474            (script_element
17475                (raw_text) @injection.content
17476                (#set! injection.language "javascript"))
17477            "#,
17478        )
17479        .unwrap(),
17480    );
17481
17482    let javascript_language = Arc::new(Language::new(
17483        LanguageConfig {
17484            name: "JavaScript".into(),
17485            line_comments: vec!["// ".into()],
17486            ..Default::default()
17487        },
17488        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17489    ));
17490
17491    cx.language_registry().add(html_language.clone());
17492    cx.language_registry().add(javascript_language);
17493    cx.update_buffer(|buffer, cx| {
17494        buffer.set_language(Some(html_language), cx);
17495    });
17496
17497    // Toggle comments for empty selections
17498    cx.set_state(
17499        &r#"
17500            <p>A</p>ˇ
17501            <p>B</p>ˇ
17502            <p>C</p>ˇ
17503        "#
17504        .unindent(),
17505    );
17506    cx.update_editor(|editor, window, cx| {
17507        editor.toggle_comments(&ToggleComments::default(), window, cx)
17508    });
17509    cx.assert_editor_state(
17510        &r#"
17511            <!-- <p>A</p>ˇ -->
17512            <!-- <p>B</p>ˇ -->
17513            <!-- <p>C</p>ˇ -->
17514        "#
17515        .unindent(),
17516    );
17517    cx.update_editor(|editor, window, cx| {
17518        editor.toggle_comments(&ToggleComments::default(), window, cx)
17519    });
17520    cx.assert_editor_state(
17521        &r#"
17522            <p>A</p>ˇ
17523            <p>B</p>ˇ
17524            <p>C</p>ˇ
17525        "#
17526        .unindent(),
17527    );
17528
17529    // Toggle comments for mixture of empty and non-empty selections, where
17530    // multiple selections occupy a given line.
17531    cx.set_state(
17532        &r#"
17533            <p>A«</p>
17534            <p>ˇ»B</p>ˇ
17535            <p>C«</p>
17536            <p>ˇ»D</p>ˇ
17537        "#
17538        .unindent(),
17539    );
17540
17541    cx.update_editor(|editor, window, cx| {
17542        editor.toggle_comments(&ToggleComments::default(), window, cx)
17543    });
17544    cx.assert_editor_state(
17545        &r#"
17546            <!-- <p>A«</p>
17547            <p>ˇ»B</p>ˇ -->
17548            <!-- <p>C«</p>
17549            <p>ˇ»D</p>ˇ -->
17550        "#
17551        .unindent(),
17552    );
17553    cx.update_editor(|editor, window, cx| {
17554        editor.toggle_comments(&ToggleComments::default(), window, cx)
17555    });
17556    cx.assert_editor_state(
17557        &r#"
17558            <p>A«</p>
17559            <p>ˇ»B</p>ˇ
17560            <p>C«</p>
17561            <p>ˇ»D</p>ˇ
17562        "#
17563        .unindent(),
17564    );
17565
17566    // Toggle comments when different languages are active for different
17567    // selections.
17568    cx.set_state(
17569        &r#"
17570            ˇ<script>
17571                ˇvar x = new Y();
17572            ˇ</script>
17573        "#
17574        .unindent(),
17575    );
17576    cx.executor().run_until_parked();
17577    cx.update_editor(|editor, window, cx| {
17578        editor.toggle_comments(&ToggleComments::default(), window, cx)
17579    });
17580    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17581    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17582    cx.assert_editor_state(
17583        &r#"
17584            <!-- ˇ<script> -->
17585                // ˇvar x = new Y();
17586            <!-- ˇ</script> -->
17587        "#
17588        .unindent(),
17589    );
17590}
17591
17592#[gpui::test]
17593fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17594    init_test(cx, |_| {});
17595
17596    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17597    let multibuffer = cx.new(|cx| {
17598        let mut multibuffer = MultiBuffer::new(ReadWrite);
17599        multibuffer.push_excerpts(
17600            buffer.clone(),
17601            [
17602                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17603                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17604            ],
17605            cx,
17606        );
17607        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17608        multibuffer
17609    });
17610
17611    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17612    editor.update_in(cx, |editor, window, cx| {
17613        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17615            s.select_ranges([
17616                Point::new(0, 0)..Point::new(0, 0),
17617                Point::new(1, 0)..Point::new(1, 0),
17618            ])
17619        });
17620
17621        editor.handle_input("X", window, cx);
17622        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17623        assert_eq!(
17624            editor.selections.ranges(&editor.display_snapshot(cx)),
17625            [
17626                Point::new(0, 1)..Point::new(0, 1),
17627                Point::new(1, 1)..Point::new(1, 1),
17628            ]
17629        );
17630
17631        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17633            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17634        });
17635        editor.backspace(&Default::default(), window, cx);
17636        assert_eq!(editor.text(cx), "Xa\nbbb");
17637        assert_eq!(
17638            editor.selections.ranges(&editor.display_snapshot(cx)),
17639            [Point::new(1, 0)..Point::new(1, 0)]
17640        );
17641
17642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17643            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17644        });
17645        editor.backspace(&Default::default(), window, cx);
17646        assert_eq!(editor.text(cx), "X\nbb");
17647        assert_eq!(
17648            editor.selections.ranges(&editor.display_snapshot(cx)),
17649            [Point::new(0, 1)..Point::new(0, 1)]
17650        );
17651    });
17652}
17653
17654#[gpui::test]
17655fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17656    init_test(cx, |_| {});
17657
17658    let markers = vec![('[', ']').into(), ('(', ')').into()];
17659    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17660        indoc! {"
17661            [aaaa
17662            (bbbb]
17663            cccc)",
17664        },
17665        markers.clone(),
17666    );
17667    let excerpt_ranges = markers.into_iter().map(|marker| {
17668        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17669        ExcerptRange::new(context)
17670    });
17671    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17672    let multibuffer = cx.new(|cx| {
17673        let mut multibuffer = MultiBuffer::new(ReadWrite);
17674        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17675        multibuffer
17676    });
17677
17678    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17679    editor.update_in(cx, |editor, window, cx| {
17680        let (expected_text, selection_ranges) = marked_text_ranges(
17681            indoc! {"
17682                aaaa
17683                bˇbbb
17684                bˇbbˇb
17685                cccc"
17686            },
17687            true,
17688        );
17689        assert_eq!(editor.text(cx), expected_text);
17690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17691            s.select_ranges(
17692                selection_ranges
17693                    .iter()
17694                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17695            )
17696        });
17697
17698        editor.handle_input("X", window, cx);
17699
17700        let (expected_text, expected_selections) = marked_text_ranges(
17701            indoc! {"
17702                aaaa
17703                bXˇbbXb
17704                bXˇbbXˇb
17705                cccc"
17706            },
17707            false,
17708        );
17709        assert_eq!(editor.text(cx), expected_text);
17710        assert_eq!(
17711            editor.selections.ranges(&editor.display_snapshot(cx)),
17712            expected_selections
17713                .iter()
17714                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17715                .collect::<Vec<_>>()
17716        );
17717
17718        editor.newline(&Newline, window, cx);
17719        let (expected_text, expected_selections) = marked_text_ranges(
17720            indoc! {"
17721                aaaa
17722                bX
17723                ˇbbX
17724                b
17725                bX
17726                ˇbbX
17727                ˇb
17728                cccc"
17729            },
17730            false,
17731        );
17732        assert_eq!(editor.text(cx), expected_text);
17733        assert_eq!(
17734            editor.selections.ranges(&editor.display_snapshot(cx)),
17735            expected_selections
17736                .iter()
17737                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17738                .collect::<Vec<_>>()
17739        );
17740    });
17741}
17742
17743#[gpui::test]
17744fn test_refresh_selections(cx: &mut TestAppContext) {
17745    init_test(cx, |_| {});
17746
17747    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17748    let mut excerpt1_id = None;
17749    let multibuffer = cx.new(|cx| {
17750        let mut multibuffer = MultiBuffer::new(ReadWrite);
17751        excerpt1_id = multibuffer
17752            .push_excerpts(
17753                buffer.clone(),
17754                [
17755                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17756                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17757                ],
17758                cx,
17759            )
17760            .into_iter()
17761            .next();
17762        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17763        multibuffer
17764    });
17765
17766    let editor = cx.add_window(|window, cx| {
17767        let mut editor = build_editor(multibuffer.clone(), window, cx);
17768        let snapshot = editor.snapshot(window, cx);
17769        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17770            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17771        });
17772        editor.begin_selection(
17773            Point::new(2, 1).to_display_point(&snapshot),
17774            true,
17775            1,
17776            window,
17777            cx,
17778        );
17779        assert_eq!(
17780            editor.selections.ranges(&editor.display_snapshot(cx)),
17781            [
17782                Point::new(1, 3)..Point::new(1, 3),
17783                Point::new(2, 1)..Point::new(2, 1),
17784            ]
17785        );
17786        editor
17787    });
17788
17789    // Refreshing selections is a no-op when excerpts haven't changed.
17790    _ = editor.update(cx, |editor, window, cx| {
17791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17792        assert_eq!(
17793            editor.selections.ranges(&editor.display_snapshot(cx)),
17794            [
17795                Point::new(1, 3)..Point::new(1, 3),
17796                Point::new(2, 1)..Point::new(2, 1),
17797            ]
17798        );
17799    });
17800
17801    multibuffer.update(cx, |multibuffer, cx| {
17802        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17803    });
17804    _ = editor.update(cx, |editor, window, cx| {
17805        // Removing an excerpt causes the first selection to become degenerate.
17806        assert_eq!(
17807            editor.selections.ranges(&editor.display_snapshot(cx)),
17808            [
17809                Point::new(0, 0)..Point::new(0, 0),
17810                Point::new(0, 1)..Point::new(0, 1)
17811            ]
17812        );
17813
17814        // Refreshing selections will relocate the first selection to the original buffer
17815        // location.
17816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17817        assert_eq!(
17818            editor.selections.ranges(&editor.display_snapshot(cx)),
17819            [
17820                Point::new(0, 1)..Point::new(0, 1),
17821                Point::new(0, 3)..Point::new(0, 3)
17822            ]
17823        );
17824        assert!(editor.selections.pending_anchor().is_some());
17825    });
17826}
17827
17828#[gpui::test]
17829fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17830    init_test(cx, |_| {});
17831
17832    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17833    let mut excerpt1_id = None;
17834    let multibuffer = cx.new(|cx| {
17835        let mut multibuffer = MultiBuffer::new(ReadWrite);
17836        excerpt1_id = multibuffer
17837            .push_excerpts(
17838                buffer.clone(),
17839                [
17840                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17841                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17842                ],
17843                cx,
17844            )
17845            .into_iter()
17846            .next();
17847        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17848        multibuffer
17849    });
17850
17851    let editor = cx.add_window(|window, cx| {
17852        let mut editor = build_editor(multibuffer.clone(), window, cx);
17853        let snapshot = editor.snapshot(window, cx);
17854        editor.begin_selection(
17855            Point::new(1, 3).to_display_point(&snapshot),
17856            false,
17857            1,
17858            window,
17859            cx,
17860        );
17861        assert_eq!(
17862            editor.selections.ranges(&editor.display_snapshot(cx)),
17863            [Point::new(1, 3)..Point::new(1, 3)]
17864        );
17865        editor
17866    });
17867
17868    multibuffer.update(cx, |multibuffer, cx| {
17869        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17870    });
17871    _ = editor.update(cx, |editor, window, cx| {
17872        assert_eq!(
17873            editor.selections.ranges(&editor.display_snapshot(cx)),
17874            [Point::new(0, 0)..Point::new(0, 0)]
17875        );
17876
17877        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17879        assert_eq!(
17880            editor.selections.ranges(&editor.display_snapshot(cx)),
17881            [Point::new(0, 3)..Point::new(0, 3)]
17882        );
17883        assert!(editor.selections.pending_anchor().is_some());
17884    });
17885}
17886
17887#[gpui::test]
17888async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17889    init_test(cx, |_| {});
17890
17891    let language = Arc::new(
17892        Language::new(
17893            LanguageConfig {
17894                brackets: BracketPairConfig {
17895                    pairs: vec![
17896                        BracketPair {
17897                            start: "{".to_string(),
17898                            end: "}".to_string(),
17899                            close: true,
17900                            surround: true,
17901                            newline: true,
17902                        },
17903                        BracketPair {
17904                            start: "/* ".to_string(),
17905                            end: " */".to_string(),
17906                            close: true,
17907                            surround: true,
17908                            newline: true,
17909                        },
17910                    ],
17911                    ..Default::default()
17912                },
17913                ..Default::default()
17914            },
17915            Some(tree_sitter_rust::LANGUAGE.into()),
17916        )
17917        .with_indents_query("")
17918        .unwrap(),
17919    );
17920
17921    let text = concat!(
17922        "{   }\n",     //
17923        "  x\n",       //
17924        "  /*   */\n", //
17925        "x\n",         //
17926        "{{} }\n",     //
17927    );
17928
17929    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17930    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17931    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17932    editor
17933        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17934        .await;
17935
17936    editor.update_in(cx, |editor, window, cx| {
17937        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17938            s.select_display_ranges([
17939                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17940                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17941                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17942            ])
17943        });
17944        editor.newline(&Newline, window, cx);
17945
17946        assert_eq!(
17947            editor.buffer().read(cx).read(cx).text(),
17948            concat!(
17949                "{ \n",    // Suppress rustfmt
17950                "\n",      //
17951                "}\n",     //
17952                "  x\n",   //
17953                "  /* \n", //
17954                "  \n",    //
17955                "  */\n",  //
17956                "x\n",     //
17957                "{{} \n",  //
17958                "}\n",     //
17959            )
17960        );
17961    });
17962}
17963
17964#[gpui::test]
17965fn test_highlighted_ranges(cx: &mut TestAppContext) {
17966    init_test(cx, |_| {});
17967
17968    let editor = cx.add_window(|window, cx| {
17969        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17970        build_editor(buffer, window, cx)
17971    });
17972
17973    _ = editor.update(cx, |editor, window, cx| {
17974        let buffer = editor.buffer.read(cx).snapshot(cx);
17975
17976        let anchor_range =
17977            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17978
17979        editor.highlight_background(
17980            HighlightKey::ColorizeBracket(0),
17981            &[
17982                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17983                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17984                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17985                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17986            ],
17987            |_, _| Hsla::red(),
17988            cx,
17989        );
17990        editor.highlight_background(
17991            HighlightKey::ColorizeBracket(1),
17992            &[
17993                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17994                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17995                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17996                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17997            ],
17998            |_, _| Hsla::green(),
17999            cx,
18000        );
18001
18002        let snapshot = editor.snapshot(window, cx);
18003        let highlighted_ranges = editor.sorted_background_highlights_in_range(
18004            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
18005            &snapshot,
18006            cx.theme(),
18007        );
18008        assert_eq!(
18009            highlighted_ranges,
18010            &[
18011                (
18012                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
18013                    Hsla::green(),
18014                ),
18015                (
18016                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
18017                    Hsla::red(),
18018                ),
18019                (
18020                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
18021                    Hsla::green(),
18022                ),
18023                (
18024                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18025                    Hsla::red(),
18026                ),
18027            ]
18028        );
18029        assert_eq!(
18030            editor.sorted_background_highlights_in_range(
18031                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
18032                &snapshot,
18033                cx.theme(),
18034            ),
18035            &[(
18036                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18037                Hsla::red(),
18038            )]
18039        );
18040    });
18041}
18042
18043#[gpui::test]
18044async fn test_following(cx: &mut TestAppContext) {
18045    init_test(cx, |_| {});
18046
18047    let fs = FakeFs::new(cx.executor());
18048    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18049
18050    let buffer = project.update(cx, |project, cx| {
18051        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
18052        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
18053    });
18054    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18055    let follower = cx.update(|cx| {
18056        cx.open_window(
18057            WindowOptions {
18058                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
18059                    gpui::Point::new(px(0.), px(0.)),
18060                    gpui::Point::new(px(10.), px(80.)),
18061                ))),
18062                ..Default::default()
18063            },
18064            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
18065        )
18066        .unwrap()
18067    });
18068
18069    let is_still_following = Rc::new(RefCell::new(true));
18070    let follower_edit_event_count = Rc::new(RefCell::new(0));
18071    let pending_update = Rc::new(RefCell::new(None));
18072    let leader_entity = leader.root(cx).unwrap();
18073    let follower_entity = follower.root(cx).unwrap();
18074    _ = follower.update(cx, {
18075        let update = pending_update.clone();
18076        let is_still_following = is_still_following.clone();
18077        let follower_edit_event_count = follower_edit_event_count.clone();
18078        |_, window, cx| {
18079            cx.subscribe_in(
18080                &leader_entity,
18081                window,
18082                move |_, leader, event, window, cx| {
18083                    leader.update(cx, |leader, cx| {
18084                        leader.add_event_to_update_proto(
18085                            event,
18086                            &mut update.borrow_mut(),
18087                            window,
18088                            cx,
18089                        );
18090                    });
18091                },
18092            )
18093            .detach();
18094
18095            cx.subscribe_in(
18096                &follower_entity,
18097                window,
18098                move |_, _, event: &EditorEvent, _window, _cx| {
18099                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
18100                        *is_still_following.borrow_mut() = false;
18101                    }
18102
18103                    if let EditorEvent::BufferEdited = event {
18104                        *follower_edit_event_count.borrow_mut() += 1;
18105                    }
18106                },
18107            )
18108            .detach();
18109        }
18110    });
18111
18112    // Update the selections only
18113    _ = leader.update(cx, |leader, window, cx| {
18114        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18115            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18116        });
18117    });
18118    follower
18119        .update(cx, |follower, window, cx| {
18120            follower.apply_update_proto(
18121                &project,
18122                pending_update.borrow_mut().take().unwrap(),
18123                window,
18124                cx,
18125            )
18126        })
18127        .unwrap()
18128        .await
18129        .unwrap();
18130    _ = follower.update(cx, |follower, _, cx| {
18131        assert_eq!(
18132            follower.selections.ranges(&follower.display_snapshot(cx)),
18133            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
18134        );
18135    });
18136    assert!(*is_still_following.borrow());
18137    assert_eq!(*follower_edit_event_count.borrow(), 0);
18138
18139    // Update the scroll position only
18140    _ = leader.update(cx, |leader, window, cx| {
18141        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18142    });
18143    follower
18144        .update(cx, |follower, window, cx| {
18145            follower.apply_update_proto(
18146                &project,
18147                pending_update.borrow_mut().take().unwrap(),
18148                window,
18149                cx,
18150            )
18151        })
18152        .unwrap()
18153        .await
18154        .unwrap();
18155    assert_eq!(
18156        follower
18157            .update(cx, |follower, _, cx| follower.scroll_position(cx))
18158            .unwrap(),
18159        gpui::Point::new(1.5, 3.5)
18160    );
18161    assert!(*is_still_following.borrow());
18162    assert_eq!(*follower_edit_event_count.borrow(), 0);
18163
18164    // Update the selections and scroll position. The follower's scroll position is updated
18165    // via autoscroll, not via the leader's exact scroll position.
18166    _ = leader.update(cx, |leader, window, cx| {
18167        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18168            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
18169        });
18170        leader.request_autoscroll(Autoscroll::newest(), cx);
18171        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18172    });
18173    follower
18174        .update(cx, |follower, window, cx| {
18175            follower.apply_update_proto(
18176                &project,
18177                pending_update.borrow_mut().take().unwrap(),
18178                window,
18179                cx,
18180            )
18181        })
18182        .unwrap()
18183        .await
18184        .unwrap();
18185    _ = follower.update(cx, |follower, _, cx| {
18186        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
18187        assert_eq!(
18188            follower.selections.ranges(&follower.display_snapshot(cx)),
18189            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
18190        );
18191    });
18192    assert!(*is_still_following.borrow());
18193
18194    // Creating a pending selection that precedes another selection
18195    _ = leader.update(cx, |leader, window, cx| {
18196        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18197            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18198        });
18199        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
18200    });
18201    follower
18202        .update(cx, |follower, window, cx| {
18203            follower.apply_update_proto(
18204                &project,
18205                pending_update.borrow_mut().take().unwrap(),
18206                window,
18207                cx,
18208            )
18209        })
18210        .unwrap()
18211        .await
18212        .unwrap();
18213    _ = follower.update(cx, |follower, _, cx| {
18214        assert_eq!(
18215            follower.selections.ranges(&follower.display_snapshot(cx)),
18216            vec![
18217                MultiBufferOffset(0)..MultiBufferOffset(0),
18218                MultiBufferOffset(1)..MultiBufferOffset(1)
18219            ]
18220        );
18221    });
18222    assert!(*is_still_following.borrow());
18223
18224    // Extend the pending selection so that it surrounds another selection
18225    _ = leader.update(cx, |leader, window, cx| {
18226        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
18227    });
18228    follower
18229        .update(cx, |follower, window, cx| {
18230            follower.apply_update_proto(
18231                &project,
18232                pending_update.borrow_mut().take().unwrap(),
18233                window,
18234                cx,
18235            )
18236        })
18237        .unwrap()
18238        .await
18239        .unwrap();
18240    _ = follower.update(cx, |follower, _, cx| {
18241        assert_eq!(
18242            follower.selections.ranges(&follower.display_snapshot(cx)),
18243            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
18244        );
18245    });
18246
18247    // Scrolling locally breaks the follow
18248    _ = follower.update(cx, |follower, window, cx| {
18249        let top_anchor = follower
18250            .buffer()
18251            .read(cx)
18252            .read(cx)
18253            .anchor_after(MultiBufferOffset(0));
18254        follower.set_scroll_anchor(
18255            ScrollAnchor {
18256                anchor: top_anchor,
18257                offset: gpui::Point::new(0.0, 0.5),
18258            },
18259            window,
18260            cx,
18261        );
18262    });
18263    assert!(!(*is_still_following.borrow()));
18264}
18265
18266#[gpui::test]
18267async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
18268    init_test(cx, |_| {});
18269
18270    let fs = FakeFs::new(cx.executor());
18271    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18272    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
18273    let workspace = window
18274        .read_with(cx, |mw, _| mw.workspace().clone())
18275        .unwrap();
18276    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
18277
18278    let cx = &mut VisualTestContext::from_window(*window, cx);
18279
18280    let leader = pane.update_in(cx, |_, window, cx| {
18281        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
18282        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
18283    });
18284
18285    // Start following the editor when it has no excerpts.
18286    let mut state_message =
18287        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18288    let workspace_entity = workspace.clone();
18289    let follower_1 = cx
18290        .update_window(*window, |_, window, cx| {
18291            Editor::from_state_proto(
18292                workspace_entity,
18293                ViewId {
18294                    creator: CollaboratorId::PeerId(PeerId::default()),
18295                    id: 0,
18296                },
18297                &mut state_message,
18298                window,
18299                cx,
18300            )
18301        })
18302        .unwrap()
18303        .unwrap()
18304        .await
18305        .unwrap();
18306
18307    let update_message = Rc::new(RefCell::new(None));
18308    follower_1.update_in(cx, {
18309        let update = update_message.clone();
18310        |_, window, cx| {
18311            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
18312                leader.update(cx, |leader, cx| {
18313                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
18314                });
18315            })
18316            .detach();
18317        }
18318    });
18319
18320    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
18321        (
18322            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
18323            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
18324        )
18325    });
18326
18327    // Insert some excerpts.
18328    leader.update(cx, |leader, cx| {
18329        leader.buffer.update(cx, |multibuffer, cx| {
18330            multibuffer.set_excerpts_for_path(
18331                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
18332                buffer_1.clone(),
18333                vec![
18334                    Point::row_range(0..3),
18335                    Point::row_range(1..6),
18336                    Point::row_range(12..15),
18337                ],
18338                0,
18339                cx,
18340            );
18341            multibuffer.set_excerpts_for_path(
18342                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
18343                buffer_2.clone(),
18344                vec![Point::row_range(0..6), Point::row_range(8..12)],
18345                0,
18346                cx,
18347            );
18348        });
18349    });
18350
18351    // Apply the update of adding the excerpts.
18352    follower_1
18353        .update_in(cx, |follower, window, cx| {
18354            follower.apply_update_proto(
18355                &project,
18356                update_message.borrow().clone().unwrap(),
18357                window,
18358                cx,
18359            )
18360        })
18361        .await
18362        .unwrap();
18363    assert_eq!(
18364        follower_1.update(cx, |editor, cx| editor.text(cx)),
18365        leader.update(cx, |editor, cx| editor.text(cx))
18366    );
18367    update_message.borrow_mut().take();
18368
18369    // Start following separately after it already has excerpts.
18370    let mut state_message =
18371        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18372    let workspace_entity = workspace.clone();
18373    let follower_2 = cx
18374        .update_window(*window, |_, window, cx| {
18375            Editor::from_state_proto(
18376                workspace_entity,
18377                ViewId {
18378                    creator: CollaboratorId::PeerId(PeerId::default()),
18379                    id: 0,
18380                },
18381                &mut state_message,
18382                window,
18383                cx,
18384            )
18385        })
18386        .unwrap()
18387        .unwrap()
18388        .await
18389        .unwrap();
18390    assert_eq!(
18391        follower_2.update(cx, |editor, cx| editor.text(cx)),
18392        leader.update(cx, |editor, cx| editor.text(cx))
18393    );
18394
18395    // Remove some excerpts.
18396    leader.update(cx, |leader, cx| {
18397        leader.buffer.update(cx, |multibuffer, cx| {
18398            let excerpt_ids = multibuffer.excerpt_ids();
18399            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
18400            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
18401        });
18402    });
18403
18404    // Apply the update of removing the excerpts.
18405    follower_1
18406        .update_in(cx, |follower, window, cx| {
18407            follower.apply_update_proto(
18408                &project,
18409                update_message.borrow().clone().unwrap(),
18410                window,
18411                cx,
18412            )
18413        })
18414        .await
18415        .unwrap();
18416    follower_2
18417        .update_in(cx, |follower, window, cx| {
18418            follower.apply_update_proto(
18419                &project,
18420                update_message.borrow().clone().unwrap(),
18421                window,
18422                cx,
18423            )
18424        })
18425        .await
18426        .unwrap();
18427    update_message.borrow_mut().take();
18428    assert_eq!(
18429        follower_1.update(cx, |editor, cx| editor.text(cx)),
18430        leader.update(cx, |editor, cx| editor.text(cx))
18431    );
18432}
18433
18434#[gpui::test]
18435async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18436    init_test(cx, |_| {});
18437
18438    let mut cx = EditorTestContext::new(cx).await;
18439    let lsp_store =
18440        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
18441
18442    cx.set_state(indoc! {"
18443        ˇfn func(abc def: i32) -> u32 {
18444        }
18445    "});
18446
18447    cx.update(|_, cx| {
18448        lsp_store.update(cx, |lsp_store, cx| {
18449            lsp_store
18450                .update_diagnostics(
18451                    LanguageServerId(0),
18452                    lsp::PublishDiagnosticsParams {
18453                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
18454                        version: None,
18455                        diagnostics: vec![
18456                            lsp::Diagnostic {
18457                                range: lsp::Range::new(
18458                                    lsp::Position::new(0, 11),
18459                                    lsp::Position::new(0, 12),
18460                                ),
18461                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18462                                ..Default::default()
18463                            },
18464                            lsp::Diagnostic {
18465                                range: lsp::Range::new(
18466                                    lsp::Position::new(0, 12),
18467                                    lsp::Position::new(0, 15),
18468                                ),
18469                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18470                                ..Default::default()
18471                            },
18472                            lsp::Diagnostic {
18473                                range: lsp::Range::new(
18474                                    lsp::Position::new(0, 25),
18475                                    lsp::Position::new(0, 28),
18476                                ),
18477                                severity: Some(lsp::DiagnosticSeverity::ERROR),
18478                                ..Default::default()
18479                            },
18480                        ],
18481                    },
18482                    None,
18483                    DiagnosticSourceKind::Pushed,
18484                    &[],
18485                    cx,
18486                )
18487                .unwrap()
18488        });
18489    });
18490
18491    executor.run_until_parked();
18492
18493    cx.update_editor(|editor, window, cx| {
18494        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18495    });
18496
18497    cx.assert_editor_state(indoc! {"
18498        fn func(abc def: i32) -> ˇu32 {
18499        }
18500    "});
18501
18502    cx.update_editor(|editor, window, cx| {
18503        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18504    });
18505
18506    cx.assert_editor_state(indoc! {"
18507        fn func(abc ˇdef: i32) -> u32 {
18508        }
18509    "});
18510
18511    cx.update_editor(|editor, window, cx| {
18512        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18513    });
18514
18515    cx.assert_editor_state(indoc! {"
18516        fn func(abcˇ def: i32) -> u32 {
18517        }
18518    "});
18519
18520    cx.update_editor(|editor, window, cx| {
18521        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
18522    });
18523
18524    cx.assert_editor_state(indoc! {"
18525        fn func(abc def: i32) -> ˇu32 {
18526        }
18527    "});
18528}
18529
18530#[gpui::test]
18531async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18532    init_test(cx, |_| {});
18533
18534    let mut cx = EditorTestContext::new(cx).await;
18535
18536    let diff_base = r#"
18537        use some::mod;
18538
18539        const A: u32 = 42;
18540
18541        fn main() {
18542            println!("hello");
18543
18544            println!("world");
18545        }
18546        "#
18547    .unindent();
18548
18549    // Edits are modified, removed, modified, added
18550    cx.set_state(
18551        &r#"
18552        use some::modified;
18553
18554        ˇ
18555        fn main() {
18556            println!("hello there");
18557
18558            println!("around the");
18559            println!("world");
18560        }
18561        "#
18562        .unindent(),
18563    );
18564
18565    cx.set_head_text(&diff_base);
18566    executor.run_until_parked();
18567
18568    cx.update_editor(|editor, window, cx| {
18569        //Wrap around the bottom of the buffer
18570        for _ in 0..3 {
18571            editor.go_to_next_hunk(&GoToHunk, window, cx);
18572        }
18573    });
18574
18575    cx.assert_editor_state(
18576        &r#"
18577        ˇuse some::modified;
18578
18579
18580        fn main() {
18581            println!("hello there");
18582
18583            println!("around the");
18584            println!("world");
18585        }
18586        "#
18587        .unindent(),
18588    );
18589
18590    cx.update_editor(|editor, window, cx| {
18591        //Wrap around the top of the buffer
18592        for _ in 0..2 {
18593            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18594        }
18595    });
18596
18597    cx.assert_editor_state(
18598        &r#"
18599        use some::modified;
18600
18601
18602        fn main() {
18603        ˇ    println!("hello there");
18604
18605            println!("around the");
18606            println!("world");
18607        }
18608        "#
18609        .unindent(),
18610    );
18611
18612    cx.update_editor(|editor, window, cx| {
18613        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18614    });
18615
18616    cx.assert_editor_state(
18617        &r#"
18618        use some::modified;
18619
18620        ˇ
18621        fn main() {
18622            println!("hello there");
18623
18624            println!("around the");
18625            println!("world");
18626        }
18627        "#
18628        .unindent(),
18629    );
18630
18631    cx.update_editor(|editor, window, cx| {
18632        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18633    });
18634
18635    cx.assert_editor_state(
18636        &r#"
18637        ˇuse some::modified;
18638
18639
18640        fn main() {
18641            println!("hello there");
18642
18643            println!("around the");
18644            println!("world");
18645        }
18646        "#
18647        .unindent(),
18648    );
18649
18650    cx.update_editor(|editor, window, cx| {
18651        for _ in 0..2 {
18652            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18653        }
18654    });
18655
18656    cx.assert_editor_state(
18657        &r#"
18658        use some::modified;
18659
18660
18661        fn main() {
18662        ˇ    println!("hello there");
18663
18664            println!("around the");
18665            println!("world");
18666        }
18667        "#
18668        .unindent(),
18669    );
18670
18671    cx.update_editor(|editor, window, cx| {
18672        editor.fold(&Fold, window, cx);
18673    });
18674
18675    cx.update_editor(|editor, window, cx| {
18676        editor.go_to_next_hunk(&GoToHunk, window, cx);
18677    });
18678
18679    cx.assert_editor_state(
18680        &r#"
18681        ˇuse some::modified;
18682
18683
18684        fn main() {
18685            println!("hello there");
18686
18687            println!("around the");
18688            println!("world");
18689        }
18690        "#
18691        .unindent(),
18692    );
18693}
18694
18695#[test]
18696fn test_split_words() {
18697    fn split(text: &str) -> Vec<&str> {
18698        split_words(text).collect()
18699    }
18700
18701    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18702    assert_eq!(split("hello_world"), &["hello_", "world"]);
18703    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18704    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18705    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18706    assert_eq!(split("helloworld"), &["helloworld"]);
18707
18708    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18709}
18710
18711#[test]
18712fn test_split_words_for_snippet_prefix() {
18713    fn split(text: &str) -> Vec<&str> {
18714        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18715    }
18716
18717    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18718    assert_eq!(split("hello_world"), &["hello_world"]);
18719    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18720    assert_eq!(split("Hello_World"), &["Hello_World"]);
18721    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18722    assert_eq!(split("helloworld"), &["helloworld"]);
18723    assert_eq!(
18724        split("this@is!@#$^many   . symbols"),
18725        &[
18726            "symbols",
18727            " symbols",
18728            ". symbols",
18729            " . symbols",
18730            "  . symbols",
18731            "   . symbols",
18732            "many   . symbols",
18733            "^many   . symbols",
18734            "$^many   . symbols",
18735            "#$^many   . symbols",
18736            "@#$^many   . symbols",
18737            "!@#$^many   . symbols",
18738            "is!@#$^many   . symbols",
18739            "@is!@#$^many   . symbols",
18740            "this@is!@#$^many   . symbols",
18741        ],
18742    );
18743    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18744}
18745
18746#[gpui::test]
18747async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18748    init_test(cx, |_| {});
18749
18750    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18751
18752    #[track_caller]
18753    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18754        let _state_context = cx.set_state(before);
18755        cx.run_until_parked();
18756        cx.update_editor(|editor, window, cx| {
18757            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18758        });
18759        cx.run_until_parked();
18760        cx.assert_editor_state(after);
18761    }
18762
18763    // Outside bracket jumps to outside of matching bracket
18764    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18765    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18766
18767    // Inside bracket jumps to inside of matching bracket
18768    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18769    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18770
18771    // When outside a bracket and inside, favor jumping to the inside bracket
18772    assert(
18773        "console.log('foo', [1, 2, 3]ˇ);",
18774        "console.log('foo', ˇ[1, 2, 3]);",
18775        &mut cx,
18776    );
18777    assert(
18778        "console.log(ˇ'foo', [1, 2, 3]);",
18779        "console.log('foo'ˇ, [1, 2, 3]);",
18780        &mut cx,
18781    );
18782
18783    // Bias forward if two options are equally likely
18784    assert(
18785        "let result = curried_fun()ˇ();",
18786        "let result = curried_fun()()ˇ;",
18787        &mut cx,
18788    );
18789
18790    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18791    assert(
18792        indoc! {"
18793            function test() {
18794                console.log('test')ˇ
18795            }"},
18796        indoc! {"
18797            function test() {
18798                console.logˇ('test')
18799            }"},
18800        &mut cx,
18801    );
18802}
18803
18804#[gpui::test]
18805async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18806    init_test(cx, |_| {});
18807    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18808    language_registry.add(markdown_lang());
18809    language_registry.add(rust_lang());
18810    let buffer = cx.new(|cx| {
18811        let mut buffer = language::Buffer::local(
18812            indoc! {"
18813            ```rs
18814            impl Worktree {
18815                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18816                }
18817            }
18818            ```
18819        "},
18820            cx,
18821        );
18822        buffer.set_language_registry(language_registry.clone());
18823        buffer.set_language(Some(markdown_lang()), cx);
18824        buffer
18825    });
18826    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18827    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18828    cx.executor().run_until_parked();
18829    _ = editor.update(cx, |editor, window, cx| {
18830        // Case 1: Test outer enclosing brackets
18831        select_ranges(
18832            editor,
18833            &indoc! {"
18834                ```rs
18835                impl Worktree {
18836                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18837                    }
1883818839                ```
18840            "},
18841            window,
18842            cx,
18843        );
18844        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18845        assert_text_with_selections(
18846            editor,
18847            &indoc! {"
18848                ```rs
18849                impl Worktree ˇ{
18850                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18851                    }
18852                }
18853                ```
18854            "},
18855            cx,
18856        );
18857        // Case 2: Test inner enclosing brackets
18858        select_ranges(
18859            editor,
18860            &indoc! {"
18861                ```rs
18862                impl Worktree {
18863                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1886418865                }
18866                ```
18867            "},
18868            window,
18869            cx,
18870        );
18871        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18872        assert_text_with_selections(
18873            editor,
18874            &indoc! {"
18875                ```rs
18876                impl Worktree {
18877                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18878                    }
18879                }
18880                ```
18881            "},
18882            cx,
18883        );
18884    });
18885}
18886
18887#[gpui::test]
18888async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18889    init_test(cx, |_| {});
18890
18891    let fs = FakeFs::new(cx.executor());
18892    fs.insert_tree(
18893        path!("/a"),
18894        json!({
18895            "main.rs": "fn main() { let a = 5; }",
18896            "other.rs": "// Test file",
18897        }),
18898    )
18899    .await;
18900    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18901
18902    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18903    language_registry.add(Arc::new(Language::new(
18904        LanguageConfig {
18905            name: "Rust".into(),
18906            matcher: LanguageMatcher {
18907                path_suffixes: vec!["rs".to_string()],
18908                ..Default::default()
18909            },
18910            brackets: BracketPairConfig {
18911                pairs: vec![BracketPair {
18912                    start: "{".to_string(),
18913                    end: "}".to_string(),
18914                    close: true,
18915                    surround: true,
18916                    newline: true,
18917                }],
18918                disabled_scopes_by_bracket_ix: Vec::new(),
18919            },
18920            ..Default::default()
18921        },
18922        Some(tree_sitter_rust::LANGUAGE.into()),
18923    )));
18924    let mut fake_servers = language_registry.register_fake_lsp(
18925        "Rust",
18926        FakeLspAdapter {
18927            capabilities: lsp::ServerCapabilities {
18928                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18929                    first_trigger_character: "{".to_string(),
18930                    more_trigger_character: None,
18931                }),
18932                ..Default::default()
18933            },
18934            ..Default::default()
18935        },
18936    );
18937
18938    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
18939    let workspace = window
18940        .read_with(cx, |mw, _| mw.workspace().clone())
18941        .unwrap();
18942
18943    let cx = &mut VisualTestContext::from_window(*window, cx);
18944
18945    let worktree_id = workspace.update_in(cx, |workspace, _, cx| {
18946        workspace.project().update(cx, |project, cx| {
18947            project.worktrees(cx).next().unwrap().read(cx).id()
18948        })
18949    });
18950
18951    let buffer = project
18952        .update(cx, |project, cx| {
18953            project.open_local_buffer(path!("/a/main.rs"), cx)
18954        })
18955        .await
18956        .unwrap();
18957    let editor_handle = workspace
18958        .update_in(cx, |workspace, window, cx| {
18959            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18960        })
18961        .await
18962        .unwrap()
18963        .downcast::<Editor>()
18964        .unwrap();
18965
18966    let fake_server = fake_servers.next().await.unwrap();
18967
18968    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18969        |params, _| async move {
18970            assert_eq!(
18971                params.text_document_position.text_document.uri,
18972                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18973            );
18974            assert_eq!(
18975                params.text_document_position.position,
18976                lsp::Position::new(0, 21),
18977            );
18978
18979            Ok(Some(vec![lsp::TextEdit {
18980                new_text: "]".to_string(),
18981                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18982            }]))
18983        },
18984    );
18985
18986    editor_handle.update_in(cx, |editor, window, cx| {
18987        window.focus(&editor.focus_handle(cx), cx);
18988        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18989            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18990        });
18991        editor.handle_input("{", window, cx);
18992    });
18993
18994    cx.executor().run_until_parked();
18995
18996    buffer.update(cx, |buffer, _| {
18997        assert_eq!(
18998            buffer.text(),
18999            "fn main() { let a = {5}; }",
19000            "No extra braces from on type formatting should appear in the buffer"
19001        )
19002    });
19003}
19004
19005#[gpui::test(iterations = 20, seeds(31))]
19006async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
19007    init_test(cx, |_| {});
19008
19009    let mut cx = EditorLspTestContext::new_rust(
19010        lsp::ServerCapabilities {
19011            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
19012                first_trigger_character: ".".to_string(),
19013                more_trigger_character: None,
19014            }),
19015            ..Default::default()
19016        },
19017        cx,
19018    )
19019    .await;
19020
19021    cx.update_buffer(|buffer, _| {
19022        // This causes autoindent to be async.
19023        buffer.set_sync_parse_timeout(None)
19024    });
19025
19026    cx.set_state("fn c() {\n    d()ˇ\n}\n");
19027    cx.simulate_keystroke("\n");
19028    cx.run_until_parked();
19029
19030    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
19031    let mut request =
19032        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
19033            let buffer_cloned = buffer_cloned.clone();
19034            async move {
19035                buffer_cloned.update(&mut cx, |buffer, _| {
19036                    assert_eq!(
19037                        buffer.text(),
19038                        "fn c() {\n    d()\n        .\n}\n",
19039                        "OnTypeFormatting should triggered after autoindent applied"
19040                    )
19041                });
19042
19043                Ok(Some(vec![]))
19044            }
19045        });
19046
19047    cx.simulate_keystroke(".");
19048    cx.run_until_parked();
19049
19050    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
19051    assert!(request.next().await.is_some());
19052    request.close();
19053    assert!(request.next().await.is_none());
19054}
19055
19056#[gpui::test]
19057async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
19058    init_test(cx, |_| {});
19059
19060    let fs = FakeFs::new(cx.executor());
19061    fs.insert_tree(
19062        path!("/a"),
19063        json!({
19064            "main.rs": "fn main() { let a = 5; }",
19065            "other.rs": "// Test file",
19066        }),
19067    )
19068    .await;
19069
19070    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19071
19072    let server_restarts = Arc::new(AtomicUsize::new(0));
19073    let closure_restarts = Arc::clone(&server_restarts);
19074    let language_server_name = "test language server";
19075    let language_name: LanguageName = "Rust".into();
19076
19077    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19078    language_registry.add(Arc::new(Language::new(
19079        LanguageConfig {
19080            name: language_name.clone(),
19081            matcher: LanguageMatcher {
19082                path_suffixes: vec!["rs".to_string()],
19083                ..Default::default()
19084            },
19085            ..Default::default()
19086        },
19087        Some(tree_sitter_rust::LANGUAGE.into()),
19088    )));
19089    let mut fake_servers = language_registry.register_fake_lsp(
19090        "Rust",
19091        FakeLspAdapter {
19092            name: language_server_name,
19093            initialization_options: Some(json!({
19094                "testOptionValue": true
19095            })),
19096            initializer: Some(Box::new(move |fake_server| {
19097                let task_restarts = Arc::clone(&closure_restarts);
19098                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
19099                    task_restarts.fetch_add(1, atomic::Ordering::Release);
19100                    futures::future::ready(Ok(()))
19101                });
19102            })),
19103            ..Default::default()
19104        },
19105    );
19106
19107    let _window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19108    let _buffer = project
19109        .update(cx, |project, cx| {
19110            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
19111        })
19112        .await
19113        .unwrap();
19114    let _fake_server = fake_servers.next().await.unwrap();
19115    update_test_language_settings(cx, |language_settings| {
19116        language_settings.languages.0.insert(
19117            language_name.clone().0.to_string(),
19118            LanguageSettingsContent {
19119                tab_size: NonZeroU32::new(8),
19120                ..Default::default()
19121            },
19122        );
19123    });
19124    cx.executor().run_until_parked();
19125    assert_eq!(
19126        server_restarts.load(atomic::Ordering::Acquire),
19127        0,
19128        "Should not restart LSP server on an unrelated change"
19129    );
19130
19131    update_test_project_settings(cx, |project_settings| {
19132        project_settings.lsp.0.insert(
19133            "Some other server name".into(),
19134            LspSettings {
19135                binary: None,
19136                settings: None,
19137                initialization_options: Some(json!({
19138                    "some other init value": false
19139                })),
19140                enable_lsp_tasks: false,
19141                fetch: None,
19142            },
19143        );
19144    });
19145    cx.executor().run_until_parked();
19146    assert_eq!(
19147        server_restarts.load(atomic::Ordering::Acquire),
19148        0,
19149        "Should not restart LSP server on an unrelated LSP settings change"
19150    );
19151
19152    update_test_project_settings(cx, |project_settings| {
19153        project_settings.lsp.0.insert(
19154            language_server_name.into(),
19155            LspSettings {
19156                binary: None,
19157                settings: None,
19158                initialization_options: Some(json!({
19159                    "anotherInitValue": false
19160                })),
19161                enable_lsp_tasks: false,
19162                fetch: None,
19163            },
19164        );
19165    });
19166    cx.executor().run_until_parked();
19167    assert_eq!(
19168        server_restarts.load(atomic::Ordering::Acquire),
19169        1,
19170        "Should restart LSP server on a related LSP settings change"
19171    );
19172
19173    update_test_project_settings(cx, |project_settings| {
19174        project_settings.lsp.0.insert(
19175            language_server_name.into(),
19176            LspSettings {
19177                binary: None,
19178                settings: None,
19179                initialization_options: Some(json!({
19180                    "anotherInitValue": false
19181                })),
19182                enable_lsp_tasks: false,
19183                fetch: None,
19184            },
19185        );
19186    });
19187    cx.executor().run_until_parked();
19188    assert_eq!(
19189        server_restarts.load(atomic::Ordering::Acquire),
19190        1,
19191        "Should not restart LSP server on a related LSP settings change that is the same"
19192    );
19193
19194    update_test_project_settings(cx, |project_settings| {
19195        project_settings.lsp.0.insert(
19196            language_server_name.into(),
19197            LspSettings {
19198                binary: None,
19199                settings: None,
19200                initialization_options: None,
19201                enable_lsp_tasks: false,
19202                fetch: None,
19203            },
19204        );
19205    });
19206    cx.executor().run_until_parked();
19207    assert_eq!(
19208        server_restarts.load(atomic::Ordering::Acquire),
19209        2,
19210        "Should restart LSP server on another related LSP settings change"
19211    );
19212}
19213
19214#[gpui::test]
19215async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
19216    init_test(cx, |_| {});
19217
19218    let mut cx = EditorLspTestContext::new_rust(
19219        lsp::ServerCapabilities {
19220            completion_provider: Some(lsp::CompletionOptions {
19221                trigger_characters: Some(vec![".".to_string()]),
19222                resolve_provider: Some(true),
19223                ..Default::default()
19224            }),
19225            ..Default::default()
19226        },
19227        cx,
19228    )
19229    .await;
19230
19231    cx.set_state("fn main() { let a = 2ˇ; }");
19232    cx.simulate_keystroke(".");
19233    let completion_item = lsp::CompletionItem {
19234        label: "some".into(),
19235        kind: Some(lsp::CompletionItemKind::SNIPPET),
19236        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
19237        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
19238            kind: lsp::MarkupKind::Markdown,
19239            value: "```rust\nSome(2)\n```".to_string(),
19240        })),
19241        deprecated: Some(false),
19242        sort_text: Some("fffffff2".to_string()),
19243        filter_text: Some("some".to_string()),
19244        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
19245        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19246            range: lsp::Range {
19247                start: lsp::Position {
19248                    line: 0,
19249                    character: 22,
19250                },
19251                end: lsp::Position {
19252                    line: 0,
19253                    character: 22,
19254                },
19255            },
19256            new_text: "Some(2)".to_string(),
19257        })),
19258        additional_text_edits: Some(vec![lsp::TextEdit {
19259            range: lsp::Range {
19260                start: lsp::Position {
19261                    line: 0,
19262                    character: 20,
19263                },
19264                end: lsp::Position {
19265                    line: 0,
19266                    character: 22,
19267                },
19268            },
19269            new_text: "".to_string(),
19270        }]),
19271        ..Default::default()
19272    };
19273
19274    let closure_completion_item = completion_item.clone();
19275    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19276        let task_completion_item = closure_completion_item.clone();
19277        async move {
19278            Ok(Some(lsp::CompletionResponse::Array(vec![
19279                task_completion_item,
19280            ])))
19281        }
19282    });
19283
19284    request.next().await;
19285
19286    cx.condition(|editor, _| editor.context_menu_visible())
19287        .await;
19288    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
19289        editor
19290            .confirm_completion(&ConfirmCompletion::default(), window, cx)
19291            .unwrap()
19292    });
19293    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
19294
19295    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19296        let task_completion_item = completion_item.clone();
19297        async move { Ok(task_completion_item) }
19298    })
19299    .next()
19300    .await
19301    .unwrap();
19302    apply_additional_edits.await.unwrap();
19303    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
19304}
19305
19306#[gpui::test]
19307async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
19308    init_test(cx, |_| {});
19309
19310    let mut cx = EditorLspTestContext::new_rust(
19311        lsp::ServerCapabilities {
19312            completion_provider: Some(lsp::CompletionOptions {
19313                trigger_characters: Some(vec![".".to_string()]),
19314                resolve_provider: Some(true),
19315                ..Default::default()
19316            }),
19317            ..Default::default()
19318        },
19319        cx,
19320    )
19321    .await;
19322
19323    cx.set_state("fn main() { let a = 2ˇ; }");
19324    cx.simulate_keystroke(".");
19325
19326    let item1 = lsp::CompletionItem {
19327        label: "method id()".to_string(),
19328        filter_text: Some("id".to_string()),
19329        detail: None,
19330        documentation: None,
19331        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19332            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19333            new_text: ".id".to_string(),
19334        })),
19335        ..lsp::CompletionItem::default()
19336    };
19337
19338    let item2 = lsp::CompletionItem {
19339        label: "other".to_string(),
19340        filter_text: Some("other".to_string()),
19341        detail: None,
19342        documentation: None,
19343        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19344            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19345            new_text: ".other".to_string(),
19346        })),
19347        ..lsp::CompletionItem::default()
19348    };
19349
19350    let item1 = item1.clone();
19351    cx.set_request_handler::<lsp::request::Completion, _, _>({
19352        let item1 = item1.clone();
19353        move |_, _, _| {
19354            let item1 = item1.clone();
19355            let item2 = item2.clone();
19356            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
19357        }
19358    })
19359    .next()
19360    .await;
19361
19362    cx.condition(|editor, _| editor.context_menu_visible())
19363        .await;
19364    cx.update_editor(|editor, _, _| {
19365        let context_menu = editor.context_menu.borrow_mut();
19366        let context_menu = context_menu
19367            .as_ref()
19368            .expect("Should have the context menu deployed");
19369        match context_menu {
19370            CodeContextMenu::Completions(completions_menu) => {
19371                let completions = completions_menu.completions.borrow_mut();
19372                assert_eq!(
19373                    completions
19374                        .iter()
19375                        .map(|completion| &completion.label.text)
19376                        .collect::<Vec<_>>(),
19377                    vec!["method id()", "other"]
19378                )
19379            }
19380            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19381        }
19382    });
19383
19384    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
19385        let item1 = item1.clone();
19386        move |_, item_to_resolve, _| {
19387            let item1 = item1.clone();
19388            async move {
19389                if item1 == item_to_resolve {
19390                    Ok(lsp::CompletionItem {
19391                        label: "method id()".to_string(),
19392                        filter_text: Some("id".to_string()),
19393                        detail: Some("Now resolved!".to_string()),
19394                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
19395                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19396                            range: lsp::Range::new(
19397                                lsp::Position::new(0, 22),
19398                                lsp::Position::new(0, 22),
19399                            ),
19400                            new_text: ".id".to_string(),
19401                        })),
19402                        ..lsp::CompletionItem::default()
19403                    })
19404                } else {
19405                    Ok(item_to_resolve)
19406                }
19407            }
19408        }
19409    })
19410    .next()
19411    .await
19412    .unwrap();
19413    cx.run_until_parked();
19414
19415    cx.update_editor(|editor, window, cx| {
19416        editor.context_menu_next(&Default::default(), window, cx);
19417    });
19418    cx.run_until_parked();
19419
19420    cx.update_editor(|editor, _, _| {
19421        let context_menu = editor.context_menu.borrow_mut();
19422        let context_menu = context_menu
19423            .as_ref()
19424            .expect("Should have the context menu deployed");
19425        match context_menu {
19426            CodeContextMenu::Completions(completions_menu) => {
19427                let completions = completions_menu.completions.borrow_mut();
19428                assert_eq!(
19429                    completions
19430                        .iter()
19431                        .map(|completion| &completion.label.text)
19432                        .collect::<Vec<_>>(),
19433                    vec!["method id() Now resolved!", "other"],
19434                    "Should update first completion label, but not second as the filter text did not match."
19435                );
19436            }
19437            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19438        }
19439    });
19440}
19441
19442#[gpui::test]
19443async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
19444    init_test(cx, |_| {});
19445    let mut cx = EditorLspTestContext::new_rust(
19446        lsp::ServerCapabilities {
19447            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
19448            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
19449            completion_provider: Some(lsp::CompletionOptions {
19450                resolve_provider: Some(true),
19451                ..Default::default()
19452            }),
19453            ..Default::default()
19454        },
19455        cx,
19456    )
19457    .await;
19458    cx.set_state(indoc! {"
19459        struct TestStruct {
19460            field: i32
19461        }
19462
19463        fn mainˇ() {
19464            let unused_var = 42;
19465            let test_struct = TestStruct { field: 42 };
19466        }
19467    "});
19468    let symbol_range = cx.lsp_range(indoc! {"
19469        struct TestStruct {
19470            field: i32
19471        }
19472
19473        «fn main»() {
19474            let unused_var = 42;
19475            let test_struct = TestStruct { field: 42 };
19476        }
19477    "});
19478    let mut hover_requests =
19479        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
19480            Ok(Some(lsp::Hover {
19481                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
19482                    kind: lsp::MarkupKind::Markdown,
19483                    value: "Function documentation".to_string(),
19484                }),
19485                range: Some(symbol_range),
19486            }))
19487        });
19488
19489    // Case 1: Test that code action menu hide hover popover
19490    cx.dispatch_action(Hover);
19491    hover_requests.next().await;
19492    cx.condition(|editor, _| editor.hover_state.visible()).await;
19493    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
19494        move |_, _, _| async move {
19495            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
19496                lsp::CodeAction {
19497                    title: "Remove unused variable".to_string(),
19498                    kind: Some(CodeActionKind::QUICKFIX),
19499                    edit: Some(lsp::WorkspaceEdit {
19500                        changes: Some(
19501                            [(
19502                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
19503                                vec![lsp::TextEdit {
19504                                    range: lsp::Range::new(
19505                                        lsp::Position::new(5, 4),
19506                                        lsp::Position::new(5, 27),
19507                                    ),
19508                                    new_text: "".to_string(),
19509                                }],
19510                            )]
19511                            .into_iter()
19512                            .collect(),
19513                        ),
19514                        ..Default::default()
19515                    }),
19516                    ..Default::default()
19517                },
19518            )]))
19519        },
19520    );
19521    cx.update_editor(|editor, window, cx| {
19522        editor.toggle_code_actions(
19523            &ToggleCodeActions {
19524                deployed_from: None,
19525                quick_launch: false,
19526            },
19527            window,
19528            cx,
19529        );
19530    });
19531    code_action_requests.next().await;
19532    cx.run_until_parked();
19533    cx.condition(|editor, _| editor.context_menu_visible())
19534        .await;
19535    cx.update_editor(|editor, _, _| {
19536        assert!(
19537            !editor.hover_state.visible(),
19538            "Hover popover should be hidden when code action menu is shown"
19539        );
19540        // Hide code actions
19541        editor.context_menu.take();
19542    });
19543
19544    // Case 2: Test that code completions hide hover popover
19545    cx.dispatch_action(Hover);
19546    hover_requests.next().await;
19547    cx.condition(|editor, _| editor.hover_state.visible()).await;
19548    let counter = Arc::new(AtomicUsize::new(0));
19549    let mut completion_requests =
19550        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19551            let counter = counter.clone();
19552            async move {
19553                counter.fetch_add(1, atomic::Ordering::Release);
19554                Ok(Some(lsp::CompletionResponse::Array(vec![
19555                    lsp::CompletionItem {
19556                        label: "main".into(),
19557                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19558                        detail: Some("() -> ()".to_string()),
19559                        ..Default::default()
19560                    },
19561                    lsp::CompletionItem {
19562                        label: "TestStruct".into(),
19563                        kind: Some(lsp::CompletionItemKind::STRUCT),
19564                        detail: Some("struct TestStruct".to_string()),
19565                        ..Default::default()
19566                    },
19567                ])))
19568            }
19569        });
19570    cx.update_editor(|editor, window, cx| {
19571        editor.show_completions(&ShowCompletions, window, cx);
19572    });
19573    completion_requests.next().await;
19574    cx.condition(|editor, _| editor.context_menu_visible())
19575        .await;
19576    cx.update_editor(|editor, _, _| {
19577        assert!(
19578            !editor.hover_state.visible(),
19579            "Hover popover should be hidden when completion menu is shown"
19580        );
19581    });
19582}
19583
19584#[gpui::test]
19585async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19586    init_test(cx, |_| {});
19587
19588    let mut cx = EditorLspTestContext::new_rust(
19589        lsp::ServerCapabilities {
19590            completion_provider: Some(lsp::CompletionOptions {
19591                trigger_characters: Some(vec![".".to_string()]),
19592                resolve_provider: Some(true),
19593                ..Default::default()
19594            }),
19595            ..Default::default()
19596        },
19597        cx,
19598    )
19599    .await;
19600
19601    cx.set_state("fn main() { let a = 2ˇ; }");
19602    cx.simulate_keystroke(".");
19603
19604    let unresolved_item_1 = lsp::CompletionItem {
19605        label: "id".to_string(),
19606        filter_text: Some("id".to_string()),
19607        detail: None,
19608        documentation: None,
19609        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19610            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19611            new_text: ".id".to_string(),
19612        })),
19613        ..lsp::CompletionItem::default()
19614    };
19615    let resolved_item_1 = lsp::CompletionItem {
19616        additional_text_edits: Some(vec![lsp::TextEdit {
19617            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19618            new_text: "!!".to_string(),
19619        }]),
19620        ..unresolved_item_1.clone()
19621    };
19622    let unresolved_item_2 = lsp::CompletionItem {
19623        label: "other".to_string(),
19624        filter_text: Some("other".to_string()),
19625        detail: None,
19626        documentation: None,
19627        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19628            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19629            new_text: ".other".to_string(),
19630        })),
19631        ..lsp::CompletionItem::default()
19632    };
19633    let resolved_item_2 = lsp::CompletionItem {
19634        additional_text_edits: Some(vec![lsp::TextEdit {
19635            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19636            new_text: "??".to_string(),
19637        }]),
19638        ..unresolved_item_2.clone()
19639    };
19640
19641    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19642    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19643    cx.lsp
19644        .server
19645        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19646            let unresolved_item_1 = unresolved_item_1.clone();
19647            let resolved_item_1 = resolved_item_1.clone();
19648            let unresolved_item_2 = unresolved_item_2.clone();
19649            let resolved_item_2 = resolved_item_2.clone();
19650            let resolve_requests_1 = resolve_requests_1.clone();
19651            let resolve_requests_2 = resolve_requests_2.clone();
19652            move |unresolved_request, _| {
19653                let unresolved_item_1 = unresolved_item_1.clone();
19654                let resolved_item_1 = resolved_item_1.clone();
19655                let unresolved_item_2 = unresolved_item_2.clone();
19656                let resolved_item_2 = resolved_item_2.clone();
19657                let resolve_requests_1 = resolve_requests_1.clone();
19658                let resolve_requests_2 = resolve_requests_2.clone();
19659                async move {
19660                    if unresolved_request == unresolved_item_1 {
19661                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19662                        Ok(resolved_item_1.clone())
19663                    } else if unresolved_request == unresolved_item_2 {
19664                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19665                        Ok(resolved_item_2.clone())
19666                    } else {
19667                        panic!("Unexpected completion item {unresolved_request:?}")
19668                    }
19669                }
19670            }
19671        })
19672        .detach();
19673
19674    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19675        let unresolved_item_1 = unresolved_item_1.clone();
19676        let unresolved_item_2 = unresolved_item_2.clone();
19677        async move {
19678            Ok(Some(lsp::CompletionResponse::Array(vec![
19679                unresolved_item_1,
19680                unresolved_item_2,
19681            ])))
19682        }
19683    })
19684    .next()
19685    .await;
19686
19687    cx.condition(|editor, _| editor.context_menu_visible())
19688        .await;
19689    cx.update_editor(|editor, _, _| {
19690        let context_menu = editor.context_menu.borrow_mut();
19691        let context_menu = context_menu
19692            .as_ref()
19693            .expect("Should have the context menu deployed");
19694        match context_menu {
19695            CodeContextMenu::Completions(completions_menu) => {
19696                let completions = completions_menu.completions.borrow_mut();
19697                assert_eq!(
19698                    completions
19699                        .iter()
19700                        .map(|completion| &completion.label.text)
19701                        .collect::<Vec<_>>(),
19702                    vec!["id", "other"]
19703                )
19704            }
19705            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19706        }
19707    });
19708    cx.run_until_parked();
19709
19710    cx.update_editor(|editor, window, cx| {
19711        editor.context_menu_next(&ContextMenuNext, window, cx);
19712    });
19713    cx.run_until_parked();
19714    cx.update_editor(|editor, window, cx| {
19715        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19716    });
19717    cx.run_until_parked();
19718    cx.update_editor(|editor, window, cx| {
19719        editor.context_menu_next(&ContextMenuNext, window, cx);
19720    });
19721    cx.run_until_parked();
19722    cx.update_editor(|editor, window, cx| {
19723        editor
19724            .compose_completion(&ComposeCompletion::default(), window, cx)
19725            .expect("No task returned")
19726    })
19727    .await
19728    .expect("Completion failed");
19729    cx.run_until_parked();
19730
19731    cx.update_editor(|editor, _, cx| {
19732        assert_eq!(
19733            resolve_requests_1.load(atomic::Ordering::Acquire),
19734            1,
19735            "Should always resolve once despite multiple selections"
19736        );
19737        assert_eq!(
19738            resolve_requests_2.load(atomic::Ordering::Acquire),
19739            1,
19740            "Should always resolve once after multiple selections and applying the completion"
19741        );
19742        assert_eq!(
19743            editor.text(cx),
19744            "fn main() { let a = ??.other; }",
19745            "Should use resolved data when applying the completion"
19746        );
19747    });
19748}
19749
19750#[gpui::test]
19751async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19752    init_test(cx, |_| {});
19753
19754    let item_0 = lsp::CompletionItem {
19755        label: "abs".into(),
19756        insert_text: Some("abs".into()),
19757        data: Some(json!({ "very": "special"})),
19758        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19759        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19760            lsp::InsertReplaceEdit {
19761                new_text: "abs".to_string(),
19762                insert: lsp::Range::default(),
19763                replace: lsp::Range::default(),
19764            },
19765        )),
19766        ..lsp::CompletionItem::default()
19767    };
19768    let items = iter::once(item_0.clone())
19769        .chain((11..51).map(|i| lsp::CompletionItem {
19770            label: format!("item_{}", i),
19771            insert_text: Some(format!("item_{}", i)),
19772            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19773            ..lsp::CompletionItem::default()
19774        }))
19775        .collect::<Vec<_>>();
19776
19777    let default_commit_characters = vec!["?".to_string()];
19778    let default_data = json!({ "default": "data"});
19779    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19780    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19781    let default_edit_range = lsp::Range {
19782        start: lsp::Position {
19783            line: 0,
19784            character: 5,
19785        },
19786        end: lsp::Position {
19787            line: 0,
19788            character: 5,
19789        },
19790    };
19791
19792    let mut cx = EditorLspTestContext::new_rust(
19793        lsp::ServerCapabilities {
19794            completion_provider: Some(lsp::CompletionOptions {
19795                trigger_characters: Some(vec![".".to_string()]),
19796                resolve_provider: Some(true),
19797                ..Default::default()
19798            }),
19799            ..Default::default()
19800        },
19801        cx,
19802    )
19803    .await;
19804
19805    cx.set_state("fn main() { let a = 2ˇ; }");
19806    cx.simulate_keystroke(".");
19807
19808    let completion_data = default_data.clone();
19809    let completion_characters = default_commit_characters.clone();
19810    let completion_items = items.clone();
19811    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19812        let default_data = completion_data.clone();
19813        let default_commit_characters = completion_characters.clone();
19814        let items = completion_items.clone();
19815        async move {
19816            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19817                items,
19818                item_defaults: Some(lsp::CompletionListItemDefaults {
19819                    data: Some(default_data.clone()),
19820                    commit_characters: Some(default_commit_characters.clone()),
19821                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19822                        default_edit_range,
19823                    )),
19824                    insert_text_format: Some(default_insert_text_format),
19825                    insert_text_mode: Some(default_insert_text_mode),
19826                }),
19827                ..lsp::CompletionList::default()
19828            })))
19829        }
19830    })
19831    .next()
19832    .await;
19833
19834    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19835    cx.lsp
19836        .server
19837        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19838            let closure_resolved_items = resolved_items.clone();
19839            move |item_to_resolve, _| {
19840                let closure_resolved_items = closure_resolved_items.clone();
19841                async move {
19842                    closure_resolved_items.lock().push(item_to_resolve.clone());
19843                    Ok(item_to_resolve)
19844                }
19845            }
19846        })
19847        .detach();
19848
19849    cx.condition(|editor, _| editor.context_menu_visible())
19850        .await;
19851    cx.run_until_parked();
19852    cx.update_editor(|editor, _, _| {
19853        let menu = editor.context_menu.borrow_mut();
19854        match menu.as_ref().expect("should have the completions menu") {
19855            CodeContextMenu::Completions(completions_menu) => {
19856                assert_eq!(
19857                    completions_menu
19858                        .entries
19859                        .borrow()
19860                        .iter()
19861                        .map(|mat| mat.string.clone())
19862                        .collect::<Vec<String>>(),
19863                    items
19864                        .iter()
19865                        .map(|completion| completion.label.clone())
19866                        .collect::<Vec<String>>()
19867                );
19868            }
19869            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19870        }
19871    });
19872    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19873    // with 4 from the end.
19874    assert_eq!(
19875        *resolved_items.lock(),
19876        [&items[0..16], &items[items.len() - 4..items.len()]]
19877            .concat()
19878            .iter()
19879            .cloned()
19880            .map(|mut item| {
19881                if item.data.is_none() {
19882                    item.data = Some(default_data.clone());
19883                }
19884                item
19885            })
19886            .collect::<Vec<lsp::CompletionItem>>(),
19887        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19888    );
19889    resolved_items.lock().clear();
19890
19891    cx.update_editor(|editor, window, cx| {
19892        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19893    });
19894    cx.run_until_parked();
19895    // Completions that have already been resolved are skipped.
19896    assert_eq!(
19897        *resolved_items.lock(),
19898        items[items.len() - 17..items.len() - 4]
19899            .iter()
19900            .cloned()
19901            .map(|mut item| {
19902                if item.data.is_none() {
19903                    item.data = Some(default_data.clone());
19904                }
19905                item
19906            })
19907            .collect::<Vec<lsp::CompletionItem>>()
19908    );
19909    resolved_items.lock().clear();
19910}
19911
19912#[gpui::test]
19913async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorLspTestContext::new(
19917        Language::new(
19918            LanguageConfig {
19919                matcher: LanguageMatcher {
19920                    path_suffixes: vec!["jsx".into()],
19921                    ..Default::default()
19922                },
19923                overrides: [(
19924                    "element".into(),
19925                    LanguageConfigOverride {
19926                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19927                        ..Default::default()
19928                    },
19929                )]
19930                .into_iter()
19931                .collect(),
19932                ..Default::default()
19933            },
19934            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19935        )
19936        .with_override_query("(jsx_self_closing_element) @element")
19937        .unwrap(),
19938        lsp::ServerCapabilities {
19939            completion_provider: Some(lsp::CompletionOptions {
19940                trigger_characters: Some(vec![":".to_string()]),
19941                ..Default::default()
19942            }),
19943            ..Default::default()
19944        },
19945        cx,
19946    )
19947    .await;
19948
19949    cx.lsp
19950        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19951            Ok(Some(lsp::CompletionResponse::Array(vec![
19952                lsp::CompletionItem {
19953                    label: "bg-blue".into(),
19954                    ..Default::default()
19955                },
19956                lsp::CompletionItem {
19957                    label: "bg-red".into(),
19958                    ..Default::default()
19959                },
19960                lsp::CompletionItem {
19961                    label: "bg-yellow".into(),
19962                    ..Default::default()
19963                },
19964            ])))
19965        });
19966
19967    cx.set_state(r#"<p class="bgˇ" />"#);
19968
19969    // Trigger completion when typing a dash, because the dash is an extra
19970    // word character in the 'element' scope, which contains the cursor.
19971    cx.simulate_keystroke("-");
19972    cx.executor().run_until_parked();
19973    cx.update_editor(|editor, _, _| {
19974        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19975        {
19976            assert_eq!(
19977                completion_menu_entries(menu),
19978                &["bg-blue", "bg-red", "bg-yellow"]
19979            );
19980        } else {
19981            panic!("expected completion menu to be open");
19982        }
19983    });
19984
19985    cx.simulate_keystroke("l");
19986    cx.executor().run_until_parked();
19987    cx.update_editor(|editor, _, _| {
19988        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19989        {
19990            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19991        } else {
19992            panic!("expected completion menu to be open");
19993        }
19994    });
19995
19996    // When filtering completions, consider the character after the '-' to
19997    // be the start of a subword.
19998    cx.set_state(r#"<p class="yelˇ" />"#);
19999    cx.simulate_keystroke("l");
20000    cx.executor().run_until_parked();
20001    cx.update_editor(|editor, _, _| {
20002        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20003        {
20004            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
20005        } else {
20006            panic!("expected completion menu to be open");
20007        }
20008    });
20009}
20010
20011fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
20012    let entries = menu.entries.borrow();
20013    entries.iter().map(|mat| mat.string.clone()).collect()
20014}
20015
20016#[gpui::test]
20017async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
20018    init_test(cx, |settings| {
20019        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
20020    });
20021
20022    let fs = FakeFs::new(cx.executor());
20023    fs.insert_file(path!("/file.ts"), Default::default()).await;
20024
20025    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
20026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20027
20028    language_registry.add(Arc::new(Language::new(
20029        LanguageConfig {
20030            name: "TypeScript".into(),
20031            matcher: LanguageMatcher {
20032                path_suffixes: vec!["ts".to_string()],
20033                ..Default::default()
20034            },
20035            ..Default::default()
20036        },
20037        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20038    )));
20039    update_test_language_settings(cx, |settings| {
20040        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
20041    });
20042
20043    let test_plugin = "test_plugin";
20044    let _ = language_registry.register_fake_lsp(
20045        "TypeScript",
20046        FakeLspAdapter {
20047            prettier_plugins: vec![test_plugin],
20048            ..Default::default()
20049        },
20050    );
20051
20052    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
20053    let buffer = project
20054        .update(cx, |project, cx| {
20055            project.open_local_buffer(path!("/file.ts"), cx)
20056        })
20057        .await
20058        .unwrap();
20059
20060    let buffer_text = "one\ntwo\nthree\n";
20061    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20062    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
20063    editor.update_in(cx, |editor, window, cx| {
20064        editor.set_text(buffer_text, window, cx)
20065    });
20066
20067    editor
20068        .update_in(cx, |editor, window, cx| {
20069            editor.perform_format(
20070                project.clone(),
20071                FormatTrigger::Manual,
20072                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20073                window,
20074                cx,
20075            )
20076        })
20077        .unwrap()
20078        .await;
20079    assert_eq!(
20080        editor.update(cx, |editor, cx| editor.text(cx)),
20081        buffer_text.to_string() + prettier_format_suffix,
20082        "Test prettier formatting was not applied to the original buffer text",
20083    );
20084
20085    update_test_language_settings(cx, |settings| {
20086        settings.defaults.formatter = Some(FormatterList::default())
20087    });
20088    let format = editor.update_in(cx, |editor, window, cx| {
20089        editor.perform_format(
20090            project.clone(),
20091            FormatTrigger::Manual,
20092            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20093            window,
20094            cx,
20095        )
20096    });
20097    format.await.unwrap();
20098    assert_eq!(
20099        editor.update(cx, |editor, cx| editor.text(cx)),
20100        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
20101        "Autoformatting (via test prettier) was not applied to the original buffer text",
20102    );
20103}
20104
20105#[gpui::test]
20106async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
20107    init_test(cx, |settings| {
20108        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
20109    });
20110
20111    let fs = FakeFs::new(cx.executor());
20112    fs.insert_file(path!("/file.settings"), Default::default())
20113        .await;
20114
20115    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
20116    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20117
20118    let ts_lang = Arc::new(Language::new(
20119        LanguageConfig {
20120            name: "TypeScript".into(),
20121            matcher: LanguageMatcher {
20122                path_suffixes: vec!["ts".to_string()],
20123                ..LanguageMatcher::default()
20124            },
20125            prettier_parser_name: Some("typescript".to_string()),
20126            ..LanguageConfig::default()
20127        },
20128        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20129    ));
20130
20131    language_registry.add(ts_lang.clone());
20132
20133    update_test_language_settings(cx, |settings| {
20134        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
20135    });
20136
20137    let test_plugin = "test_plugin";
20138    let _ = language_registry.register_fake_lsp(
20139        "TypeScript",
20140        FakeLspAdapter {
20141            prettier_plugins: vec![test_plugin],
20142            ..Default::default()
20143        },
20144    );
20145
20146    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
20147    let buffer = project
20148        .update(cx, |project, cx| {
20149            project.open_local_buffer(path!("/file.settings"), cx)
20150        })
20151        .await
20152        .unwrap();
20153
20154    project.update(cx, |project, cx| {
20155        project.set_language_for_buffer(&buffer, ts_lang, cx)
20156    });
20157
20158    let buffer_text = "one\ntwo\nthree\n";
20159    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20160    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
20161    editor.update_in(cx, |editor, window, cx| {
20162        editor.set_text(buffer_text, window, cx)
20163    });
20164
20165    editor
20166        .update_in(cx, |editor, window, cx| {
20167            editor.perform_format(
20168                project.clone(),
20169                FormatTrigger::Manual,
20170                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20171                window,
20172                cx,
20173            )
20174        })
20175        .unwrap()
20176        .await;
20177    assert_eq!(
20178        editor.update(cx, |editor, cx| editor.text(cx)),
20179        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
20180        "Test prettier formatting was not applied to the original buffer text",
20181    );
20182
20183    update_test_language_settings(cx, |settings| {
20184        settings.defaults.formatter = Some(FormatterList::default())
20185    });
20186    let format = editor.update_in(cx, |editor, window, cx| {
20187        editor.perform_format(
20188            project.clone(),
20189            FormatTrigger::Manual,
20190            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20191            window,
20192            cx,
20193        )
20194    });
20195    format.await.unwrap();
20196
20197    assert_eq!(
20198        editor.update(cx, |editor, cx| editor.text(cx)),
20199        buffer_text.to_string()
20200            + prettier_format_suffix
20201            + "\ntypescript\n"
20202            + prettier_format_suffix
20203            + "\ntypescript",
20204        "Autoformatting (via test prettier) was not applied to the original buffer text",
20205    );
20206}
20207
20208#[gpui::test]
20209async fn test_addition_reverts(cx: &mut TestAppContext) {
20210    init_test(cx, |_| {});
20211    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20212    let base_text = indoc! {r#"
20213        struct Row;
20214        struct Row1;
20215        struct Row2;
20216
20217        struct Row4;
20218        struct Row5;
20219        struct Row6;
20220
20221        struct Row8;
20222        struct Row9;
20223        struct Row10;"#};
20224
20225    // When addition hunks are not adjacent to carets, no hunk revert is performed
20226    assert_hunk_revert(
20227        indoc! {r#"struct Row;
20228                   struct Row1;
20229                   struct Row1.1;
20230                   struct Row1.2;
20231                   struct Row2;ˇ
20232
20233                   struct Row4;
20234                   struct Row5;
20235                   struct Row6;
20236
20237                   struct Row8;
20238                   ˇstruct Row9;
20239                   struct Row9.1;
20240                   struct Row9.2;
20241                   struct Row9.3;
20242                   struct Row10;"#},
20243        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
20244        indoc! {r#"struct Row;
20245                   struct Row1;
20246                   struct Row1.1;
20247                   struct Row1.2;
20248                   struct Row2;ˇ
20249
20250                   struct Row4;
20251                   struct Row5;
20252                   struct Row6;
20253
20254                   struct Row8;
20255                   ˇstruct Row9;
20256                   struct Row9.1;
20257                   struct Row9.2;
20258                   struct Row9.3;
20259                   struct Row10;"#},
20260        base_text,
20261        &mut cx,
20262    );
20263    // Same for selections
20264    assert_hunk_revert(
20265        indoc! {r#"struct Row;
20266                   struct Row1;
20267                   struct Row2;
20268                   struct Row2.1;
20269                   struct Row2.2;
20270                   «ˇ
20271                   struct Row4;
20272                   struct» Row5;
20273                   «struct Row6;
20274                   ˇ»
20275                   struct Row9.1;
20276                   struct Row9.2;
20277                   struct Row9.3;
20278                   struct Row8;
20279                   struct Row9;
20280                   struct Row10;"#},
20281        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
20282        indoc! {r#"struct Row;
20283                   struct Row1;
20284                   struct Row2;
20285                   struct Row2.1;
20286                   struct Row2.2;
20287                   «ˇ
20288                   struct Row4;
20289                   struct» Row5;
20290                   «struct Row6;
20291                   ˇ»
20292                   struct Row9.1;
20293                   struct Row9.2;
20294                   struct Row9.3;
20295                   struct Row8;
20296                   struct Row9;
20297                   struct Row10;"#},
20298        base_text,
20299        &mut cx,
20300    );
20301
20302    // When carets and selections intersect the addition hunks, those are reverted.
20303    // Adjacent carets got merged.
20304    assert_hunk_revert(
20305        indoc! {r#"struct Row;
20306                   ˇ// something on the top
20307                   struct Row1;
20308                   struct Row2;
20309                   struct Roˇw3.1;
20310                   struct Row2.2;
20311                   struct Row2.3;ˇ
20312
20313                   struct Row4;
20314                   struct ˇRow5.1;
20315                   struct Row5.2;
20316                   struct «Rowˇ»5.3;
20317                   struct Row5;
20318                   struct Row6;
20319                   ˇ
20320                   struct Row9.1;
20321                   struct «Rowˇ»9.2;
20322                   struct «ˇRow»9.3;
20323                   struct Row8;
20324                   struct Row9;
20325                   «ˇ// something on bottom»
20326                   struct Row10;"#},
20327        vec![
20328            DiffHunkStatusKind::Added,
20329            DiffHunkStatusKind::Added,
20330            DiffHunkStatusKind::Added,
20331            DiffHunkStatusKind::Added,
20332            DiffHunkStatusKind::Added,
20333        ],
20334        indoc! {r#"struct Row;
20335                   ˇstruct Row1;
20336                   struct Row2;
20337                   ˇ
20338                   struct Row4;
20339                   ˇstruct Row5;
20340                   struct Row6;
20341                   ˇ
20342                   ˇstruct Row8;
20343                   struct Row9;
20344                   ˇstruct Row10;"#},
20345        base_text,
20346        &mut cx,
20347    );
20348}
20349
20350#[gpui::test]
20351async fn test_modification_reverts(cx: &mut TestAppContext) {
20352    init_test(cx, |_| {});
20353    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20354    let base_text = indoc! {r#"
20355        struct Row;
20356        struct Row1;
20357        struct Row2;
20358
20359        struct Row4;
20360        struct Row5;
20361        struct Row6;
20362
20363        struct Row8;
20364        struct Row9;
20365        struct Row10;"#};
20366
20367    // Modification hunks behave the same as the addition ones.
20368    assert_hunk_revert(
20369        indoc! {r#"struct Row;
20370                   struct Row1;
20371                   struct Row33;
20372                   ˇ
20373                   struct Row4;
20374                   struct Row5;
20375                   struct Row6;
20376                   ˇ
20377                   struct Row99;
20378                   struct Row9;
20379                   struct Row10;"#},
20380        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20381        indoc! {r#"struct Row;
20382                   struct Row1;
20383                   struct Row33;
20384                   ˇ
20385                   struct Row4;
20386                   struct Row5;
20387                   struct Row6;
20388                   ˇ
20389                   struct Row99;
20390                   struct Row9;
20391                   struct Row10;"#},
20392        base_text,
20393        &mut cx,
20394    );
20395    assert_hunk_revert(
20396        indoc! {r#"struct Row;
20397                   struct Row1;
20398                   struct Row33;
20399                   «ˇ
20400                   struct Row4;
20401                   struct» Row5;
20402                   «struct Row6;
20403                   ˇ»
20404                   struct Row99;
20405                   struct Row9;
20406                   struct Row10;"#},
20407        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20408        indoc! {r#"struct Row;
20409                   struct Row1;
20410                   struct Row33;
20411                   «ˇ
20412                   struct Row4;
20413                   struct» Row5;
20414                   «struct Row6;
20415                   ˇ»
20416                   struct Row99;
20417                   struct Row9;
20418                   struct Row10;"#},
20419        base_text,
20420        &mut cx,
20421    );
20422
20423    assert_hunk_revert(
20424        indoc! {r#"ˇstruct Row1.1;
20425                   struct Row1;
20426                   «ˇstr»uct Row22;
20427
20428                   struct ˇRow44;
20429                   struct Row5;
20430                   struct «Rˇ»ow66;ˇ
20431
20432                   «struˇ»ct Row88;
20433                   struct Row9;
20434                   struct Row1011;ˇ"#},
20435        vec![
20436            DiffHunkStatusKind::Modified,
20437            DiffHunkStatusKind::Modified,
20438            DiffHunkStatusKind::Modified,
20439            DiffHunkStatusKind::Modified,
20440            DiffHunkStatusKind::Modified,
20441            DiffHunkStatusKind::Modified,
20442        ],
20443        indoc! {r#"struct Row;
20444                   ˇstruct Row1;
20445                   struct Row2;
20446                   ˇ
20447                   struct Row4;
20448                   ˇstruct Row5;
20449                   struct Row6;
20450                   ˇ
20451                   struct Row8;
20452                   ˇstruct Row9;
20453                   struct Row10;ˇ"#},
20454        base_text,
20455        &mut cx,
20456    );
20457}
20458
20459#[gpui::test]
20460async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
20461    init_test(cx, |_| {});
20462    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20463    let base_text = indoc! {r#"
20464        one
20465
20466        two
20467        three
20468        "#};
20469
20470    cx.set_head_text(base_text);
20471    cx.set_state("\nˇ\n");
20472    cx.executor().run_until_parked();
20473    cx.update_editor(|editor, _window, cx| {
20474        editor.expand_selected_diff_hunks(cx);
20475    });
20476    cx.executor().run_until_parked();
20477    cx.update_editor(|editor, window, cx| {
20478        editor.backspace(&Default::default(), window, cx);
20479    });
20480    cx.run_until_parked();
20481    cx.assert_state_with_diff(
20482        indoc! {r#"
20483
20484        - two
20485        - threeˇ
20486        +
20487        "#}
20488        .to_string(),
20489    );
20490}
20491
20492#[gpui::test]
20493async fn test_deletion_reverts(cx: &mut TestAppContext) {
20494    init_test(cx, |_| {});
20495    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20496    let base_text = indoc! {r#"struct Row;
20497struct Row1;
20498struct Row2;
20499
20500struct Row4;
20501struct Row5;
20502struct Row6;
20503
20504struct Row8;
20505struct Row9;
20506struct Row10;"#};
20507
20508    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
20509    assert_hunk_revert(
20510        indoc! {r#"struct Row;
20511                   struct Row2;
20512
20513                   ˇstruct Row4;
20514                   struct Row5;
20515                   struct Row6;
20516                   ˇ
20517                   struct Row8;
20518                   struct Row10;"#},
20519        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20520        indoc! {r#"struct Row;
20521                   struct Row2;
20522
20523                   ˇstruct Row4;
20524                   struct Row5;
20525                   struct Row6;
20526                   ˇ
20527                   struct Row8;
20528                   struct Row10;"#},
20529        base_text,
20530        &mut cx,
20531    );
20532    assert_hunk_revert(
20533        indoc! {r#"struct Row;
20534                   struct Row2;
20535
20536                   «ˇstruct Row4;
20537                   struct» Row5;
20538                   «struct Row6;
20539                   ˇ»
20540                   struct Row8;
20541                   struct Row10;"#},
20542        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20543        indoc! {r#"struct Row;
20544                   struct Row2;
20545
20546                   «ˇstruct Row4;
20547                   struct» Row5;
20548                   «struct Row6;
20549                   ˇ»
20550                   struct Row8;
20551                   struct Row10;"#},
20552        base_text,
20553        &mut cx,
20554    );
20555
20556    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20557    assert_hunk_revert(
20558        indoc! {r#"struct Row;
20559                   ˇstruct Row2;
20560
20561                   struct Row4;
20562                   struct Row5;
20563                   struct Row6;
20564
20565                   struct Row8;ˇ
20566                   struct Row10;"#},
20567        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20568        indoc! {r#"struct Row;
20569                   struct Row1;
20570                   ˇstruct Row2;
20571
20572                   struct Row4;
20573                   struct Row5;
20574                   struct Row6;
20575
20576                   struct Row8;ˇ
20577                   struct Row9;
20578                   struct Row10;"#},
20579        base_text,
20580        &mut cx,
20581    );
20582    assert_hunk_revert(
20583        indoc! {r#"struct Row;
20584                   struct Row2«ˇ;
20585                   struct Row4;
20586                   struct» Row5;
20587                   «struct Row6;
20588
20589                   struct Row8;ˇ»
20590                   struct Row10;"#},
20591        vec![
20592            DiffHunkStatusKind::Deleted,
20593            DiffHunkStatusKind::Deleted,
20594            DiffHunkStatusKind::Deleted,
20595        ],
20596        indoc! {r#"struct Row;
20597                   struct Row1;
20598                   struct Row2«ˇ;
20599
20600                   struct Row4;
20601                   struct» Row5;
20602                   «struct Row6;
20603
20604                   struct Row8;ˇ»
20605                   struct Row9;
20606                   struct Row10;"#},
20607        base_text,
20608        &mut cx,
20609    );
20610}
20611
20612#[gpui::test]
20613async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20614    init_test(cx, |_| {});
20615
20616    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20617    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20618    let base_text_3 =
20619        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20620
20621    let text_1 = edit_first_char_of_every_line(base_text_1);
20622    let text_2 = edit_first_char_of_every_line(base_text_2);
20623    let text_3 = edit_first_char_of_every_line(base_text_3);
20624
20625    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20626    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20627    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20628
20629    let multibuffer = cx.new(|cx| {
20630        let mut multibuffer = MultiBuffer::new(ReadWrite);
20631        multibuffer.push_excerpts(
20632            buffer_1.clone(),
20633            [
20634                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20635                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20636                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20637            ],
20638            cx,
20639        );
20640        multibuffer.push_excerpts(
20641            buffer_2.clone(),
20642            [
20643                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20644                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20645                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20646            ],
20647            cx,
20648        );
20649        multibuffer.push_excerpts(
20650            buffer_3.clone(),
20651            [
20652                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20653                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20654                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20655            ],
20656            cx,
20657        );
20658        multibuffer
20659    });
20660
20661    let fs = FakeFs::new(cx.executor());
20662    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20663    let (editor, cx) = cx
20664        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20665    editor.update_in(cx, |editor, _window, cx| {
20666        for (buffer, diff_base) in [
20667            (buffer_1.clone(), base_text_1),
20668            (buffer_2.clone(), base_text_2),
20669            (buffer_3.clone(), base_text_3),
20670        ] {
20671            let diff = cx.new(|cx| {
20672                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20673            });
20674            editor
20675                .buffer
20676                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20677        }
20678    });
20679    cx.executor().run_until_parked();
20680
20681    editor.update_in(cx, |editor, window, cx| {
20682        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
20683        editor.select_all(&SelectAll, window, cx);
20684        editor.git_restore(&Default::default(), window, cx);
20685    });
20686    cx.executor().run_until_parked();
20687
20688    // When all ranges are selected, all buffer hunks are reverted.
20689    editor.update(cx, |editor, cx| {
20690        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
20691    });
20692    buffer_1.update(cx, |buffer, _| {
20693        assert_eq!(buffer.text(), base_text_1);
20694    });
20695    buffer_2.update(cx, |buffer, _| {
20696        assert_eq!(buffer.text(), base_text_2);
20697    });
20698    buffer_3.update(cx, |buffer, _| {
20699        assert_eq!(buffer.text(), base_text_3);
20700    });
20701
20702    editor.update_in(cx, |editor, window, cx| {
20703        editor.undo(&Default::default(), window, cx);
20704    });
20705
20706    editor.update_in(cx, |editor, window, cx| {
20707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20708            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20709        });
20710        editor.git_restore(&Default::default(), window, cx);
20711    });
20712
20713    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20714    // but not affect buffer_2 and its related excerpts.
20715    editor.update(cx, |editor, cx| {
20716        assert_eq!(
20717            editor.text(cx),
20718            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
20719        );
20720    });
20721    buffer_1.update(cx, |buffer, _| {
20722        assert_eq!(buffer.text(), base_text_1);
20723    });
20724    buffer_2.update(cx, |buffer, _| {
20725        assert_eq!(
20726            buffer.text(),
20727            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20728        );
20729    });
20730    buffer_3.update(cx, |buffer, _| {
20731        assert_eq!(
20732            buffer.text(),
20733            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20734        );
20735    });
20736
20737    fn edit_first_char_of_every_line(text: &str) -> String {
20738        text.split('\n')
20739            .map(|line| format!("X{}", &line[1..]))
20740            .collect::<Vec<_>>()
20741            .join("\n")
20742    }
20743}
20744
20745#[gpui::test]
20746async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20747    init_test(cx, |_| {});
20748
20749    let cols = 4;
20750    let rows = 10;
20751    let sample_text_1 = sample_text(rows, cols, 'a');
20752    assert_eq!(
20753        sample_text_1,
20754        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20755    );
20756    let sample_text_2 = sample_text(rows, cols, 'l');
20757    assert_eq!(
20758        sample_text_2,
20759        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20760    );
20761    let sample_text_3 = sample_text(rows, cols, 'v');
20762    assert_eq!(
20763        sample_text_3,
20764        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20765    );
20766
20767    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20768    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20769    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20770
20771    let multi_buffer = cx.new(|cx| {
20772        let mut multibuffer = MultiBuffer::new(ReadWrite);
20773        multibuffer.push_excerpts(
20774            buffer_1.clone(),
20775            [
20776                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20777                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20778                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20779            ],
20780            cx,
20781        );
20782        multibuffer.push_excerpts(
20783            buffer_2.clone(),
20784            [
20785                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20786                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20787                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20788            ],
20789            cx,
20790        );
20791        multibuffer.push_excerpts(
20792            buffer_3.clone(),
20793            [
20794                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20795                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20796                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20797            ],
20798            cx,
20799        );
20800        multibuffer
20801    });
20802
20803    let fs = FakeFs::new(cx.executor());
20804    fs.insert_tree(
20805        "/a",
20806        json!({
20807            "main.rs": sample_text_1,
20808            "other.rs": sample_text_2,
20809            "lib.rs": sample_text_3,
20810        }),
20811    )
20812    .await;
20813    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20814    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
20815    let workspace = window
20816        .read_with(cx, |mw, _| mw.workspace().clone())
20817        .unwrap();
20818    let cx = &mut VisualTestContext::from_window(*window, cx);
20819    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20820        Editor::new(
20821            EditorMode::full(),
20822            multi_buffer,
20823            Some(project.clone()),
20824            window,
20825            cx,
20826        )
20827    });
20828    let multibuffer_item_id = workspace.update_in(cx, |workspace, window, cx| {
20829        assert!(
20830            workspace.active_item(cx).is_none(),
20831            "active item should be None before the first item is added"
20832        );
20833        workspace.add_item_to_active_pane(
20834            Box::new(multi_buffer_editor.clone()),
20835            None,
20836            true,
20837            window,
20838            cx,
20839        );
20840        let active_item = workspace
20841            .active_item(cx)
20842            .expect("should have an active item after adding the multi buffer");
20843        assert_eq!(
20844            active_item.buffer_kind(cx),
20845            ItemBufferKind::Multibuffer,
20846            "A multi buffer was expected to active after adding"
20847        );
20848        active_item.item_id()
20849    });
20850
20851    cx.executor().run_until_parked();
20852
20853    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20854        editor.change_selections(
20855            SelectionEffects::scroll(Autoscroll::Next),
20856            window,
20857            cx,
20858            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20859        );
20860        editor.open_excerpts(&OpenExcerpts, window, cx);
20861    });
20862    cx.executor().run_until_parked();
20863    let first_item_id = workspace.update_in(cx, |workspace, window, cx| {
20864        let active_item = workspace
20865            .active_item(cx)
20866            .expect("should have an active item after navigating into the 1st buffer");
20867        let first_item_id = active_item.item_id();
20868        assert_ne!(
20869            first_item_id, multibuffer_item_id,
20870            "Should navigate into the 1st buffer and activate it"
20871        );
20872        assert_eq!(
20873            active_item.buffer_kind(cx),
20874            ItemBufferKind::Singleton,
20875            "New active item should be a singleton buffer"
20876        );
20877        assert_eq!(
20878            active_item
20879                .act_as::<Editor>(cx)
20880                .expect("should have navigated into an editor for the 1st buffer")
20881                .read(cx)
20882                .text(cx),
20883            sample_text_1
20884        );
20885
20886        workspace
20887            .go_back(workspace.active_pane().downgrade(), window, cx)
20888            .detach_and_log_err(cx);
20889
20890        first_item_id
20891    });
20892
20893    cx.executor().run_until_parked();
20894    workspace.update_in(cx, |workspace, _, cx| {
20895        let active_item = workspace
20896            .active_item(cx)
20897            .expect("should have an active item after navigating back");
20898        assert_eq!(
20899            active_item.item_id(),
20900            multibuffer_item_id,
20901            "Should navigate back to the multi buffer"
20902        );
20903        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20904    });
20905
20906    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20907        editor.change_selections(
20908            SelectionEffects::scroll(Autoscroll::Next),
20909            window,
20910            cx,
20911            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20912        );
20913        editor.open_excerpts(&OpenExcerpts, window, cx);
20914    });
20915    cx.executor().run_until_parked();
20916    let second_item_id = workspace.update_in(cx, |workspace, window, cx| {
20917        let active_item = workspace
20918            .active_item(cx)
20919            .expect("should have an active item after navigating into the 2nd buffer");
20920        let second_item_id = active_item.item_id();
20921        assert_ne!(
20922            second_item_id, multibuffer_item_id,
20923            "Should navigate away from the multibuffer"
20924        );
20925        assert_ne!(
20926            second_item_id, first_item_id,
20927            "Should navigate into the 2nd buffer and activate it"
20928        );
20929        assert_eq!(
20930            active_item.buffer_kind(cx),
20931            ItemBufferKind::Singleton,
20932            "New active item should be a singleton buffer"
20933        );
20934        assert_eq!(
20935            active_item
20936                .act_as::<Editor>(cx)
20937                .expect("should have navigated into an editor")
20938                .read(cx)
20939                .text(cx),
20940            sample_text_2
20941        );
20942
20943        workspace
20944            .go_back(workspace.active_pane().downgrade(), window, cx)
20945            .detach_and_log_err(cx);
20946
20947        second_item_id
20948    });
20949
20950    cx.executor().run_until_parked();
20951    workspace.update_in(cx, |workspace, _, cx| {
20952        let active_item = workspace
20953            .active_item(cx)
20954            .expect("should have an active item after navigating back from the 2nd buffer");
20955        assert_eq!(
20956            active_item.item_id(),
20957            multibuffer_item_id,
20958            "Should navigate back from the 2nd buffer to the multi buffer"
20959        );
20960        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20961    });
20962
20963    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20964        editor.change_selections(
20965            SelectionEffects::scroll(Autoscroll::Next),
20966            window,
20967            cx,
20968            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20969        );
20970        editor.open_excerpts(&OpenExcerpts, window, cx);
20971    });
20972    cx.executor().run_until_parked();
20973    workspace.update_in(cx, |workspace, window, cx| {
20974        let active_item = workspace
20975            .active_item(cx)
20976            .expect("should have an active item after navigating into the 3rd buffer");
20977        let third_item_id = active_item.item_id();
20978        assert_ne!(
20979            third_item_id, multibuffer_item_id,
20980            "Should navigate into the 3rd buffer and activate it"
20981        );
20982        assert_ne!(third_item_id, first_item_id);
20983        assert_ne!(third_item_id, second_item_id);
20984        assert_eq!(
20985            active_item.buffer_kind(cx),
20986            ItemBufferKind::Singleton,
20987            "New active item should be a singleton buffer"
20988        );
20989        assert_eq!(
20990            active_item
20991                .act_as::<Editor>(cx)
20992                .expect("should have navigated into an editor")
20993                .read(cx)
20994                .text(cx),
20995            sample_text_3
20996        );
20997
20998        workspace
20999            .go_back(workspace.active_pane().downgrade(), window, cx)
21000            .detach_and_log_err(cx);
21001    });
21002
21003    cx.executor().run_until_parked();
21004    workspace.update_in(cx, |workspace, _, cx| {
21005        let active_item = workspace
21006            .active_item(cx)
21007            .expect("should have an active item after navigating back from the 3rd buffer");
21008        assert_eq!(
21009            active_item.item_id(),
21010            multibuffer_item_id,
21011            "Should navigate back from the 3rd buffer to the multi buffer"
21012        );
21013        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21014    });
21015}
21016
21017#[gpui::test]
21018async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21019    init_test(cx, |_| {});
21020
21021    let mut cx = EditorTestContext::new(cx).await;
21022
21023    let diff_base = r#"
21024        use some::mod;
21025
21026        const A: u32 = 42;
21027
21028        fn main() {
21029            println!("hello");
21030
21031            println!("world");
21032        }
21033        "#
21034    .unindent();
21035
21036    cx.set_state(
21037        &r#"
21038        use some::modified;
21039
21040        ˇ
21041        fn main() {
21042            println!("hello there");
21043
21044            println!("around the");
21045            println!("world");
21046        }
21047        "#
21048        .unindent(),
21049    );
21050
21051    cx.set_head_text(&diff_base);
21052    executor.run_until_parked();
21053
21054    cx.update_editor(|editor, window, cx| {
21055        editor.go_to_next_hunk(&GoToHunk, window, cx);
21056        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21057    });
21058    executor.run_until_parked();
21059    cx.assert_state_with_diff(
21060        r#"
21061          use some::modified;
21062
21063
21064          fn main() {
21065        -     println!("hello");
21066        + ˇ    println!("hello there");
21067
21068              println!("around the");
21069              println!("world");
21070          }
21071        "#
21072        .unindent(),
21073    );
21074
21075    cx.update_editor(|editor, window, cx| {
21076        for _ in 0..2 {
21077            editor.go_to_next_hunk(&GoToHunk, window, cx);
21078            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21079        }
21080    });
21081    executor.run_until_parked();
21082    cx.assert_state_with_diff(
21083        r#"
21084        - use some::mod;
21085        + ˇuse some::modified;
21086
21087
21088          fn main() {
21089        -     println!("hello");
21090        +     println!("hello there");
21091
21092        +     println!("around the");
21093              println!("world");
21094          }
21095        "#
21096        .unindent(),
21097    );
21098
21099    cx.update_editor(|editor, window, cx| {
21100        editor.go_to_next_hunk(&GoToHunk, window, cx);
21101        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21102    });
21103    executor.run_until_parked();
21104    cx.assert_state_with_diff(
21105        r#"
21106        - use some::mod;
21107        + use some::modified;
21108
21109        - const A: u32 = 42;
21110          ˇ
21111          fn main() {
21112        -     println!("hello");
21113        +     println!("hello there");
21114
21115        +     println!("around the");
21116              println!("world");
21117          }
21118        "#
21119        .unindent(),
21120    );
21121
21122    cx.update_editor(|editor, window, cx| {
21123        editor.cancel(&Cancel, window, cx);
21124    });
21125
21126    cx.assert_state_with_diff(
21127        r#"
21128          use some::modified;
21129
21130          ˇ
21131          fn main() {
21132              println!("hello there");
21133
21134              println!("around the");
21135              println!("world");
21136          }
21137        "#
21138        .unindent(),
21139    );
21140}
21141
21142#[gpui::test]
21143async fn test_diff_base_change_with_expanded_diff_hunks(
21144    executor: BackgroundExecutor,
21145    cx: &mut TestAppContext,
21146) {
21147    init_test(cx, |_| {});
21148
21149    let mut cx = EditorTestContext::new(cx).await;
21150
21151    let diff_base = r#"
21152        use some::mod1;
21153        use some::mod2;
21154
21155        const A: u32 = 42;
21156        const B: u32 = 42;
21157        const C: u32 = 42;
21158
21159        fn main() {
21160            println!("hello");
21161
21162            println!("world");
21163        }
21164        "#
21165    .unindent();
21166
21167    cx.set_state(
21168        &r#"
21169        use some::mod2;
21170
21171        const A: u32 = 42;
21172        const C: u32 = 42;
21173
21174        fn main(ˇ) {
21175            //println!("hello");
21176
21177            println!("world");
21178            //
21179            //
21180        }
21181        "#
21182        .unindent(),
21183    );
21184
21185    cx.set_head_text(&diff_base);
21186    executor.run_until_parked();
21187
21188    cx.update_editor(|editor, window, cx| {
21189        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21190    });
21191    executor.run_until_parked();
21192    cx.assert_state_with_diff(
21193        r#"
21194        - use some::mod1;
21195          use some::mod2;
21196
21197          const A: u32 = 42;
21198        - const B: u32 = 42;
21199          const C: u32 = 42;
21200
21201          fn main(ˇ) {
21202        -     println!("hello");
21203        +     //println!("hello");
21204
21205              println!("world");
21206        +     //
21207        +     //
21208          }
21209        "#
21210        .unindent(),
21211    );
21212
21213    cx.set_head_text("new diff base!");
21214    executor.run_until_parked();
21215    cx.assert_state_with_diff(
21216        r#"
21217        - new diff base!
21218        + use some::mod2;
21219        +
21220        + const A: u32 = 42;
21221        + const C: u32 = 42;
21222        +
21223        + fn main(ˇ) {
21224        +     //println!("hello");
21225        +
21226        +     println!("world");
21227        +     //
21228        +     //
21229        + }
21230        "#
21231        .unindent(),
21232    );
21233}
21234
21235#[gpui::test]
21236async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
21237    init_test(cx, |_| {});
21238
21239    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
21240    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
21241    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
21242    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
21243    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
21244    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
21245
21246    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
21247    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
21248    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
21249
21250    let multi_buffer = cx.new(|cx| {
21251        let mut multibuffer = MultiBuffer::new(ReadWrite);
21252        multibuffer.push_excerpts(
21253            buffer_1.clone(),
21254            [
21255                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21256                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21257                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
21258            ],
21259            cx,
21260        );
21261        multibuffer.push_excerpts(
21262            buffer_2.clone(),
21263            [
21264                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21265                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21266                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
21267            ],
21268            cx,
21269        );
21270        multibuffer.push_excerpts(
21271            buffer_3.clone(),
21272            [
21273                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21274                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21275                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
21276            ],
21277            cx,
21278        );
21279        multibuffer
21280    });
21281
21282    let editor =
21283        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21284    editor
21285        .update(cx, |editor, _window, cx| {
21286            for (buffer, diff_base) in [
21287                (buffer_1.clone(), file_1_old),
21288                (buffer_2.clone(), file_2_old),
21289                (buffer_3.clone(), file_3_old),
21290            ] {
21291                let diff = cx.new(|cx| {
21292                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
21293                });
21294                editor
21295                    .buffer
21296                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
21297            }
21298        })
21299        .unwrap();
21300
21301    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21302    cx.run_until_parked();
21303
21304    cx.assert_editor_state(
21305        &"
21306            ˇaaa
21307            ccc
21308            ddd
21309
21310            ggg
21311            hhh
21312
21313
21314            lll
21315            mmm
21316            NNN
21317
21318            qqq
21319            rrr
21320
21321            uuu
21322            111
21323            222
21324            333
21325
21326            666
21327            777
21328
21329            000
21330            !!!"
21331        .unindent(),
21332    );
21333
21334    cx.update_editor(|editor, window, cx| {
21335        editor.select_all(&SelectAll, window, cx);
21336        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21337    });
21338    cx.executor().run_until_parked();
21339
21340    cx.assert_state_with_diff(
21341        "
21342            «aaa
21343          - bbb
21344            ccc
21345            ddd
21346
21347            ggg
21348            hhh
21349
21350
21351            lll
21352            mmm
21353          - nnn
21354          + NNN
21355
21356            qqq
21357            rrr
21358
21359            uuu
21360            111
21361            222
21362            333
21363
21364          + 666
21365            777
21366
21367            000
21368            !!!ˇ»"
21369            .unindent(),
21370    );
21371}
21372
21373#[gpui::test]
21374async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
21375    init_test(cx, |_| {});
21376
21377    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
21378    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
21379
21380    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
21381    let multi_buffer = cx.new(|cx| {
21382        let mut multibuffer = MultiBuffer::new(ReadWrite);
21383        multibuffer.push_excerpts(
21384            buffer.clone(),
21385            [
21386                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
21387                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
21388                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
21389            ],
21390            cx,
21391        );
21392        multibuffer
21393    });
21394
21395    let editor =
21396        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21397    editor
21398        .update(cx, |editor, _window, cx| {
21399            let diff = cx.new(|cx| {
21400                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
21401            });
21402            editor
21403                .buffer
21404                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
21405        })
21406        .unwrap();
21407
21408    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21409    cx.run_until_parked();
21410
21411    cx.update_editor(|editor, window, cx| {
21412        editor.expand_all_diff_hunks(&Default::default(), window, cx)
21413    });
21414    cx.executor().run_until_parked();
21415
21416    // When the start of a hunk coincides with the start of its excerpt,
21417    // the hunk is expanded. When the start of a hunk is earlier than
21418    // the start of its excerpt, the hunk is not expanded.
21419    cx.assert_state_with_diff(
21420        "
21421            ˇaaa
21422          - bbb
21423          + BBB
21424
21425          - ddd
21426          - eee
21427          + DDD
21428          + EEE
21429            fff
21430
21431            iii
21432        "
21433        .unindent(),
21434    );
21435}
21436
21437#[gpui::test]
21438async fn test_edits_around_expanded_insertion_hunks(
21439    executor: BackgroundExecutor,
21440    cx: &mut TestAppContext,
21441) {
21442    init_test(cx, |_| {});
21443
21444    let mut cx = EditorTestContext::new(cx).await;
21445
21446    let diff_base = r#"
21447        use some::mod1;
21448        use some::mod2;
21449
21450        const A: u32 = 42;
21451
21452        fn main() {
21453            println!("hello");
21454
21455            println!("world");
21456        }
21457        "#
21458    .unindent();
21459    executor.run_until_parked();
21460    cx.set_state(
21461        &r#"
21462        use some::mod1;
21463        use some::mod2;
21464
21465        const A: u32 = 42;
21466        const B: u32 = 42;
21467        const C: u32 = 42;
21468        ˇ
21469
21470        fn main() {
21471            println!("hello");
21472
21473            println!("world");
21474        }
21475        "#
21476        .unindent(),
21477    );
21478
21479    cx.set_head_text(&diff_base);
21480    executor.run_until_parked();
21481
21482    cx.update_editor(|editor, window, cx| {
21483        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21484    });
21485    executor.run_until_parked();
21486
21487    cx.assert_state_with_diff(
21488        r#"
21489        use some::mod1;
21490        use some::mod2;
21491
21492        const A: u32 = 42;
21493      + const B: u32 = 42;
21494      + const C: u32 = 42;
21495      + ˇ
21496
21497        fn main() {
21498            println!("hello");
21499
21500            println!("world");
21501        }
21502      "#
21503        .unindent(),
21504    );
21505
21506    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
21507    executor.run_until_parked();
21508
21509    cx.assert_state_with_diff(
21510        r#"
21511        use some::mod1;
21512        use some::mod2;
21513
21514        const A: u32 = 42;
21515      + const B: u32 = 42;
21516      + const C: u32 = 42;
21517      + const D: u32 = 42;
21518      + ˇ
21519
21520        fn main() {
21521            println!("hello");
21522
21523            println!("world");
21524        }
21525      "#
21526        .unindent(),
21527    );
21528
21529    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
21530    executor.run_until_parked();
21531
21532    cx.assert_state_with_diff(
21533        r#"
21534        use some::mod1;
21535        use some::mod2;
21536
21537        const A: u32 = 42;
21538      + const B: u32 = 42;
21539      + const C: u32 = 42;
21540      + const D: u32 = 42;
21541      + const E: u32 = 42;
21542      + ˇ
21543
21544        fn main() {
21545            println!("hello");
21546
21547            println!("world");
21548        }
21549      "#
21550        .unindent(),
21551    );
21552
21553    cx.update_editor(|editor, window, cx| {
21554        editor.delete_line(&DeleteLine, window, cx);
21555    });
21556    executor.run_until_parked();
21557
21558    cx.assert_state_with_diff(
21559        r#"
21560        use some::mod1;
21561        use some::mod2;
21562
21563        const A: u32 = 42;
21564      + const B: u32 = 42;
21565      + const C: u32 = 42;
21566      + const D: u32 = 42;
21567      + const E: u32 = 42;
21568        ˇ
21569        fn main() {
21570            println!("hello");
21571
21572            println!("world");
21573        }
21574      "#
21575        .unindent(),
21576    );
21577
21578    cx.update_editor(|editor, window, cx| {
21579        editor.move_up(&MoveUp, window, cx);
21580        editor.delete_line(&DeleteLine, window, cx);
21581        editor.move_up(&MoveUp, window, cx);
21582        editor.delete_line(&DeleteLine, window, cx);
21583        editor.move_up(&MoveUp, window, cx);
21584        editor.delete_line(&DeleteLine, window, cx);
21585    });
21586    executor.run_until_parked();
21587    cx.assert_state_with_diff(
21588        r#"
21589        use some::mod1;
21590        use some::mod2;
21591
21592        const A: u32 = 42;
21593      + const B: u32 = 42;
21594        ˇ
21595        fn main() {
21596            println!("hello");
21597
21598            println!("world");
21599        }
21600      "#
21601        .unindent(),
21602    );
21603
21604    cx.update_editor(|editor, window, cx| {
21605        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21606        editor.delete_line(&DeleteLine, window, cx);
21607    });
21608    executor.run_until_parked();
21609    cx.assert_state_with_diff(
21610        r#"
21611        ˇ
21612        fn main() {
21613            println!("hello");
21614
21615            println!("world");
21616        }
21617      "#
21618        .unindent(),
21619    );
21620}
21621
21622#[gpui::test]
21623async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21624    init_test(cx, |_| {});
21625
21626    let mut cx = EditorTestContext::new(cx).await;
21627    cx.set_head_text(indoc! { "
21628        one
21629        two
21630        three
21631        four
21632        five
21633        "
21634    });
21635    cx.set_state(indoc! { "
21636        one
21637        ˇthree
21638        five
21639    "});
21640    cx.run_until_parked();
21641    cx.update_editor(|editor, window, cx| {
21642        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21643    });
21644    cx.assert_state_with_diff(
21645        indoc! { "
21646        one
21647      - two
21648        ˇthree
21649      - four
21650        five
21651    "}
21652        .to_string(),
21653    );
21654    cx.update_editor(|editor, window, cx| {
21655        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21656    });
21657
21658    cx.assert_state_with_diff(
21659        indoc! { "
21660        one
21661        ˇthree
21662        five
21663    "}
21664        .to_string(),
21665    );
21666
21667    cx.update_editor(|editor, window, cx| {
21668        editor.move_up(&MoveUp, window, cx);
21669        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21670    });
21671    cx.assert_state_with_diff(
21672        indoc! { "
21673        ˇone
21674      - two
21675        three
21676        five
21677    "}
21678        .to_string(),
21679    );
21680
21681    cx.update_editor(|editor, window, cx| {
21682        editor.move_down(&MoveDown, window, cx);
21683        editor.move_down(&MoveDown, window, cx);
21684        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21685    });
21686    cx.assert_state_with_diff(
21687        indoc! { "
21688        one
21689      - two
21690        ˇthree
21691      - four
21692        five
21693    "}
21694        .to_string(),
21695    );
21696
21697    cx.set_state(indoc! { "
21698        one
21699        ˇTWO
21700        three
21701        four
21702        five
21703    "});
21704    cx.run_until_parked();
21705    cx.update_editor(|editor, window, cx| {
21706        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21707    });
21708
21709    cx.assert_state_with_diff(
21710        indoc! { "
21711            one
21712          - two
21713          + ˇTWO
21714            three
21715            four
21716            five
21717        "}
21718        .to_string(),
21719    );
21720    cx.update_editor(|editor, window, cx| {
21721        editor.move_up(&Default::default(), window, cx);
21722        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21723    });
21724    cx.assert_state_with_diff(
21725        indoc! { "
21726            one
21727            ˇTWO
21728            three
21729            four
21730            five
21731        "}
21732        .to_string(),
21733    );
21734}
21735
21736#[gpui::test]
21737async fn test_toggling_adjacent_diff_hunks_2(
21738    executor: BackgroundExecutor,
21739    cx: &mut TestAppContext,
21740) {
21741    init_test(cx, |_| {});
21742
21743    let mut cx = EditorTestContext::new(cx).await;
21744
21745    let diff_base = r#"
21746        lineA
21747        lineB
21748        lineC
21749        lineD
21750        "#
21751    .unindent();
21752
21753    cx.set_state(
21754        &r#"
21755        ˇlineA1
21756        lineB
21757        lineD
21758        "#
21759        .unindent(),
21760    );
21761    cx.set_head_text(&diff_base);
21762    executor.run_until_parked();
21763
21764    cx.update_editor(|editor, window, cx| {
21765        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21766    });
21767    executor.run_until_parked();
21768    cx.assert_state_with_diff(
21769        r#"
21770        - lineA
21771        + ˇlineA1
21772          lineB
21773          lineD
21774        "#
21775        .unindent(),
21776    );
21777
21778    cx.update_editor(|editor, window, cx| {
21779        editor.move_down(&MoveDown, window, cx);
21780        editor.move_right(&MoveRight, window, cx);
21781        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21782    });
21783    executor.run_until_parked();
21784    cx.assert_state_with_diff(
21785        r#"
21786        - lineA
21787        + lineA1
21788          lˇineB
21789        - lineC
21790          lineD
21791        "#
21792        .unindent(),
21793    );
21794}
21795
21796#[gpui::test]
21797async fn test_edits_around_expanded_deletion_hunks(
21798    executor: BackgroundExecutor,
21799    cx: &mut TestAppContext,
21800) {
21801    init_test(cx, |_| {});
21802
21803    let mut cx = EditorTestContext::new(cx).await;
21804
21805    let diff_base = r#"
21806        use some::mod1;
21807        use some::mod2;
21808
21809        const A: u32 = 42;
21810        const B: u32 = 42;
21811        const C: u32 = 42;
21812
21813
21814        fn main() {
21815            println!("hello");
21816
21817            println!("world");
21818        }
21819    "#
21820    .unindent();
21821    executor.run_until_parked();
21822    cx.set_state(
21823        &r#"
21824        use some::mod1;
21825        use some::mod2;
21826
21827        ˇconst B: u32 = 42;
21828        const C: u32 = 42;
21829
21830
21831        fn main() {
21832            println!("hello");
21833
21834            println!("world");
21835        }
21836        "#
21837        .unindent(),
21838    );
21839
21840    cx.set_head_text(&diff_base);
21841    executor.run_until_parked();
21842
21843    cx.update_editor(|editor, window, cx| {
21844        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21845    });
21846    executor.run_until_parked();
21847
21848    cx.assert_state_with_diff(
21849        r#"
21850        use some::mod1;
21851        use some::mod2;
21852
21853      - const A: u32 = 42;
21854        ˇconst B: u32 = 42;
21855        const C: u32 = 42;
21856
21857
21858        fn main() {
21859            println!("hello");
21860
21861            println!("world");
21862        }
21863      "#
21864        .unindent(),
21865    );
21866
21867    cx.update_editor(|editor, window, cx| {
21868        editor.delete_line(&DeleteLine, window, cx);
21869    });
21870    executor.run_until_parked();
21871    cx.assert_state_with_diff(
21872        r#"
21873        use some::mod1;
21874        use some::mod2;
21875
21876      - const A: u32 = 42;
21877      - const B: u32 = 42;
21878        ˇconst C: u32 = 42;
21879
21880
21881        fn main() {
21882            println!("hello");
21883
21884            println!("world");
21885        }
21886      "#
21887        .unindent(),
21888    );
21889
21890    cx.update_editor(|editor, window, cx| {
21891        editor.delete_line(&DeleteLine, window, cx);
21892    });
21893    executor.run_until_parked();
21894    cx.assert_state_with_diff(
21895        r#"
21896        use some::mod1;
21897        use some::mod2;
21898
21899      - const A: u32 = 42;
21900      - const B: u32 = 42;
21901      - const C: u32 = 42;
21902        ˇ
21903
21904        fn main() {
21905            println!("hello");
21906
21907            println!("world");
21908        }
21909      "#
21910        .unindent(),
21911    );
21912
21913    cx.update_editor(|editor, window, cx| {
21914        editor.handle_input("replacement", window, cx);
21915    });
21916    executor.run_until_parked();
21917    cx.assert_state_with_diff(
21918        r#"
21919        use some::mod1;
21920        use some::mod2;
21921
21922      - const A: u32 = 42;
21923      - const B: u32 = 42;
21924      - const C: u32 = 42;
21925      -
21926      + replacementˇ
21927
21928        fn main() {
21929            println!("hello");
21930
21931            println!("world");
21932        }
21933      "#
21934        .unindent(),
21935    );
21936}
21937
21938#[gpui::test]
21939async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21940    init_test(cx, |_| {});
21941
21942    let mut cx = EditorTestContext::new(cx).await;
21943
21944    let base_text = r#"
21945        one
21946        two
21947        three
21948        four
21949        five
21950    "#
21951    .unindent();
21952    executor.run_until_parked();
21953    cx.set_state(
21954        &r#"
21955        one
21956        two
21957        fˇour
21958        five
21959        "#
21960        .unindent(),
21961    );
21962
21963    cx.set_head_text(&base_text);
21964    executor.run_until_parked();
21965
21966    cx.update_editor(|editor, window, cx| {
21967        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21968    });
21969    executor.run_until_parked();
21970
21971    cx.assert_state_with_diff(
21972        r#"
21973          one
21974          two
21975        - three
21976          fˇour
21977          five
21978        "#
21979        .unindent(),
21980    );
21981
21982    cx.update_editor(|editor, window, cx| {
21983        editor.backspace(&Backspace, window, cx);
21984        editor.backspace(&Backspace, window, cx);
21985    });
21986    executor.run_until_parked();
21987    cx.assert_state_with_diff(
21988        r#"
21989          one
21990          two
21991        - threeˇ
21992        - four
21993        + our
21994          five
21995        "#
21996        .unindent(),
21997    );
21998}
21999
22000#[gpui::test]
22001async fn test_edit_after_expanded_modification_hunk(
22002    executor: BackgroundExecutor,
22003    cx: &mut TestAppContext,
22004) {
22005    init_test(cx, |_| {});
22006
22007    let mut cx = EditorTestContext::new(cx).await;
22008
22009    let diff_base = r#"
22010        use some::mod1;
22011        use some::mod2;
22012
22013        const A: u32 = 42;
22014        const B: u32 = 42;
22015        const C: u32 = 42;
22016        const D: u32 = 42;
22017
22018
22019        fn main() {
22020            println!("hello");
22021
22022            println!("world");
22023        }"#
22024    .unindent();
22025
22026    cx.set_state(
22027        &r#"
22028        use some::mod1;
22029        use some::mod2;
22030
22031        const A: u32 = 42;
22032        const B: u32 = 42;
22033        const C: u32 = 43ˇ
22034        const D: u32 = 42;
22035
22036
22037        fn main() {
22038            println!("hello");
22039
22040            println!("world");
22041        }"#
22042        .unindent(),
22043    );
22044
22045    cx.set_head_text(&diff_base);
22046    executor.run_until_parked();
22047    cx.update_editor(|editor, window, cx| {
22048        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22049    });
22050    executor.run_until_parked();
22051
22052    cx.assert_state_with_diff(
22053        r#"
22054        use some::mod1;
22055        use some::mod2;
22056
22057        const A: u32 = 42;
22058        const B: u32 = 42;
22059      - const C: u32 = 42;
22060      + const C: u32 = 43ˇ
22061        const D: u32 = 42;
22062
22063
22064        fn main() {
22065            println!("hello");
22066
22067            println!("world");
22068        }"#
22069        .unindent(),
22070    );
22071
22072    cx.update_editor(|editor, window, cx| {
22073        editor.handle_input("\nnew_line\n", window, cx);
22074    });
22075    executor.run_until_parked();
22076
22077    cx.assert_state_with_diff(
22078        r#"
22079        use some::mod1;
22080        use some::mod2;
22081
22082        const A: u32 = 42;
22083        const B: u32 = 42;
22084      - const C: u32 = 42;
22085      + const C: u32 = 43
22086      + new_line
22087      + ˇ
22088        const D: u32 = 42;
22089
22090
22091        fn main() {
22092            println!("hello");
22093
22094            println!("world");
22095        }"#
22096        .unindent(),
22097    );
22098}
22099
22100#[gpui::test]
22101async fn test_stage_and_unstage_added_file_hunk(
22102    executor: BackgroundExecutor,
22103    cx: &mut TestAppContext,
22104) {
22105    init_test(cx, |_| {});
22106
22107    let mut cx = EditorTestContext::new(cx).await;
22108    cx.update_editor(|editor, _, cx| {
22109        editor.set_expand_all_diff_hunks(cx);
22110    });
22111
22112    let working_copy = r#"
22113            ˇfn main() {
22114                println!("hello, world!");
22115            }
22116        "#
22117    .unindent();
22118
22119    cx.set_state(&working_copy);
22120    executor.run_until_parked();
22121
22122    cx.assert_state_with_diff(
22123        r#"
22124            + ˇfn main() {
22125            +     println!("hello, world!");
22126            + }
22127        "#
22128        .unindent(),
22129    );
22130    cx.assert_index_text(None);
22131
22132    cx.update_editor(|editor, window, cx| {
22133        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22134    });
22135    executor.run_until_parked();
22136    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
22137    cx.assert_state_with_diff(
22138        r#"
22139            + ˇfn main() {
22140            +     println!("hello, world!");
22141            + }
22142        "#
22143        .unindent(),
22144    );
22145
22146    cx.update_editor(|editor, window, cx| {
22147        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22148    });
22149    executor.run_until_parked();
22150    cx.assert_index_text(None);
22151}
22152
22153async fn setup_indent_guides_editor(
22154    text: &str,
22155    cx: &mut TestAppContext,
22156) -> (BufferId, EditorTestContext) {
22157    init_test(cx, |_| {});
22158
22159    let mut cx = EditorTestContext::new(cx).await;
22160
22161    let buffer_id = cx.update_editor(|editor, window, cx| {
22162        editor.set_text(text, window, cx);
22163        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
22164
22165        buffer_ids[0]
22166    });
22167
22168    (buffer_id, cx)
22169}
22170
22171fn assert_indent_guides(
22172    range: Range<u32>,
22173    expected: Vec<IndentGuide>,
22174    active_indices: Option<Vec<usize>>,
22175    cx: &mut EditorTestContext,
22176) {
22177    let indent_guides = cx.update_editor(|editor, window, cx| {
22178        let snapshot = editor.snapshot(window, cx).display_snapshot;
22179        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
22180            editor,
22181            MultiBufferRow(range.start)..MultiBufferRow(range.end),
22182            true,
22183            &snapshot,
22184            cx,
22185        );
22186
22187        indent_guides.sort_by(|a, b| {
22188            a.depth.cmp(&b.depth).then(
22189                a.start_row
22190                    .cmp(&b.start_row)
22191                    .then(a.end_row.cmp(&b.end_row)),
22192            )
22193        });
22194        indent_guides
22195    });
22196
22197    if let Some(expected) = active_indices {
22198        let active_indices = cx.update_editor(|editor, window, cx| {
22199            let snapshot = editor.snapshot(window, cx).display_snapshot;
22200            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
22201        });
22202
22203        assert_eq!(
22204            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
22205            expected,
22206            "Active indent guide indices do not match"
22207        );
22208    }
22209
22210    assert_eq!(indent_guides, expected, "Indent guides do not match");
22211}
22212
22213fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
22214    IndentGuide {
22215        buffer_id,
22216        start_row: MultiBufferRow(start_row),
22217        end_row: MultiBufferRow(end_row),
22218        depth,
22219        tab_size: 4,
22220        settings: IndentGuideSettings {
22221            enabled: true,
22222            line_width: 1,
22223            active_line_width: 1,
22224            coloring: IndentGuideColoring::default(),
22225            background_coloring: IndentGuideBackgroundColoring::default(),
22226        },
22227    }
22228}
22229
22230#[gpui::test]
22231async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
22232    let (buffer_id, mut cx) = setup_indent_guides_editor(
22233        &"
22234        fn main() {
22235            let a = 1;
22236        }"
22237        .unindent(),
22238        cx,
22239    )
22240    .await;
22241
22242    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22243}
22244
22245#[gpui::test]
22246async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
22247    let (buffer_id, mut cx) = setup_indent_guides_editor(
22248        &"
22249        fn main() {
22250            let a = 1;
22251            let b = 2;
22252        }"
22253        .unindent(),
22254        cx,
22255    )
22256    .await;
22257
22258    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
22259}
22260
22261#[gpui::test]
22262async fn test_indent_guide_nested(cx: &mut TestAppContext) {
22263    let (buffer_id, mut cx) = setup_indent_guides_editor(
22264        &"
22265        fn main() {
22266            let a = 1;
22267            if a == 3 {
22268                let b = 2;
22269            } else {
22270                let c = 3;
22271            }
22272        }"
22273        .unindent(),
22274        cx,
22275    )
22276    .await;
22277
22278    assert_indent_guides(
22279        0..8,
22280        vec![
22281            indent_guide(buffer_id, 1, 6, 0),
22282            indent_guide(buffer_id, 3, 3, 1),
22283            indent_guide(buffer_id, 5, 5, 1),
22284        ],
22285        None,
22286        &mut cx,
22287    );
22288}
22289
22290#[gpui::test]
22291async fn test_indent_guide_tab(cx: &mut TestAppContext) {
22292    let (buffer_id, mut cx) = setup_indent_guides_editor(
22293        &"
22294        fn main() {
22295            let a = 1;
22296                let b = 2;
22297            let c = 3;
22298        }"
22299        .unindent(),
22300        cx,
22301    )
22302    .await;
22303
22304    assert_indent_guides(
22305        0..5,
22306        vec![
22307            indent_guide(buffer_id, 1, 3, 0),
22308            indent_guide(buffer_id, 2, 2, 1),
22309        ],
22310        None,
22311        &mut cx,
22312    );
22313}
22314
22315#[gpui::test]
22316async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
22317    let (buffer_id, mut cx) = setup_indent_guides_editor(
22318        &"
22319        fn main() {
22320            let a = 1;
22321
22322            let c = 3;
22323        }"
22324        .unindent(),
22325        cx,
22326    )
22327    .await;
22328
22329    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
22330}
22331
22332#[gpui::test]
22333async fn test_indent_guide_complex(cx: &mut TestAppContext) {
22334    let (buffer_id, mut cx) = setup_indent_guides_editor(
22335        &"
22336        fn main() {
22337            let a = 1;
22338
22339            let c = 3;
22340
22341            if a == 3 {
22342                let b = 2;
22343            } else {
22344                let c = 3;
22345            }
22346        }"
22347        .unindent(),
22348        cx,
22349    )
22350    .await;
22351
22352    assert_indent_guides(
22353        0..11,
22354        vec![
22355            indent_guide(buffer_id, 1, 9, 0),
22356            indent_guide(buffer_id, 6, 6, 1),
22357            indent_guide(buffer_id, 8, 8, 1),
22358        ],
22359        None,
22360        &mut cx,
22361    );
22362}
22363
22364#[gpui::test]
22365async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
22366    let (buffer_id, mut cx) = setup_indent_guides_editor(
22367        &"
22368        fn main() {
22369            let a = 1;
22370
22371            let c = 3;
22372
22373            if a == 3 {
22374                let b = 2;
22375            } else {
22376                let c = 3;
22377            }
22378        }"
22379        .unindent(),
22380        cx,
22381    )
22382    .await;
22383
22384    assert_indent_guides(
22385        1..11,
22386        vec![
22387            indent_guide(buffer_id, 1, 9, 0),
22388            indent_guide(buffer_id, 6, 6, 1),
22389            indent_guide(buffer_id, 8, 8, 1),
22390        ],
22391        None,
22392        &mut cx,
22393    );
22394}
22395
22396#[gpui::test]
22397async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
22398    let (buffer_id, mut cx) = setup_indent_guides_editor(
22399        &"
22400        fn main() {
22401            let a = 1;
22402
22403            let c = 3;
22404
22405            if a == 3 {
22406                let b = 2;
22407            } else {
22408                let c = 3;
22409            }
22410        }"
22411        .unindent(),
22412        cx,
22413    )
22414    .await;
22415
22416    assert_indent_guides(
22417        1..10,
22418        vec![
22419            indent_guide(buffer_id, 1, 9, 0),
22420            indent_guide(buffer_id, 6, 6, 1),
22421            indent_guide(buffer_id, 8, 8, 1),
22422        ],
22423        None,
22424        &mut cx,
22425    );
22426}
22427
22428#[gpui::test]
22429async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
22430    let (buffer_id, mut cx) = setup_indent_guides_editor(
22431        &"
22432        fn main() {
22433            if a {
22434                b(
22435                    c,
22436                    d,
22437                )
22438            } else {
22439                e(
22440                    f
22441                )
22442            }
22443        }"
22444        .unindent(),
22445        cx,
22446    )
22447    .await;
22448
22449    assert_indent_guides(
22450        0..11,
22451        vec![
22452            indent_guide(buffer_id, 1, 10, 0),
22453            indent_guide(buffer_id, 2, 5, 1),
22454            indent_guide(buffer_id, 7, 9, 1),
22455            indent_guide(buffer_id, 3, 4, 2),
22456            indent_guide(buffer_id, 8, 8, 2),
22457        ],
22458        None,
22459        &mut cx,
22460    );
22461
22462    cx.update_editor(|editor, window, cx| {
22463        editor.fold_at(MultiBufferRow(2), window, cx);
22464        assert_eq!(
22465            editor.display_text(cx),
22466            "
22467            fn main() {
22468                if a {
22469                    b(⋯
22470                    )
22471                } else {
22472                    e(
22473                        f
22474                    )
22475                }
22476            }"
22477            .unindent()
22478        );
22479    });
22480
22481    assert_indent_guides(
22482        0..11,
22483        vec![
22484            indent_guide(buffer_id, 1, 10, 0),
22485            indent_guide(buffer_id, 2, 5, 1),
22486            indent_guide(buffer_id, 7, 9, 1),
22487            indent_guide(buffer_id, 8, 8, 2),
22488        ],
22489        None,
22490        &mut cx,
22491    );
22492}
22493
22494#[gpui::test]
22495async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
22496    let (buffer_id, mut cx) = setup_indent_guides_editor(
22497        &"
22498        block1
22499            block2
22500                block3
22501                    block4
22502            block2
22503        block1
22504        block1"
22505            .unindent(),
22506        cx,
22507    )
22508    .await;
22509
22510    assert_indent_guides(
22511        1..10,
22512        vec![
22513            indent_guide(buffer_id, 1, 4, 0),
22514            indent_guide(buffer_id, 2, 3, 1),
22515            indent_guide(buffer_id, 3, 3, 2),
22516        ],
22517        None,
22518        &mut cx,
22519    );
22520}
22521
22522#[gpui::test]
22523async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
22524    let (buffer_id, mut cx) = setup_indent_guides_editor(
22525        &"
22526        block1
22527            block2
22528                block3
22529
22530        block1
22531        block1"
22532            .unindent(),
22533        cx,
22534    )
22535    .await;
22536
22537    assert_indent_guides(
22538        0..6,
22539        vec![
22540            indent_guide(buffer_id, 1, 2, 0),
22541            indent_guide(buffer_id, 2, 2, 1),
22542        ],
22543        None,
22544        &mut cx,
22545    );
22546}
22547
22548#[gpui::test]
22549async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22550    let (buffer_id, mut cx) = setup_indent_guides_editor(
22551        &"
22552        function component() {
22553        \treturn (
22554        \t\t\t
22555        \t\t<div>
22556        \t\t\t<abc></abc>
22557        \t\t</div>
22558        \t)
22559        }"
22560        .unindent(),
22561        cx,
22562    )
22563    .await;
22564
22565    assert_indent_guides(
22566        0..8,
22567        vec![
22568            indent_guide(buffer_id, 1, 6, 0),
22569            indent_guide(buffer_id, 2, 5, 1),
22570            indent_guide(buffer_id, 4, 4, 2),
22571        ],
22572        None,
22573        &mut cx,
22574    );
22575}
22576
22577#[gpui::test]
22578async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22579    let (buffer_id, mut cx) = setup_indent_guides_editor(
22580        &"
22581        function component() {
22582        \treturn (
22583        \t
22584        \t\t<div>
22585        \t\t\t<abc></abc>
22586        \t\t</div>
22587        \t)
22588        }"
22589        .unindent(),
22590        cx,
22591    )
22592    .await;
22593
22594    assert_indent_guides(
22595        0..8,
22596        vec![
22597            indent_guide(buffer_id, 1, 6, 0),
22598            indent_guide(buffer_id, 2, 5, 1),
22599            indent_guide(buffer_id, 4, 4, 2),
22600        ],
22601        None,
22602        &mut cx,
22603    );
22604}
22605
22606#[gpui::test]
22607async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22608    let (buffer_id, mut cx) = setup_indent_guides_editor(
22609        &"
22610        block1
22611
22612
22613
22614            block2
22615        "
22616        .unindent(),
22617        cx,
22618    )
22619    .await;
22620
22621    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22622}
22623
22624#[gpui::test]
22625async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22626    let (buffer_id, mut cx) = setup_indent_guides_editor(
22627        &"
22628        def a:
22629        \tb = 3
22630        \tif True:
22631        \t\tc = 4
22632        \t\td = 5
22633        \tprint(b)
22634        "
22635        .unindent(),
22636        cx,
22637    )
22638    .await;
22639
22640    assert_indent_guides(
22641        0..6,
22642        vec![
22643            indent_guide(buffer_id, 1, 5, 0),
22644            indent_guide(buffer_id, 3, 4, 1),
22645        ],
22646        None,
22647        &mut cx,
22648    );
22649}
22650
22651#[gpui::test]
22652async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22653    let (buffer_id, mut cx) = setup_indent_guides_editor(
22654        &"
22655    fn main() {
22656        let a = 1;
22657    }"
22658        .unindent(),
22659        cx,
22660    )
22661    .await;
22662
22663    cx.update_editor(|editor, window, cx| {
22664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22665            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22666        });
22667    });
22668
22669    assert_indent_guides(
22670        0..3,
22671        vec![indent_guide(buffer_id, 1, 1, 0)],
22672        Some(vec![0]),
22673        &mut cx,
22674    );
22675}
22676
22677#[gpui::test]
22678async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22679    let (buffer_id, mut cx) = setup_indent_guides_editor(
22680        &"
22681    fn main() {
22682        if 1 == 2 {
22683            let a = 1;
22684        }
22685    }"
22686        .unindent(),
22687        cx,
22688    )
22689    .await;
22690
22691    cx.update_editor(|editor, window, cx| {
22692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22693            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22694        });
22695    });
22696    cx.run_until_parked();
22697
22698    assert_indent_guides(
22699        0..4,
22700        vec![
22701            indent_guide(buffer_id, 1, 3, 0),
22702            indent_guide(buffer_id, 2, 2, 1),
22703        ],
22704        Some(vec![1]),
22705        &mut cx,
22706    );
22707
22708    cx.update_editor(|editor, window, cx| {
22709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22710            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22711        });
22712    });
22713    cx.run_until_parked();
22714
22715    assert_indent_guides(
22716        0..4,
22717        vec![
22718            indent_guide(buffer_id, 1, 3, 0),
22719            indent_guide(buffer_id, 2, 2, 1),
22720        ],
22721        Some(vec![1]),
22722        &mut cx,
22723    );
22724
22725    cx.update_editor(|editor, window, cx| {
22726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22727            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22728        });
22729    });
22730    cx.run_until_parked();
22731
22732    assert_indent_guides(
22733        0..4,
22734        vec![
22735            indent_guide(buffer_id, 1, 3, 0),
22736            indent_guide(buffer_id, 2, 2, 1),
22737        ],
22738        Some(vec![0]),
22739        &mut cx,
22740    );
22741}
22742
22743#[gpui::test]
22744async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22745    let (buffer_id, mut cx) = setup_indent_guides_editor(
22746        &"
22747    fn main() {
22748        let a = 1;
22749
22750        let b = 2;
22751    }"
22752        .unindent(),
22753        cx,
22754    )
22755    .await;
22756
22757    cx.update_editor(|editor, window, cx| {
22758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22759            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22760        });
22761    });
22762
22763    assert_indent_guides(
22764        0..5,
22765        vec![indent_guide(buffer_id, 1, 3, 0)],
22766        Some(vec![0]),
22767        &mut cx,
22768    );
22769}
22770
22771#[gpui::test]
22772async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22773    let (buffer_id, mut cx) = setup_indent_guides_editor(
22774        &"
22775    def m:
22776        a = 1
22777        pass"
22778            .unindent(),
22779        cx,
22780    )
22781    .await;
22782
22783    cx.update_editor(|editor, window, cx| {
22784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22785            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22786        });
22787    });
22788
22789    assert_indent_guides(
22790        0..3,
22791        vec![indent_guide(buffer_id, 1, 2, 0)],
22792        Some(vec![0]),
22793        &mut cx,
22794    );
22795}
22796
22797#[gpui::test]
22798async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22799    init_test(cx, |_| {});
22800    let mut cx = EditorTestContext::new(cx).await;
22801    let text = indoc! {
22802        "
22803        impl A {
22804            fn b() {
22805                0;
22806                3;
22807                5;
22808                6;
22809                7;
22810            }
22811        }
22812        "
22813    };
22814    let base_text = indoc! {
22815        "
22816        impl A {
22817            fn b() {
22818                0;
22819                1;
22820                2;
22821                3;
22822                4;
22823            }
22824            fn c() {
22825                5;
22826                6;
22827                7;
22828            }
22829        }
22830        "
22831    };
22832
22833    cx.update_editor(|editor, window, cx| {
22834        editor.set_text(text, window, cx);
22835
22836        editor.buffer().update(cx, |multibuffer, cx| {
22837            let buffer = multibuffer.as_singleton().unwrap();
22838            let diff = cx.new(|cx| {
22839                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22840            });
22841
22842            multibuffer.set_all_diff_hunks_expanded(cx);
22843            multibuffer.add_diff(diff, cx);
22844
22845            buffer.read(cx).remote_id()
22846        })
22847    });
22848    cx.run_until_parked();
22849
22850    cx.assert_state_with_diff(
22851        indoc! { "
22852          impl A {
22853              fn b() {
22854                  0;
22855        -         1;
22856        -         2;
22857                  3;
22858        -         4;
22859        -     }
22860        -     fn c() {
22861                  5;
22862                  6;
22863                  7;
22864              }
22865          }
22866          ˇ"
22867        }
22868        .to_string(),
22869    );
22870
22871    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22872        editor
22873            .snapshot(window, cx)
22874            .buffer_snapshot()
22875            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22876            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22877            .collect::<Vec<_>>()
22878    });
22879    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22880    assert_eq!(
22881        actual_guides,
22882        vec![
22883            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22884            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22885            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22886        ]
22887    );
22888}
22889
22890#[gpui::test]
22891async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22892    init_test(cx, |_| {});
22893    let mut cx = EditorTestContext::new(cx).await;
22894
22895    let diff_base = r#"
22896        a
22897        b
22898        c
22899        "#
22900    .unindent();
22901
22902    cx.set_state(
22903        &r#"
22904        ˇA
22905        b
22906        C
22907        "#
22908        .unindent(),
22909    );
22910    cx.set_head_text(&diff_base);
22911    cx.update_editor(|editor, window, cx| {
22912        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22913    });
22914    executor.run_until_parked();
22915
22916    let both_hunks_expanded = r#"
22917        - a
22918        + ˇA
22919          b
22920        - c
22921        + C
22922        "#
22923    .unindent();
22924
22925    cx.assert_state_with_diff(both_hunks_expanded.clone());
22926
22927    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22928        let snapshot = editor.snapshot(window, cx);
22929        let hunks = editor
22930            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22931            .collect::<Vec<_>>();
22932        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22933        hunks
22934            .into_iter()
22935            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22936            .collect::<Vec<_>>()
22937    });
22938    assert_eq!(hunk_ranges.len(), 2);
22939
22940    cx.update_editor(|editor, _, cx| {
22941        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22942    });
22943    executor.run_until_parked();
22944
22945    let second_hunk_expanded = r#"
22946          ˇA
22947          b
22948        - c
22949        + C
22950        "#
22951    .unindent();
22952
22953    cx.assert_state_with_diff(second_hunk_expanded);
22954
22955    cx.update_editor(|editor, _, cx| {
22956        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22957    });
22958    executor.run_until_parked();
22959
22960    cx.assert_state_with_diff(both_hunks_expanded.clone());
22961
22962    cx.update_editor(|editor, _, cx| {
22963        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22964    });
22965    executor.run_until_parked();
22966
22967    let first_hunk_expanded = r#"
22968        - a
22969        + ˇA
22970          b
22971          C
22972        "#
22973    .unindent();
22974
22975    cx.assert_state_with_diff(first_hunk_expanded);
22976
22977    cx.update_editor(|editor, _, cx| {
22978        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22979    });
22980    executor.run_until_parked();
22981
22982    cx.assert_state_with_diff(both_hunks_expanded);
22983
22984    cx.set_state(
22985        &r#"
22986        ˇA
22987        b
22988        "#
22989        .unindent(),
22990    );
22991    cx.run_until_parked();
22992
22993    // TODO this cursor position seems bad
22994    cx.assert_state_with_diff(
22995        r#"
22996        - ˇa
22997        + A
22998          b
22999        "#
23000        .unindent(),
23001    );
23002
23003    cx.update_editor(|editor, window, cx| {
23004        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23005    });
23006
23007    cx.assert_state_with_diff(
23008        r#"
23009            - ˇa
23010            + A
23011              b
23012            - c
23013            "#
23014        .unindent(),
23015    );
23016
23017    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23018        let snapshot = editor.snapshot(window, cx);
23019        let hunks = editor
23020            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23021            .collect::<Vec<_>>();
23022        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23023        hunks
23024            .into_iter()
23025            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23026            .collect::<Vec<_>>()
23027    });
23028    assert_eq!(hunk_ranges.len(), 2);
23029
23030    cx.update_editor(|editor, _, cx| {
23031        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23032    });
23033    executor.run_until_parked();
23034
23035    cx.assert_state_with_diff(
23036        r#"
23037        - ˇa
23038        + A
23039          b
23040        "#
23041        .unindent(),
23042    );
23043}
23044
23045#[gpui::test]
23046async fn test_toggle_deletion_hunk_at_start_of_file(
23047    executor: BackgroundExecutor,
23048    cx: &mut TestAppContext,
23049) {
23050    init_test(cx, |_| {});
23051    let mut cx = EditorTestContext::new(cx).await;
23052
23053    let diff_base = r#"
23054        a
23055        b
23056        c
23057        "#
23058    .unindent();
23059
23060    cx.set_state(
23061        &r#"
23062        ˇb
23063        c
23064        "#
23065        .unindent(),
23066    );
23067    cx.set_head_text(&diff_base);
23068    cx.update_editor(|editor, window, cx| {
23069        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23070    });
23071    executor.run_until_parked();
23072
23073    let hunk_expanded = r#"
23074        - a
23075          ˇb
23076          c
23077        "#
23078    .unindent();
23079
23080    cx.assert_state_with_diff(hunk_expanded.clone());
23081
23082    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23083        let snapshot = editor.snapshot(window, cx);
23084        let hunks = editor
23085            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23086            .collect::<Vec<_>>();
23087        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23088        hunks
23089            .into_iter()
23090            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23091            .collect::<Vec<_>>()
23092    });
23093    assert_eq!(hunk_ranges.len(), 1);
23094
23095    cx.update_editor(|editor, _, cx| {
23096        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23097    });
23098    executor.run_until_parked();
23099
23100    let hunk_collapsed = r#"
23101          ˇb
23102          c
23103        "#
23104    .unindent();
23105
23106    cx.assert_state_with_diff(hunk_collapsed);
23107
23108    cx.update_editor(|editor, _, cx| {
23109        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23110    });
23111    executor.run_until_parked();
23112
23113    cx.assert_state_with_diff(hunk_expanded);
23114}
23115
23116#[gpui::test]
23117async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
23118    executor: BackgroundExecutor,
23119    cx: &mut TestAppContext,
23120) {
23121    init_test(cx, |_| {});
23122    let mut cx = EditorTestContext::new(cx).await;
23123
23124    cx.set_state("ˇnew\nsecond\nthird\n");
23125    cx.set_head_text("old\nsecond\nthird\n");
23126    cx.update_editor(|editor, window, cx| {
23127        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
23128    });
23129    executor.run_until_parked();
23130    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
23131
23132    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
23133    cx.update_editor(|editor, window, cx| {
23134        let snapshot = editor.snapshot(window, cx);
23135        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23136        let hunks = editor
23137            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23138            .collect::<Vec<_>>();
23139        assert_eq!(hunks.len(), 1);
23140        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
23141        editor.toggle_single_diff_hunk(hunk_range, cx)
23142    });
23143    executor.run_until_parked();
23144    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
23145
23146    // Keep the editor scrolled to the top so the full hunk remains visible.
23147    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
23148}
23149
23150#[gpui::test]
23151async fn test_display_diff_hunks(cx: &mut TestAppContext) {
23152    init_test(cx, |_| {});
23153
23154    let fs = FakeFs::new(cx.executor());
23155    fs.insert_tree(
23156        path!("/test"),
23157        json!({
23158            ".git": {},
23159            "file-1": "ONE\n",
23160            "file-2": "TWO\n",
23161            "file-3": "THREE\n",
23162        }),
23163    )
23164    .await;
23165
23166    fs.set_head_for_repo(
23167        path!("/test/.git").as_ref(),
23168        &[
23169            ("file-1", "one\n".into()),
23170            ("file-2", "two\n".into()),
23171            ("file-3", "three\n".into()),
23172        ],
23173        "deadbeef",
23174    );
23175
23176    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
23177    let mut buffers = vec![];
23178    for i in 1..=3 {
23179        let buffer = project
23180            .update(cx, |project, cx| {
23181                let path = format!(path!("/test/file-{}"), i);
23182                project.open_local_buffer(path, cx)
23183            })
23184            .await
23185            .unwrap();
23186        buffers.push(buffer);
23187    }
23188
23189    let multibuffer = cx.new(|cx| {
23190        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
23191        multibuffer.set_all_diff_hunks_expanded(cx);
23192        for buffer in &buffers {
23193            let snapshot = buffer.read(cx).snapshot();
23194            multibuffer.set_excerpts_for_path(
23195                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
23196                buffer.clone(),
23197                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
23198                2,
23199                cx,
23200            );
23201        }
23202        multibuffer
23203    });
23204
23205    let editor = cx.add_window(|window, cx| {
23206        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
23207    });
23208    cx.run_until_parked();
23209
23210    let snapshot = editor
23211        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23212        .unwrap();
23213    let hunks = snapshot
23214        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
23215        .map(|hunk| match hunk {
23216            DisplayDiffHunk::Unfolded {
23217                display_row_range, ..
23218            } => display_row_range,
23219            DisplayDiffHunk::Folded { .. } => unreachable!(),
23220        })
23221        .collect::<Vec<_>>();
23222    assert_eq!(
23223        hunks,
23224        [
23225            DisplayRow(2)..DisplayRow(4),
23226            DisplayRow(7)..DisplayRow(9),
23227            DisplayRow(12)..DisplayRow(14),
23228        ]
23229    );
23230}
23231
23232#[gpui::test]
23233async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
23234    init_test(cx, |_| {});
23235
23236    let mut cx = EditorTestContext::new(cx).await;
23237    cx.set_head_text(indoc! { "
23238        one
23239        two
23240        three
23241        four
23242        five
23243        "
23244    });
23245    cx.set_index_text(indoc! { "
23246        one
23247        two
23248        three
23249        four
23250        five
23251        "
23252    });
23253    cx.set_state(indoc! {"
23254        one
23255        TWO
23256        ˇTHREE
23257        FOUR
23258        five
23259    "});
23260    cx.run_until_parked();
23261    cx.update_editor(|editor, window, cx| {
23262        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23263    });
23264    cx.run_until_parked();
23265    cx.assert_index_text(Some(indoc! {"
23266        one
23267        TWO
23268        THREE
23269        FOUR
23270        five
23271    "}));
23272    cx.set_state(indoc! { "
23273        one
23274        TWO
23275        ˇTHREE-HUNDRED
23276        FOUR
23277        five
23278    "});
23279    cx.run_until_parked();
23280    cx.update_editor(|editor, window, cx| {
23281        let snapshot = editor.snapshot(window, cx);
23282        let hunks = editor
23283            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23284            .collect::<Vec<_>>();
23285        assert_eq!(hunks.len(), 1);
23286        assert_eq!(
23287            hunks[0].status(),
23288            DiffHunkStatus {
23289                kind: DiffHunkStatusKind::Modified,
23290                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
23291            }
23292        );
23293
23294        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23295    });
23296    cx.run_until_parked();
23297    cx.assert_index_text(Some(indoc! {"
23298        one
23299        TWO
23300        THREE-HUNDRED
23301        FOUR
23302        five
23303    "}));
23304}
23305
23306#[gpui::test]
23307fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
23308    init_test(cx, |_| {});
23309
23310    let editor = cx.add_window(|window, cx| {
23311        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
23312        build_editor(buffer, window, cx)
23313    });
23314
23315    let render_args = Arc::new(Mutex::new(None));
23316    let snapshot = editor
23317        .update(cx, |editor, window, cx| {
23318            let snapshot = editor.buffer().read(cx).snapshot(cx);
23319            let range =
23320                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
23321
23322            struct RenderArgs {
23323                row: MultiBufferRow,
23324                folded: bool,
23325                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
23326            }
23327
23328            let crease = Crease::inline(
23329                range,
23330                FoldPlaceholder::test(),
23331                {
23332                    let toggle_callback = render_args.clone();
23333                    move |row, folded, callback, _window, _cx| {
23334                        *toggle_callback.lock() = Some(RenderArgs {
23335                            row,
23336                            folded,
23337                            callback,
23338                        });
23339                        div()
23340                    }
23341                },
23342                |_row, _folded, _window, _cx| div(),
23343            );
23344
23345            editor.insert_creases(Some(crease), cx);
23346            let snapshot = editor.snapshot(window, cx);
23347            let _div =
23348                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
23349            snapshot
23350        })
23351        .unwrap();
23352
23353    let render_args = render_args.lock().take().unwrap();
23354    assert_eq!(render_args.row, MultiBufferRow(1));
23355    assert!(!render_args.folded);
23356    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23357
23358    cx.update_window(*editor, |_, window, cx| {
23359        (render_args.callback)(true, window, cx)
23360    })
23361    .unwrap();
23362    let snapshot = editor
23363        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23364        .unwrap();
23365    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
23366
23367    cx.update_window(*editor, |_, window, cx| {
23368        (render_args.callback)(false, window, cx)
23369    })
23370    .unwrap();
23371    let snapshot = editor
23372        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23373        .unwrap();
23374    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
23375}
23376
23377#[gpui::test]
23378async fn test_input_text(cx: &mut TestAppContext) {
23379    init_test(cx, |_| {});
23380    let mut cx = EditorTestContext::new(cx).await;
23381
23382    cx.set_state(
23383        &r#"ˇone
23384        two
23385
23386        three
23387        fourˇ
23388        five
23389
23390        siˇx"#
23391            .unindent(),
23392    );
23393
23394    cx.dispatch_action(HandleInput(String::new()));
23395    cx.assert_editor_state(
23396        &r#"ˇone
23397        two
23398
23399        three
23400        fourˇ
23401        five
23402
23403        siˇx"#
23404            .unindent(),
23405    );
23406
23407    cx.dispatch_action(HandleInput("AAAA".to_string()));
23408    cx.assert_editor_state(
23409        &r#"AAAAˇone
23410        two
23411
23412        three
23413        fourAAAAˇ
23414        five
23415
23416        siAAAAˇx"#
23417            .unindent(),
23418    );
23419}
23420
23421#[gpui::test]
23422async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
23423    init_test(cx, |_| {});
23424
23425    let mut cx = EditorTestContext::new(cx).await;
23426    cx.set_state(
23427        r#"let foo = 1;
23428let foo = 2;
23429let foo = 3;
23430let fooˇ = 4;
23431let foo = 5;
23432let foo = 6;
23433let foo = 7;
23434let foo = 8;
23435let foo = 9;
23436let foo = 10;
23437let foo = 11;
23438let foo = 12;
23439let foo = 13;
23440let foo = 14;
23441let foo = 15;"#,
23442    );
23443
23444    cx.update_editor(|e, window, cx| {
23445        assert_eq!(
23446            e.next_scroll_position,
23447            NextScrollCursorCenterTopBottom::Center,
23448            "Default next scroll direction is center",
23449        );
23450
23451        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23452        assert_eq!(
23453            e.next_scroll_position,
23454            NextScrollCursorCenterTopBottom::Top,
23455            "After center, next scroll direction should be top",
23456        );
23457
23458        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23459        assert_eq!(
23460            e.next_scroll_position,
23461            NextScrollCursorCenterTopBottom::Bottom,
23462            "After top, next scroll direction should be bottom",
23463        );
23464
23465        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23466        assert_eq!(
23467            e.next_scroll_position,
23468            NextScrollCursorCenterTopBottom::Center,
23469            "After bottom, scrolling should start over",
23470        );
23471
23472        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
23473        assert_eq!(
23474            e.next_scroll_position,
23475            NextScrollCursorCenterTopBottom::Top,
23476            "Scrolling continues if retriggered fast enough"
23477        );
23478    });
23479
23480    cx.executor()
23481        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
23482    cx.executor().run_until_parked();
23483    cx.update_editor(|e, _, _| {
23484        assert_eq!(
23485            e.next_scroll_position,
23486            NextScrollCursorCenterTopBottom::Center,
23487            "If scrolling is not triggered fast enough, it should reset"
23488        );
23489    });
23490}
23491
23492#[gpui::test]
23493async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
23494    init_test(cx, |_| {});
23495    let mut cx = EditorLspTestContext::new_rust(
23496        lsp::ServerCapabilities {
23497            definition_provider: Some(lsp::OneOf::Left(true)),
23498            references_provider: Some(lsp::OneOf::Left(true)),
23499            ..lsp::ServerCapabilities::default()
23500        },
23501        cx,
23502    )
23503    .await;
23504
23505    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
23506        let go_to_definition = cx
23507            .lsp
23508            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23509                move |params, _| async move {
23510                    if empty_go_to_definition {
23511                        Ok(None)
23512                    } else {
23513                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
23514                            uri: params.text_document_position_params.text_document.uri,
23515                            range: lsp::Range::new(
23516                                lsp::Position::new(4, 3),
23517                                lsp::Position::new(4, 6),
23518                            ),
23519                        })))
23520                    }
23521                },
23522            );
23523        let references = cx
23524            .lsp
23525            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23526                Ok(Some(vec![lsp::Location {
23527                    uri: params.text_document_position.text_document.uri,
23528                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
23529                }]))
23530            });
23531        (go_to_definition, references)
23532    };
23533
23534    cx.set_state(
23535        &r#"fn one() {
23536            let mut a = ˇtwo();
23537        }
23538
23539        fn two() {}"#
23540            .unindent(),
23541    );
23542    set_up_lsp_handlers(false, &mut cx);
23543    let navigated = cx
23544        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23545        .await
23546        .expect("Failed to navigate to definition");
23547    assert_eq!(
23548        navigated,
23549        Navigated::Yes,
23550        "Should have navigated to definition from the GetDefinition response"
23551    );
23552    cx.assert_editor_state(
23553        &r#"fn one() {
23554            let mut a = two();
23555        }
23556
23557        fn «twoˇ»() {}"#
23558            .unindent(),
23559    );
23560
23561    let editors = cx.update_workspace(|workspace, _, cx| {
23562        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23563    });
23564    cx.update_editor(|_, _, test_editor_cx| {
23565        assert_eq!(
23566            editors.len(),
23567            1,
23568            "Initially, only one, test, editor should be open in the workspace"
23569        );
23570        assert_eq!(
23571            test_editor_cx.entity(),
23572            editors.last().expect("Asserted len is 1").clone()
23573        );
23574    });
23575
23576    set_up_lsp_handlers(true, &mut cx);
23577    let navigated = cx
23578        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23579        .await
23580        .expect("Failed to navigate to lookup references");
23581    assert_eq!(
23582        navigated,
23583        Navigated::Yes,
23584        "Should have navigated to references as a fallback after empty GoToDefinition response"
23585    );
23586    // We should not change the selections in the existing file,
23587    // if opening another milti buffer with the references
23588    cx.assert_editor_state(
23589        &r#"fn one() {
23590            let mut a = two();
23591        }
23592
23593        fn «twoˇ»() {}"#
23594            .unindent(),
23595    );
23596    let editors = cx.update_workspace(|workspace, _, cx| {
23597        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23598    });
23599    cx.update_editor(|_, _, test_editor_cx| {
23600        assert_eq!(
23601            editors.len(),
23602            2,
23603            "After falling back to references search, we open a new editor with the results"
23604        );
23605        let references_fallback_text = editors
23606            .into_iter()
23607            .find(|new_editor| *new_editor != test_editor_cx.entity())
23608            .expect("Should have one non-test editor now")
23609            .read(test_editor_cx)
23610            .text(test_editor_cx);
23611        assert_eq!(
23612            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23613            "Should use the range from the references response and not the GoToDefinition one"
23614        );
23615    });
23616}
23617
23618#[gpui::test]
23619async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23620    init_test(cx, |_| {});
23621    cx.update(|cx| {
23622        let mut editor_settings = EditorSettings::get_global(cx).clone();
23623        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23624        EditorSettings::override_global(editor_settings, cx);
23625    });
23626    let mut cx = EditorLspTestContext::new_rust(
23627        lsp::ServerCapabilities {
23628            definition_provider: Some(lsp::OneOf::Left(true)),
23629            references_provider: Some(lsp::OneOf::Left(true)),
23630            ..lsp::ServerCapabilities::default()
23631        },
23632        cx,
23633    )
23634    .await;
23635    let original_state = r#"fn one() {
23636        let mut a = ˇtwo();
23637    }
23638
23639    fn two() {}"#
23640        .unindent();
23641    cx.set_state(&original_state);
23642
23643    let mut go_to_definition = cx
23644        .lsp
23645        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23646            move |_, _| async move { Ok(None) },
23647        );
23648    let _references = cx
23649        .lsp
23650        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23651            panic!("Should not call for references with no go to definition fallback")
23652        });
23653
23654    let navigated = cx
23655        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23656        .await
23657        .expect("Failed to navigate to lookup references");
23658    go_to_definition
23659        .next()
23660        .await
23661        .expect("Should have called the go_to_definition handler");
23662
23663    assert_eq!(
23664        navigated,
23665        Navigated::No,
23666        "Should have navigated to references as a fallback after empty GoToDefinition response"
23667    );
23668    cx.assert_editor_state(&original_state);
23669    let editors = cx.update_workspace(|workspace, _, cx| {
23670        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23671    });
23672    cx.update_editor(|_, _, _| {
23673        assert_eq!(
23674            editors.len(),
23675            1,
23676            "After unsuccessful fallback, no other editor should have been opened"
23677        );
23678    });
23679}
23680
23681#[gpui::test]
23682async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23683    init_test(cx, |_| {});
23684    let mut cx = EditorLspTestContext::new_rust(
23685        lsp::ServerCapabilities {
23686            references_provider: Some(lsp::OneOf::Left(true)),
23687            ..lsp::ServerCapabilities::default()
23688        },
23689        cx,
23690    )
23691    .await;
23692
23693    cx.set_state(
23694        &r#"
23695        fn one() {
23696            let mut a = two();
23697        }
23698
23699        fn ˇtwo() {}"#
23700            .unindent(),
23701    );
23702    cx.lsp
23703        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23704            Ok(Some(vec![
23705                lsp::Location {
23706                    uri: params.text_document_position.text_document.uri.clone(),
23707                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23708                },
23709                lsp::Location {
23710                    uri: params.text_document_position.text_document.uri,
23711                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23712                },
23713            ]))
23714        });
23715    let navigated = cx
23716        .update_editor(|editor, window, cx| {
23717            editor.find_all_references(&FindAllReferences::default(), window, cx)
23718        })
23719        .unwrap()
23720        .await
23721        .expect("Failed to navigate to references");
23722    assert_eq!(
23723        navigated,
23724        Navigated::Yes,
23725        "Should have navigated to references from the FindAllReferences response"
23726    );
23727    cx.assert_editor_state(
23728        &r#"fn one() {
23729            let mut a = two();
23730        }
23731
23732        fn ˇtwo() {}"#
23733            .unindent(),
23734    );
23735
23736    let editors = cx.update_workspace(|workspace, _, cx| {
23737        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23738    });
23739    cx.update_editor(|_, _, _| {
23740        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23741    });
23742
23743    cx.set_state(
23744        &r#"fn one() {
23745            let mut a = ˇtwo();
23746        }
23747
23748        fn two() {}"#
23749            .unindent(),
23750    );
23751    let navigated = cx
23752        .update_editor(|editor, window, cx| {
23753            editor.find_all_references(&FindAllReferences::default(), window, cx)
23754        })
23755        .unwrap()
23756        .await
23757        .expect("Failed to navigate to references");
23758    assert_eq!(
23759        navigated,
23760        Navigated::Yes,
23761        "Should have navigated to references from the FindAllReferences response"
23762    );
23763    cx.assert_editor_state(
23764        &r#"fn one() {
23765            let mut a = ˇtwo();
23766        }
23767
23768        fn two() {}"#
23769            .unindent(),
23770    );
23771    let editors = cx.update_workspace(|workspace, _, cx| {
23772        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23773    });
23774    cx.update_editor(|_, _, _| {
23775        assert_eq!(
23776            editors.len(),
23777            2,
23778            "should have re-used the previous multibuffer"
23779        );
23780    });
23781
23782    cx.set_state(
23783        &r#"fn one() {
23784            let mut a = ˇtwo();
23785        }
23786        fn three() {}
23787        fn two() {}"#
23788            .unindent(),
23789    );
23790    cx.lsp
23791        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23792            Ok(Some(vec![
23793                lsp::Location {
23794                    uri: params.text_document_position.text_document.uri.clone(),
23795                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23796                },
23797                lsp::Location {
23798                    uri: params.text_document_position.text_document.uri,
23799                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23800                },
23801            ]))
23802        });
23803    let navigated = cx
23804        .update_editor(|editor, window, cx| {
23805            editor.find_all_references(&FindAllReferences::default(), window, cx)
23806        })
23807        .unwrap()
23808        .await
23809        .expect("Failed to navigate to references");
23810    assert_eq!(
23811        navigated,
23812        Navigated::Yes,
23813        "Should have navigated to references from the FindAllReferences response"
23814    );
23815    cx.assert_editor_state(
23816        &r#"fn one() {
23817                let mut a = ˇtwo();
23818            }
23819            fn three() {}
23820            fn two() {}"#
23821            .unindent(),
23822    );
23823    let editors = cx.update_workspace(|workspace, _, cx| {
23824        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23825    });
23826    cx.update_editor(|_, _, _| {
23827        assert_eq!(
23828            editors.len(),
23829            3,
23830            "should have used a new multibuffer as offsets changed"
23831        );
23832    });
23833}
23834#[gpui::test]
23835async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23836    init_test(cx, |_| {});
23837
23838    let language = Arc::new(Language::new(
23839        LanguageConfig::default(),
23840        Some(tree_sitter_rust::LANGUAGE.into()),
23841    ));
23842
23843    let text = r#"
23844        #[cfg(test)]
23845        mod tests() {
23846            #[test]
23847            fn runnable_1() {
23848                let a = 1;
23849            }
23850
23851            #[test]
23852            fn runnable_2() {
23853                let a = 1;
23854                let b = 2;
23855            }
23856        }
23857    "#
23858    .unindent();
23859
23860    let fs = FakeFs::new(cx.executor());
23861    fs.insert_file("/file.rs", Default::default()).await;
23862
23863    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23864    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
23865    let cx = &mut VisualTestContext::from_window(*window, cx);
23866    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23867    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23868
23869    let editor = cx.new_window_entity(|window, cx| {
23870        Editor::new(
23871            EditorMode::full(),
23872            multi_buffer,
23873            Some(project.clone()),
23874            window,
23875            cx,
23876        )
23877    });
23878
23879    editor.update_in(cx, |editor, window, cx| {
23880        let snapshot = editor.buffer().read(cx).snapshot(cx);
23881        editor.tasks.insert(
23882            (buffer.read(cx).remote_id(), 3),
23883            RunnableTasks {
23884                templates: vec![],
23885                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23886                column: 0,
23887                extra_variables: HashMap::default(),
23888                context_range: BufferOffset(43)..BufferOffset(85),
23889            },
23890        );
23891        editor.tasks.insert(
23892            (buffer.read(cx).remote_id(), 8),
23893            RunnableTasks {
23894                templates: vec![],
23895                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23896                column: 0,
23897                extra_variables: HashMap::default(),
23898                context_range: BufferOffset(86)..BufferOffset(191),
23899            },
23900        );
23901
23902        // Test finding task when cursor is inside function body
23903        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23904            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23905        });
23906        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23907        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23908
23909        // Test finding task when cursor is on function name
23910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23911            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23912        });
23913        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23914        assert_eq!(row, 8, "Should find task when cursor is on function name");
23915    });
23916}
23917
23918#[gpui::test]
23919async fn test_folding_buffers(cx: &mut TestAppContext) {
23920    init_test(cx, |_| {});
23921
23922    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23923    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23924    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23925
23926    let fs = FakeFs::new(cx.executor());
23927    fs.insert_tree(
23928        path!("/a"),
23929        json!({
23930            "first.rs": sample_text_1,
23931            "second.rs": sample_text_2,
23932            "third.rs": sample_text_3,
23933        }),
23934    )
23935    .await;
23936    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23937    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
23938    let cx = &mut VisualTestContext::from_window(*window, cx);
23939    let worktree = project.update(cx, |project, cx| {
23940        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23941        assert_eq!(worktrees.len(), 1);
23942        worktrees.pop().unwrap()
23943    });
23944    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23945
23946    let buffer_1 = project
23947        .update(cx, |project, cx| {
23948            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23949        })
23950        .await
23951        .unwrap();
23952    let buffer_2 = project
23953        .update(cx, |project, cx| {
23954            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23955        })
23956        .await
23957        .unwrap();
23958    let buffer_3 = project
23959        .update(cx, |project, cx| {
23960            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23961        })
23962        .await
23963        .unwrap();
23964
23965    let multi_buffer = cx.new(|cx| {
23966        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23967        multi_buffer.push_excerpts(
23968            buffer_1.clone(),
23969            [
23970                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23971                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23972                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23973            ],
23974            cx,
23975        );
23976        multi_buffer.push_excerpts(
23977            buffer_2.clone(),
23978            [
23979                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23980                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23981                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23982            ],
23983            cx,
23984        );
23985        multi_buffer.push_excerpts(
23986            buffer_3.clone(),
23987            [
23988                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23989                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23990                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23991            ],
23992            cx,
23993        );
23994        multi_buffer
23995    });
23996    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23997        Editor::new(
23998            EditorMode::full(),
23999            multi_buffer.clone(),
24000            Some(project.clone()),
24001            window,
24002            cx,
24003        )
24004    });
24005
24006    assert_eq!(
24007        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24008        "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
24009    );
24010
24011    multi_buffer_editor.update(cx, |editor, cx| {
24012        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
24013    });
24014    assert_eq!(
24015        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24016        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
24017        "After folding the first buffer, its text should not be displayed"
24018    );
24019
24020    multi_buffer_editor.update(cx, |editor, cx| {
24021        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
24022    });
24023    assert_eq!(
24024        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24025        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
24026        "After folding the second buffer, its text should not be displayed"
24027    );
24028
24029    multi_buffer_editor.update(cx, |editor, cx| {
24030        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
24031    });
24032    assert_eq!(
24033        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24034        "\n\n\n\n\n",
24035        "After folding the third buffer, its text should not be displayed"
24036    );
24037
24038    // Emulate selection inside the fold logic, that should work
24039    multi_buffer_editor.update_in(cx, |editor, window, cx| {
24040        editor
24041            .snapshot(window, cx)
24042            .next_line_boundary(Point::new(0, 4));
24043    });
24044
24045    multi_buffer_editor.update(cx, |editor, cx| {
24046        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
24047    });
24048    assert_eq!(
24049        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24050        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
24051        "After unfolding the second buffer, its text should be displayed"
24052    );
24053
24054    // Typing inside of buffer 1 causes that buffer to be unfolded.
24055    multi_buffer_editor.update_in(cx, |editor, window, cx| {
24056        assert_eq!(
24057            multi_buffer
24058                .read(cx)
24059                .snapshot(cx)
24060                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
24061                .collect::<String>(),
24062            "bbbb"
24063        );
24064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24065            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
24066        });
24067        editor.handle_input("B", window, cx);
24068    });
24069
24070    assert_eq!(
24071        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24072        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
24073        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
24074    );
24075
24076    multi_buffer_editor.update(cx, |editor, cx| {
24077        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
24078    });
24079    assert_eq!(
24080        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24081        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
24082        "After unfolding the all buffers, all original text should be displayed"
24083    );
24084}
24085
24086#[gpui::test]
24087async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
24088    init_test(cx, |_| {});
24089
24090    let sample_text_1 = "1111\n2222\n3333".to_string();
24091    let sample_text_2 = "4444\n5555\n6666".to_string();
24092    let sample_text_3 = "7777\n8888\n9999".to_string();
24093
24094    let fs = FakeFs::new(cx.executor());
24095    fs.insert_tree(
24096        path!("/a"),
24097        json!({
24098            "first.rs": sample_text_1,
24099            "second.rs": sample_text_2,
24100            "third.rs": sample_text_3,
24101        }),
24102    )
24103    .await;
24104    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24105    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24106    let cx = &mut VisualTestContext::from_window(*window, cx);
24107    let worktree = project.update(cx, |project, cx| {
24108        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24109        assert_eq!(worktrees.len(), 1);
24110        worktrees.pop().unwrap()
24111    });
24112    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24113
24114    let buffer_1 = project
24115        .update(cx, |project, cx| {
24116            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
24117        })
24118        .await
24119        .unwrap();
24120    let buffer_2 = project
24121        .update(cx, |project, cx| {
24122            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
24123        })
24124        .await
24125        .unwrap();
24126    let buffer_3 = project
24127        .update(cx, |project, cx| {
24128            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
24129        })
24130        .await
24131        .unwrap();
24132
24133    let multi_buffer = cx.new(|cx| {
24134        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24135        multi_buffer.push_excerpts(
24136            buffer_1.clone(),
24137            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
24138            cx,
24139        );
24140        multi_buffer.push_excerpts(
24141            buffer_2.clone(),
24142            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
24143            cx,
24144        );
24145        multi_buffer.push_excerpts(
24146            buffer_3.clone(),
24147            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
24148            cx,
24149        );
24150        multi_buffer
24151    });
24152
24153    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
24154        Editor::new(
24155            EditorMode::full(),
24156            multi_buffer,
24157            Some(project.clone()),
24158            window,
24159            cx,
24160        )
24161    });
24162
24163    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
24164    assert_eq!(
24165        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24166        full_text,
24167    );
24168
24169    multi_buffer_editor.update(cx, |editor, cx| {
24170        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
24171    });
24172    assert_eq!(
24173        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24174        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
24175        "After folding the first buffer, its text should not be displayed"
24176    );
24177
24178    multi_buffer_editor.update(cx, |editor, cx| {
24179        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
24180    });
24181
24182    assert_eq!(
24183        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24184        "\n\n\n\n\n\n7777\n8888\n9999",
24185        "After folding the second buffer, its text should not be displayed"
24186    );
24187
24188    multi_buffer_editor.update(cx, |editor, cx| {
24189        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
24190    });
24191    assert_eq!(
24192        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24193        "\n\n\n\n\n",
24194        "After folding the third buffer, its text should not be displayed"
24195    );
24196
24197    multi_buffer_editor.update(cx, |editor, cx| {
24198        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
24199    });
24200    assert_eq!(
24201        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24202        "\n\n\n\n4444\n5555\n6666\n\n",
24203        "After unfolding the second buffer, its text should be displayed"
24204    );
24205
24206    multi_buffer_editor.update(cx, |editor, cx| {
24207        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
24208    });
24209    assert_eq!(
24210        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24211        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
24212        "After unfolding the first buffer, its text should be displayed"
24213    );
24214
24215    multi_buffer_editor.update(cx, |editor, cx| {
24216        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
24217    });
24218    assert_eq!(
24219        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24220        full_text,
24221        "After unfolding all buffers, all original text should be displayed"
24222    );
24223}
24224
24225#[gpui::test]
24226async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
24227    init_test(cx, |_| {});
24228
24229    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
24230
24231    let fs = FakeFs::new(cx.executor());
24232    fs.insert_tree(
24233        path!("/a"),
24234        json!({
24235            "main.rs": sample_text,
24236        }),
24237    )
24238    .await;
24239    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24240    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24241    let cx = &mut VisualTestContext::from_window(*window, cx);
24242    let worktree = project.update(cx, |project, cx| {
24243        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24244        assert_eq!(worktrees.len(), 1);
24245        worktrees.pop().unwrap()
24246    });
24247    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24248
24249    let buffer_1 = project
24250        .update(cx, |project, cx| {
24251            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24252        })
24253        .await
24254        .unwrap();
24255
24256    let multi_buffer = cx.new(|cx| {
24257        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24258        multi_buffer.push_excerpts(
24259            buffer_1.clone(),
24260            [ExcerptRange::new(
24261                Point::new(0, 0)
24262                    ..Point::new(
24263                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
24264                        0,
24265                    ),
24266            )],
24267            cx,
24268        );
24269        multi_buffer
24270    });
24271    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
24272        Editor::new(
24273            EditorMode::full(),
24274            multi_buffer,
24275            Some(project.clone()),
24276            window,
24277            cx,
24278        )
24279    });
24280
24281    let selection_range = Point::new(1, 0)..Point::new(2, 0);
24282    multi_buffer_editor.update_in(cx, |editor, window, cx| {
24283        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24284        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
24285        editor.highlight_text(
24286            HighlightKey::Editor,
24287            vec![highlight_range.clone()],
24288            HighlightStyle::color(Hsla::green()),
24289            cx,
24290        );
24291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24292            s.select_ranges(Some(highlight_range))
24293        });
24294    });
24295
24296    let full_text = format!("\n\n{sample_text}");
24297    assert_eq!(
24298        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24299        full_text,
24300    );
24301}
24302
24303#[gpui::test]
24304async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
24305    init_test(cx, |_| {});
24306    cx.update(|cx| {
24307        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
24308            "keymaps/default-linux.json",
24309            cx,
24310        )
24311        .unwrap();
24312        cx.bind_keys(default_key_bindings);
24313    });
24314
24315    let (editor, cx) = cx.add_window_view(|window, cx| {
24316        let multi_buffer = MultiBuffer::build_multi(
24317            [
24318                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
24319                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
24320                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
24321                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
24322            ],
24323            cx,
24324        );
24325        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
24326
24327        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
24328        // fold all but the second buffer, so that we test navigating between two
24329        // adjacent folded buffers, as well as folded buffers at the start and
24330        // end the multibuffer
24331        editor.fold_buffer(buffer_ids[0], cx);
24332        editor.fold_buffer(buffer_ids[2], cx);
24333        editor.fold_buffer(buffer_ids[3], cx);
24334
24335        editor
24336    });
24337    cx.simulate_resize(size(px(1000.), px(1000.)));
24338
24339    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
24340    cx.assert_excerpts_with_selections(indoc! {"
24341        [EXCERPT]
24342        ˇ[FOLDED]
24343        [EXCERPT]
24344        a1
24345        b1
24346        [EXCERPT]
24347        [FOLDED]
24348        [EXCERPT]
24349        [FOLDED]
24350        "
24351    });
24352    cx.simulate_keystroke("down");
24353    cx.assert_excerpts_with_selections(indoc! {"
24354        [EXCERPT]
24355        [FOLDED]
24356        [EXCERPT]
24357        ˇa1
24358        b1
24359        [EXCERPT]
24360        [FOLDED]
24361        [EXCERPT]
24362        [FOLDED]
24363        "
24364    });
24365    cx.simulate_keystroke("down");
24366    cx.assert_excerpts_with_selections(indoc! {"
24367        [EXCERPT]
24368        [FOLDED]
24369        [EXCERPT]
24370        a1
24371        ˇb1
24372        [EXCERPT]
24373        [FOLDED]
24374        [EXCERPT]
24375        [FOLDED]
24376        "
24377    });
24378    cx.simulate_keystroke("down");
24379    cx.assert_excerpts_with_selections(indoc! {"
24380        [EXCERPT]
24381        [FOLDED]
24382        [EXCERPT]
24383        a1
24384        b1
24385        ˇ[EXCERPT]
24386        [FOLDED]
24387        [EXCERPT]
24388        [FOLDED]
24389        "
24390    });
24391    cx.simulate_keystroke("down");
24392    cx.assert_excerpts_with_selections(indoc! {"
24393        [EXCERPT]
24394        [FOLDED]
24395        [EXCERPT]
24396        a1
24397        b1
24398        [EXCERPT]
24399        ˇ[FOLDED]
24400        [EXCERPT]
24401        [FOLDED]
24402        "
24403    });
24404    for _ in 0..5 {
24405        cx.simulate_keystroke("down");
24406        cx.assert_excerpts_with_selections(indoc! {"
24407            [EXCERPT]
24408            [FOLDED]
24409            [EXCERPT]
24410            a1
24411            b1
24412            [EXCERPT]
24413            [FOLDED]
24414            [EXCERPT]
24415            ˇ[FOLDED]
24416            "
24417        });
24418    }
24419
24420    cx.simulate_keystroke("up");
24421    cx.assert_excerpts_with_selections(indoc! {"
24422        [EXCERPT]
24423        [FOLDED]
24424        [EXCERPT]
24425        a1
24426        b1
24427        [EXCERPT]
24428        ˇ[FOLDED]
24429        [EXCERPT]
24430        [FOLDED]
24431        "
24432    });
24433    cx.simulate_keystroke("up");
24434    cx.assert_excerpts_with_selections(indoc! {"
24435        [EXCERPT]
24436        [FOLDED]
24437        [EXCERPT]
24438        a1
24439        b1
24440        ˇ[EXCERPT]
24441        [FOLDED]
24442        [EXCERPT]
24443        [FOLDED]
24444        "
24445    });
24446    cx.simulate_keystroke("up");
24447    cx.assert_excerpts_with_selections(indoc! {"
24448        [EXCERPT]
24449        [FOLDED]
24450        [EXCERPT]
24451        a1
24452        ˇb1
24453        [EXCERPT]
24454        [FOLDED]
24455        [EXCERPT]
24456        [FOLDED]
24457        "
24458    });
24459    cx.simulate_keystroke("up");
24460    cx.assert_excerpts_with_selections(indoc! {"
24461        [EXCERPT]
24462        [FOLDED]
24463        [EXCERPT]
24464        ˇa1
24465        b1
24466        [EXCERPT]
24467        [FOLDED]
24468        [EXCERPT]
24469        [FOLDED]
24470        "
24471    });
24472    for _ in 0..5 {
24473        cx.simulate_keystroke("up");
24474        cx.assert_excerpts_with_selections(indoc! {"
24475            [EXCERPT]
24476            ˇ[FOLDED]
24477            [EXCERPT]
24478            a1
24479            b1
24480            [EXCERPT]
24481            [FOLDED]
24482            [EXCERPT]
24483            [FOLDED]
24484            "
24485        });
24486    }
24487}
24488
24489#[gpui::test]
24490async fn test_edit_prediction_text(cx: &mut TestAppContext) {
24491    init_test(cx, |_| {});
24492
24493    // Simple insertion
24494    assert_highlighted_edits(
24495        "Hello, world!",
24496        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
24497        true,
24498        cx,
24499        |highlighted_edits, cx| {
24500            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
24501            assert_eq!(highlighted_edits.highlights.len(), 1);
24502            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
24503            assert_eq!(
24504                highlighted_edits.highlights[0].1.background_color,
24505                Some(cx.theme().status().created_background)
24506            );
24507        },
24508    )
24509    .await;
24510
24511    // Replacement
24512    assert_highlighted_edits(
24513        "This is a test.",
24514        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
24515        false,
24516        cx,
24517        |highlighted_edits, cx| {
24518            assert_eq!(highlighted_edits.text, "That is a test.");
24519            assert_eq!(highlighted_edits.highlights.len(), 1);
24520            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
24521            assert_eq!(
24522                highlighted_edits.highlights[0].1.background_color,
24523                Some(cx.theme().status().created_background)
24524            );
24525        },
24526    )
24527    .await;
24528
24529    // Multiple edits
24530    assert_highlighted_edits(
24531        "Hello, world!",
24532        vec![
24533            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
24534            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
24535        ],
24536        false,
24537        cx,
24538        |highlighted_edits, cx| {
24539            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
24540            assert_eq!(highlighted_edits.highlights.len(), 2);
24541            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
24542            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
24543            assert_eq!(
24544                highlighted_edits.highlights[0].1.background_color,
24545                Some(cx.theme().status().created_background)
24546            );
24547            assert_eq!(
24548                highlighted_edits.highlights[1].1.background_color,
24549                Some(cx.theme().status().created_background)
24550            );
24551        },
24552    )
24553    .await;
24554
24555    // Multiple lines with edits
24556    assert_highlighted_edits(
24557        "First line\nSecond line\nThird line\nFourth line",
24558        vec![
24559            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24560            (
24561                Point::new(2, 0)..Point::new(2, 10),
24562                "New third line".to_string(),
24563            ),
24564            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24565        ],
24566        false,
24567        cx,
24568        |highlighted_edits, cx| {
24569            assert_eq!(
24570                highlighted_edits.text,
24571                "Second modified\nNew third line\nFourth updated line"
24572            );
24573            assert_eq!(highlighted_edits.highlights.len(), 3);
24574            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24575            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24576            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24577            for highlight in &highlighted_edits.highlights {
24578                assert_eq!(
24579                    highlight.1.background_color,
24580                    Some(cx.theme().status().created_background)
24581                );
24582            }
24583        },
24584    )
24585    .await;
24586}
24587
24588#[gpui::test]
24589async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24590    init_test(cx, |_| {});
24591
24592    // Deletion
24593    assert_highlighted_edits(
24594        "Hello, world!",
24595        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24596        true,
24597        cx,
24598        |highlighted_edits, cx| {
24599            assert_eq!(highlighted_edits.text, "Hello, world!");
24600            assert_eq!(highlighted_edits.highlights.len(), 1);
24601            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24602            assert_eq!(
24603                highlighted_edits.highlights[0].1.background_color,
24604                Some(cx.theme().status().deleted_background)
24605            );
24606        },
24607    )
24608    .await;
24609
24610    // Insertion
24611    assert_highlighted_edits(
24612        "Hello, world!",
24613        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24614        true,
24615        cx,
24616        |highlighted_edits, cx| {
24617            assert_eq!(highlighted_edits.highlights.len(), 1);
24618            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24619            assert_eq!(
24620                highlighted_edits.highlights[0].1.background_color,
24621                Some(cx.theme().status().created_background)
24622            );
24623        },
24624    )
24625    .await;
24626}
24627
24628async fn assert_highlighted_edits(
24629    text: &str,
24630    edits: Vec<(Range<Point>, String)>,
24631    include_deletions: bool,
24632    cx: &mut TestAppContext,
24633    assertion_fn: impl Fn(HighlightedText, &App),
24634) {
24635    let window = cx.add_window(|window, cx| {
24636        let buffer = MultiBuffer::build_simple(text, cx);
24637        Editor::new(EditorMode::full(), buffer, None, window, cx)
24638    });
24639    let cx = &mut VisualTestContext::from_window(*window, cx);
24640
24641    let (buffer, snapshot) = window
24642        .update(cx, |editor, _window, cx| {
24643            (
24644                editor.buffer().clone(),
24645                editor.buffer().read(cx).snapshot(cx),
24646            )
24647        })
24648        .unwrap();
24649
24650    let edits = edits
24651        .into_iter()
24652        .map(|(range, edit)| {
24653            (
24654                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24655                edit,
24656            )
24657        })
24658        .collect::<Vec<_>>();
24659
24660    let text_anchor_edits = edits
24661        .clone()
24662        .into_iter()
24663        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24664        .collect::<Vec<_>>();
24665
24666    let edit_preview = window
24667        .update(cx, |_, _window, cx| {
24668            buffer
24669                .read(cx)
24670                .as_singleton()
24671                .unwrap()
24672                .read(cx)
24673                .preview_edits(text_anchor_edits.into(), cx)
24674        })
24675        .unwrap()
24676        .await;
24677
24678    cx.update(|_window, cx| {
24679        let highlighted_edits = edit_prediction_edit_text(
24680            snapshot.as_singleton().unwrap().2,
24681            &edits,
24682            &edit_preview,
24683            include_deletions,
24684            cx,
24685        );
24686        assertion_fn(highlighted_edits, cx)
24687    });
24688}
24689
24690#[track_caller]
24691fn assert_breakpoint(
24692    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24693    path: &Arc<Path>,
24694    expected: Vec<(u32, Breakpoint)>,
24695) {
24696    if expected.is_empty() {
24697        assert!(!breakpoints.contains_key(path), "{}", path.display());
24698    } else {
24699        let mut breakpoint = breakpoints
24700            .get(path)
24701            .unwrap()
24702            .iter()
24703            .map(|breakpoint| {
24704                (
24705                    breakpoint.row,
24706                    Breakpoint {
24707                        message: breakpoint.message.clone(),
24708                        state: breakpoint.state,
24709                        condition: breakpoint.condition.clone(),
24710                        hit_condition: breakpoint.hit_condition.clone(),
24711                    },
24712                )
24713            })
24714            .collect::<Vec<_>>();
24715
24716        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24717
24718        assert_eq!(expected, breakpoint);
24719    }
24720}
24721
24722fn add_log_breakpoint_at_cursor(
24723    editor: &mut Editor,
24724    log_message: &str,
24725    window: &mut Window,
24726    cx: &mut Context<Editor>,
24727) {
24728    let (anchor, bp) = editor
24729        .breakpoints_at_cursors(window, cx)
24730        .first()
24731        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24732        .unwrap_or_else(|| {
24733            let snapshot = editor.snapshot(window, cx);
24734            let cursor_position: Point =
24735                editor.selections.newest(&snapshot.display_snapshot).head();
24736
24737            let breakpoint_position = snapshot
24738                .buffer_snapshot()
24739                .anchor_before(Point::new(cursor_position.row, 0));
24740
24741            (breakpoint_position, Breakpoint::new_log(log_message))
24742        });
24743
24744    editor.edit_breakpoint_at_anchor(
24745        anchor,
24746        bp,
24747        BreakpointEditAction::EditLogMessage(log_message.into()),
24748        cx,
24749    );
24750}
24751
24752#[gpui::test]
24753async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24754    init_test(cx, |_| {});
24755
24756    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24757    let fs = FakeFs::new(cx.executor());
24758    fs.insert_tree(
24759        path!("/a"),
24760        json!({
24761            "main.rs": sample_text,
24762        }),
24763    )
24764    .await;
24765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24766    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24767    let cx = &mut VisualTestContext::from_window(*window, cx);
24768
24769    let fs = FakeFs::new(cx.executor());
24770    fs.insert_tree(
24771        path!("/a"),
24772        json!({
24773            "main.rs": sample_text,
24774        }),
24775    )
24776    .await;
24777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24778    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24779    let workspace = window
24780        .read_with(cx, |mw, _| mw.workspace().clone())
24781        .unwrap();
24782    let cx = &mut VisualTestContext::from_window(*window, cx);
24783    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
24784        workspace.project().update(cx, |project, cx| {
24785            project.worktrees(cx).next().unwrap().read(cx).id()
24786        })
24787    });
24788
24789    let buffer = project
24790        .update(cx, |project, cx| {
24791            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24792        })
24793        .await
24794        .unwrap();
24795
24796    let (editor, cx) = cx.add_window_view(|window, cx| {
24797        Editor::new(
24798            EditorMode::full(),
24799            MultiBuffer::build_from_buffer(buffer, cx),
24800            Some(project.clone()),
24801            window,
24802            cx,
24803        )
24804    });
24805
24806    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24807    let abs_path = project.read_with(cx, |project, cx| {
24808        project
24809            .absolute_path(&project_path, cx)
24810            .map(Arc::from)
24811            .unwrap()
24812    });
24813
24814    // assert we can add breakpoint on the first line
24815    editor.update_in(cx, |editor, window, cx| {
24816        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24817        editor.move_to_end(&MoveToEnd, window, cx);
24818        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24819    });
24820
24821    let breakpoints = editor.update(cx, |editor, cx| {
24822        editor
24823            .breakpoint_store()
24824            .as_ref()
24825            .unwrap()
24826            .read(cx)
24827            .all_source_breakpoints(cx)
24828    });
24829
24830    assert_eq!(1, breakpoints.len());
24831    assert_breakpoint(
24832        &breakpoints,
24833        &abs_path,
24834        vec![
24835            (0, Breakpoint::new_standard()),
24836            (3, Breakpoint::new_standard()),
24837        ],
24838    );
24839
24840    editor.update_in(cx, |editor, window, cx| {
24841        editor.move_to_beginning(&MoveToBeginning, window, cx);
24842        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24843    });
24844
24845    let breakpoints = editor.update(cx, |editor, cx| {
24846        editor
24847            .breakpoint_store()
24848            .as_ref()
24849            .unwrap()
24850            .read(cx)
24851            .all_source_breakpoints(cx)
24852    });
24853
24854    assert_eq!(1, breakpoints.len());
24855    assert_breakpoint(
24856        &breakpoints,
24857        &abs_path,
24858        vec![(3, Breakpoint::new_standard())],
24859    );
24860
24861    editor.update_in(cx, |editor, window, cx| {
24862        editor.move_to_end(&MoveToEnd, window, cx);
24863        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24864    });
24865
24866    let breakpoints = editor.update(cx, |editor, cx| {
24867        editor
24868            .breakpoint_store()
24869            .as_ref()
24870            .unwrap()
24871            .read(cx)
24872            .all_source_breakpoints(cx)
24873    });
24874
24875    assert_eq!(0, breakpoints.len());
24876    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24877}
24878
24879#[gpui::test]
24880async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24881    init_test(cx, |_| {});
24882
24883    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24884
24885    let fs = FakeFs::new(cx.executor());
24886    fs.insert_tree(
24887        path!("/a"),
24888        json!({
24889            "main.rs": sample_text,
24890        }),
24891    )
24892    .await;
24893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24894    let (multi_workspace, cx) =
24895        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24896    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
24897
24898    let worktree_id = workspace.update(cx, |workspace, cx| {
24899        workspace.project().update(cx, |project, cx| {
24900            project.worktrees(cx).next().unwrap().read(cx).id()
24901        })
24902    });
24903
24904    let buffer = project
24905        .update(cx, |project, cx| {
24906            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24907        })
24908        .await
24909        .unwrap();
24910
24911    let (editor, cx) = cx.add_window_view(|window, cx| {
24912        Editor::new(
24913            EditorMode::full(),
24914            MultiBuffer::build_from_buffer(buffer, cx),
24915            Some(project.clone()),
24916            window,
24917            cx,
24918        )
24919    });
24920
24921    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24922    let abs_path = project.read_with(cx, |project, cx| {
24923        project
24924            .absolute_path(&project_path, cx)
24925            .map(Arc::from)
24926            .unwrap()
24927    });
24928
24929    editor.update_in(cx, |editor, window, cx| {
24930        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24931    });
24932
24933    let breakpoints = editor.update(cx, |editor, cx| {
24934        editor
24935            .breakpoint_store()
24936            .as_ref()
24937            .unwrap()
24938            .read(cx)
24939            .all_source_breakpoints(cx)
24940    });
24941
24942    assert_breakpoint(
24943        &breakpoints,
24944        &abs_path,
24945        vec![(0, Breakpoint::new_log("hello world"))],
24946    );
24947
24948    // Removing a log message from a log breakpoint should remove it
24949    editor.update_in(cx, |editor, window, cx| {
24950        add_log_breakpoint_at_cursor(editor, "", window, cx);
24951    });
24952
24953    let breakpoints = editor.update(cx, |editor, cx| {
24954        editor
24955            .breakpoint_store()
24956            .as_ref()
24957            .unwrap()
24958            .read(cx)
24959            .all_source_breakpoints(cx)
24960    });
24961
24962    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24963
24964    editor.update_in(cx, |editor, window, cx| {
24965        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24966        editor.move_to_end(&MoveToEnd, window, cx);
24967        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24968        // Not adding a log message to a standard breakpoint shouldn't remove it
24969        add_log_breakpoint_at_cursor(editor, "", window, cx);
24970    });
24971
24972    let breakpoints = editor.update(cx, |editor, cx| {
24973        editor
24974            .breakpoint_store()
24975            .as_ref()
24976            .unwrap()
24977            .read(cx)
24978            .all_source_breakpoints(cx)
24979    });
24980
24981    assert_breakpoint(
24982        &breakpoints,
24983        &abs_path,
24984        vec![
24985            (0, Breakpoint::new_standard()),
24986            (3, Breakpoint::new_standard()),
24987        ],
24988    );
24989
24990    editor.update_in(cx, |editor, window, cx| {
24991        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24992    });
24993
24994    let breakpoints = editor.update(cx, |editor, cx| {
24995        editor
24996            .breakpoint_store()
24997            .as_ref()
24998            .unwrap()
24999            .read(cx)
25000            .all_source_breakpoints(cx)
25001    });
25002
25003    assert_breakpoint(
25004        &breakpoints,
25005        &abs_path,
25006        vec![
25007            (0, Breakpoint::new_standard()),
25008            (3, Breakpoint::new_log("hello world")),
25009        ],
25010    );
25011
25012    editor.update_in(cx, |editor, window, cx| {
25013        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
25014    });
25015
25016    let breakpoints = editor.update(cx, |editor, cx| {
25017        editor
25018            .breakpoint_store()
25019            .as_ref()
25020            .unwrap()
25021            .read(cx)
25022            .all_source_breakpoints(cx)
25023    });
25024
25025    assert_breakpoint(
25026        &breakpoints,
25027        &abs_path,
25028        vec![
25029            (0, Breakpoint::new_standard()),
25030            (3, Breakpoint::new_log("hello Earth!!")),
25031        ],
25032    );
25033}
25034
25035/// This also tests that Editor::breakpoint_at_cursor_head is working properly
25036/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
25037/// or when breakpoints were placed out of order. This tests for a regression too
25038#[gpui::test]
25039async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
25040    init_test(cx, |_| {});
25041
25042    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25043    let fs = FakeFs::new(cx.executor());
25044    fs.insert_tree(
25045        path!("/a"),
25046        json!({
25047            "main.rs": sample_text,
25048        }),
25049    )
25050    .await;
25051    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25052    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25053    let cx = &mut VisualTestContext::from_window(*window, cx);
25054
25055    let fs = FakeFs::new(cx.executor());
25056    fs.insert_tree(
25057        path!("/a"),
25058        json!({
25059            "main.rs": sample_text,
25060        }),
25061    )
25062    .await;
25063    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25064    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25065    let workspace = window
25066        .read_with(cx, |mw, _| mw.workspace().clone())
25067        .unwrap();
25068    let cx = &mut VisualTestContext::from_window(*window, cx);
25069    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25070        workspace.project().update(cx, |project, cx| {
25071            project.worktrees(cx).next().unwrap().read(cx).id()
25072        })
25073    });
25074
25075    let buffer = project
25076        .update(cx, |project, cx| {
25077            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25078        })
25079        .await
25080        .unwrap();
25081
25082    let (editor, cx) = cx.add_window_view(|window, cx| {
25083        Editor::new(
25084            EditorMode::full(),
25085            MultiBuffer::build_from_buffer(buffer, cx),
25086            Some(project.clone()),
25087            window,
25088            cx,
25089        )
25090    });
25091
25092    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
25093    let abs_path = project.read_with(cx, |project, cx| {
25094        project
25095            .absolute_path(&project_path, cx)
25096            .map(Arc::from)
25097            .unwrap()
25098    });
25099
25100    // assert we can add breakpoint on the first line
25101    editor.update_in(cx, |editor, window, cx| {
25102        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25103        editor.move_to_end(&MoveToEnd, window, cx);
25104        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25105        editor.move_up(&MoveUp, window, cx);
25106        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25107    });
25108
25109    let breakpoints = editor.update(cx, |editor, cx| {
25110        editor
25111            .breakpoint_store()
25112            .as_ref()
25113            .unwrap()
25114            .read(cx)
25115            .all_source_breakpoints(cx)
25116    });
25117
25118    assert_eq!(1, breakpoints.len());
25119    assert_breakpoint(
25120        &breakpoints,
25121        &abs_path,
25122        vec![
25123            (0, Breakpoint::new_standard()),
25124            (2, Breakpoint::new_standard()),
25125            (3, Breakpoint::new_standard()),
25126        ],
25127    );
25128
25129    editor.update_in(cx, |editor, window, cx| {
25130        editor.move_to_beginning(&MoveToBeginning, window, cx);
25131        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25132        editor.move_to_end(&MoveToEnd, window, cx);
25133        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25134        // Disabling a breakpoint that doesn't exist should do nothing
25135        editor.move_up(&MoveUp, window, cx);
25136        editor.move_up(&MoveUp, window, cx);
25137        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25138    });
25139
25140    let breakpoints = editor.update(cx, |editor, cx| {
25141        editor
25142            .breakpoint_store()
25143            .as_ref()
25144            .unwrap()
25145            .read(cx)
25146            .all_source_breakpoints(cx)
25147    });
25148
25149    let disable_breakpoint = {
25150        let mut bp = Breakpoint::new_standard();
25151        bp.state = BreakpointState::Disabled;
25152        bp
25153    };
25154
25155    assert_eq!(1, breakpoints.len());
25156    assert_breakpoint(
25157        &breakpoints,
25158        &abs_path,
25159        vec![
25160            (0, disable_breakpoint.clone()),
25161            (2, Breakpoint::new_standard()),
25162            (3, disable_breakpoint.clone()),
25163        ],
25164    );
25165
25166    editor.update_in(cx, |editor, window, cx| {
25167        editor.move_to_beginning(&MoveToBeginning, window, cx);
25168        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
25169        editor.move_to_end(&MoveToEnd, window, cx);
25170        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
25171        editor.move_up(&MoveUp, window, cx);
25172        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25173    });
25174
25175    let breakpoints = editor.update(cx, |editor, cx| {
25176        editor
25177            .breakpoint_store()
25178            .as_ref()
25179            .unwrap()
25180            .read(cx)
25181            .all_source_breakpoints(cx)
25182    });
25183
25184    assert_eq!(1, breakpoints.len());
25185    assert_breakpoint(
25186        &breakpoints,
25187        &abs_path,
25188        vec![
25189            (0, Breakpoint::new_standard()),
25190            (2, disable_breakpoint),
25191            (3, Breakpoint::new_standard()),
25192        ],
25193    );
25194}
25195
25196#[gpui::test]
25197async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
25198    init_test(cx, |_| {});
25199
25200    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25201    let fs = FakeFs::new(cx.executor());
25202    fs.insert_tree(
25203        path!("/a"),
25204        json!({
25205            "main.rs": sample_text,
25206        }),
25207    )
25208    .await;
25209    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25210    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25211    let workspace = window
25212        .read_with(cx, |mw, _| mw.workspace().clone())
25213        .unwrap();
25214    let cx = &mut VisualTestContext::from_window(*window, cx);
25215    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25216        workspace.project().update(cx, |project, cx| {
25217            project.worktrees(cx).next().unwrap().read(cx).id()
25218        })
25219    });
25220
25221    let buffer = project
25222        .update(cx, |project, cx| {
25223            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25224        })
25225        .await
25226        .unwrap();
25227
25228    let (editor, cx) = cx.add_window_view(|window, cx| {
25229        Editor::new(
25230            EditorMode::full(),
25231            MultiBuffer::build_from_buffer(buffer, cx),
25232            Some(project.clone()),
25233            window,
25234            cx,
25235        )
25236    });
25237
25238    // Simulate hovering over row 0 with no existing breakpoint.
25239    editor.update(cx, |editor, _cx| {
25240        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
25241            display_row: DisplayRow(0),
25242            is_active: true,
25243            collides_with_existing_breakpoint: false,
25244        });
25245    });
25246
25247    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
25248    editor.update_in(cx, |editor, window, cx| {
25249        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25250    });
25251    editor.update(cx, |editor, _cx| {
25252        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
25253        assert!(
25254            indicator.collides_with_existing_breakpoint,
25255            "Adding a breakpoint on the hovered row should set collision to true"
25256        );
25257    });
25258
25259    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
25260    editor.update_in(cx, |editor, window, cx| {
25261        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25262    });
25263    editor.update(cx, |editor, _cx| {
25264        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
25265        assert!(
25266            !indicator.collides_with_existing_breakpoint,
25267            "Removing a breakpoint on the hovered row should set collision to false"
25268        );
25269    });
25270
25271    // Now move cursor to row 2 while phantom indicator stays on row 0.
25272    editor.update_in(cx, |editor, window, cx| {
25273        editor.move_down(&MoveDown, window, cx);
25274        editor.move_down(&MoveDown, window, cx);
25275    });
25276
25277    // Ensure phantom indicator is still on row 0, not colliding.
25278    editor.update(cx, |editor, _cx| {
25279        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
25280            display_row: DisplayRow(0),
25281            is_active: true,
25282            collides_with_existing_breakpoint: false,
25283        });
25284    });
25285
25286    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
25287    editor.update_in(cx, |editor, window, cx| {
25288        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25289    });
25290    editor.update(cx, |editor, _cx| {
25291        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
25292        assert!(
25293            !indicator.collides_with_existing_breakpoint,
25294            "Toggling a breakpoint on a different row should not affect the phantom indicator"
25295        );
25296    });
25297}
25298
25299#[gpui::test]
25300async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
25301    init_test(cx, |_| {});
25302    let capabilities = lsp::ServerCapabilities {
25303        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
25304            prepare_provider: Some(true),
25305            work_done_progress_options: Default::default(),
25306        })),
25307        ..Default::default()
25308    };
25309    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
25310
25311    cx.set_state(indoc! {"
25312        struct Fˇoo {}
25313    "});
25314
25315    cx.update_editor(|editor, _, cx| {
25316        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
25317        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
25318        editor.highlight_background(
25319            HighlightKey::DocumentHighlightRead,
25320            &[highlight_range],
25321            |_, theme| theme.colors().editor_document_highlight_read_background,
25322            cx,
25323        );
25324    });
25325
25326    let mut prepare_rename_handler = cx
25327        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
25328            move |_, _, _| async move {
25329                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
25330                    start: lsp::Position {
25331                        line: 0,
25332                        character: 7,
25333                    },
25334                    end: lsp::Position {
25335                        line: 0,
25336                        character: 10,
25337                    },
25338                })))
25339            },
25340        );
25341    let prepare_rename_task = cx
25342        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
25343        .expect("Prepare rename was not started");
25344    prepare_rename_handler.next().await.unwrap();
25345    prepare_rename_task.await.expect("Prepare rename failed");
25346
25347    let mut rename_handler =
25348        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
25349            let edit = lsp::TextEdit {
25350                range: lsp::Range {
25351                    start: lsp::Position {
25352                        line: 0,
25353                        character: 7,
25354                    },
25355                    end: lsp::Position {
25356                        line: 0,
25357                        character: 10,
25358                    },
25359                },
25360                new_text: "FooRenamed".to_string(),
25361            };
25362            Ok(Some(lsp::WorkspaceEdit::new(
25363                // Specify the same edit twice
25364                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
25365            )))
25366        });
25367    let rename_task = cx
25368        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25369        .expect("Confirm rename was not started");
25370    rename_handler.next().await.unwrap();
25371    rename_task.await.expect("Confirm rename failed");
25372    cx.run_until_parked();
25373
25374    // Despite two edits, only one is actually applied as those are identical
25375    cx.assert_editor_state(indoc! {"
25376        struct FooRenamedˇ {}
25377    "});
25378}
25379
25380#[gpui::test]
25381async fn test_rename_without_prepare(cx: &mut TestAppContext) {
25382    init_test(cx, |_| {});
25383    // These capabilities indicate that the server does not support prepare rename.
25384    let capabilities = lsp::ServerCapabilities {
25385        rename_provider: Some(lsp::OneOf::Left(true)),
25386        ..Default::default()
25387    };
25388    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
25389
25390    cx.set_state(indoc! {"
25391        struct Fˇoo {}
25392    "});
25393
25394    cx.update_editor(|editor, _window, cx| {
25395        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
25396        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
25397        editor.highlight_background(
25398            HighlightKey::DocumentHighlightRead,
25399            &[highlight_range],
25400            |_, theme| theme.colors().editor_document_highlight_read_background,
25401            cx,
25402        );
25403    });
25404
25405    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
25406        .expect("Prepare rename was not started")
25407        .await
25408        .expect("Prepare rename failed");
25409
25410    let mut rename_handler =
25411        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
25412            let edit = lsp::TextEdit {
25413                range: lsp::Range {
25414                    start: lsp::Position {
25415                        line: 0,
25416                        character: 7,
25417                    },
25418                    end: lsp::Position {
25419                        line: 0,
25420                        character: 10,
25421                    },
25422                },
25423                new_text: "FooRenamed".to_string(),
25424            };
25425            Ok(Some(lsp::WorkspaceEdit::new(
25426                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
25427            )))
25428        });
25429    let rename_task = cx
25430        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
25431        .expect("Confirm rename was not started");
25432    rename_handler.next().await.unwrap();
25433    rename_task.await.expect("Confirm rename failed");
25434    cx.run_until_parked();
25435
25436    // Correct range is renamed, as `surrounding_word` is used to find it.
25437    cx.assert_editor_state(indoc! {"
25438        struct FooRenamedˇ {}
25439    "});
25440}
25441
25442#[gpui::test]
25443async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
25444    init_test(cx, |_| {});
25445    let mut cx = EditorTestContext::new(cx).await;
25446
25447    let language = Arc::new(
25448        Language::new(
25449            LanguageConfig::default(),
25450            Some(tree_sitter_html::LANGUAGE.into()),
25451        )
25452        .with_brackets_query(
25453            r#"
25454            ("<" @open "/>" @close)
25455            ("</" @open ">" @close)
25456            ("<" @open ">" @close)
25457            ("\"" @open "\"" @close)
25458            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
25459        "#,
25460        )
25461        .unwrap(),
25462    );
25463    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25464
25465    cx.set_state(indoc! {"
25466        <span>ˇ</span>
25467    "});
25468    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25469    cx.assert_editor_state(indoc! {"
25470        <span>
25471        ˇ
25472        </span>
25473    "});
25474
25475    cx.set_state(indoc! {"
25476        <span><span></span>ˇ</span>
25477    "});
25478    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25479    cx.assert_editor_state(indoc! {"
25480        <span><span></span>
25481        ˇ</span>
25482    "});
25483
25484    cx.set_state(indoc! {"
25485        <span>ˇ
25486        </span>
25487    "});
25488    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
25489    cx.assert_editor_state(indoc! {"
25490        <span>
25491        ˇ
25492        </span>
25493    "});
25494}
25495
25496#[gpui::test(iterations = 10)]
25497async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
25498    init_test(cx, |_| {});
25499
25500    let fs = FakeFs::new(cx.executor());
25501    fs.insert_tree(
25502        path!("/dir"),
25503        json!({
25504            "a.ts": "a",
25505        }),
25506    )
25507    .await;
25508
25509    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
25510    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25511    let workspace = window
25512        .read_with(cx, |mw, _| mw.workspace().clone())
25513        .unwrap();
25514    let cx = &mut VisualTestContext::from_window(*window, cx);
25515
25516    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25517    language_registry.add(Arc::new(Language::new(
25518        LanguageConfig {
25519            name: "TypeScript".into(),
25520            matcher: LanguageMatcher {
25521                path_suffixes: vec!["ts".to_string()],
25522                ..Default::default()
25523            },
25524            ..Default::default()
25525        },
25526        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
25527    )));
25528    let mut fake_language_servers = language_registry.register_fake_lsp(
25529        "TypeScript",
25530        FakeLspAdapter {
25531            capabilities: lsp::ServerCapabilities {
25532                code_lens_provider: Some(lsp::CodeLensOptions {
25533                    resolve_provider: Some(true),
25534                }),
25535                execute_command_provider: Some(lsp::ExecuteCommandOptions {
25536                    commands: vec!["_the/command".to_string()],
25537                    ..lsp::ExecuteCommandOptions::default()
25538                }),
25539                ..lsp::ServerCapabilities::default()
25540            },
25541            ..FakeLspAdapter::default()
25542        },
25543    );
25544
25545    let editor = workspace
25546        .update_in(cx, |workspace, window, cx| {
25547            workspace.open_abs_path(
25548                PathBuf::from(path!("/dir/a.ts")),
25549                OpenOptions::default(),
25550                window,
25551                cx,
25552            )
25553        })
25554        .await
25555        .unwrap()
25556        .downcast::<Editor>()
25557        .unwrap();
25558    cx.executor().run_until_parked();
25559
25560    let fake_server = fake_language_servers.next().await.unwrap();
25561
25562    let buffer = editor.update(cx, |editor, cx| {
25563        editor
25564            .buffer()
25565            .read(cx)
25566            .as_singleton()
25567            .expect("have opened a single file by path")
25568    });
25569
25570    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
25571    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
25572    drop(buffer_snapshot);
25573    let actions = cx
25574        .update_window(*window, |_, window, cx| {
25575            project.code_actions(&buffer, anchor..anchor, window, cx)
25576        })
25577        .unwrap();
25578
25579    fake_server
25580        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25581            Ok(Some(vec![
25582                lsp::CodeLens {
25583                    range: lsp::Range::default(),
25584                    command: Some(lsp::Command {
25585                        title: "Code lens command".to_owned(),
25586                        command: "_the/command".to_owned(),
25587                        arguments: None,
25588                    }),
25589                    data: None,
25590                },
25591                lsp::CodeLens {
25592                    range: lsp::Range::default(),
25593                    command: Some(lsp::Command {
25594                        title: "Command not in capabilities".to_owned(),
25595                        command: "not in capabilities".to_owned(),
25596                        arguments: None,
25597                    }),
25598                    data: None,
25599                },
25600                lsp::CodeLens {
25601                    range: lsp::Range {
25602                        start: lsp::Position {
25603                            line: 1,
25604                            character: 1,
25605                        },
25606                        end: lsp::Position {
25607                            line: 1,
25608                            character: 1,
25609                        },
25610                    },
25611                    command: Some(lsp::Command {
25612                        title: "Command not in range".to_owned(),
25613                        command: "_the/command".to_owned(),
25614                        arguments: None,
25615                    }),
25616                    data: None,
25617                },
25618            ]))
25619        })
25620        .next()
25621        .await;
25622
25623    let actions = actions.await.unwrap();
25624    assert_eq!(
25625        actions.len(),
25626        1,
25627        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
25628    );
25629    let action = actions[0].clone();
25630    let apply = project.update(cx, |project, cx| {
25631        project.apply_code_action(buffer.clone(), action, true, cx)
25632    });
25633
25634    // Resolving the code action does not populate its edits. In absence of
25635    // edits, we must execute the given command.
25636    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
25637        |mut lens, _| async move {
25638            let lens_command = lens.command.as_mut().expect("should have a command");
25639            assert_eq!(lens_command.title, "Code lens command");
25640            lens_command.arguments = Some(vec![json!("the-argument")]);
25641            Ok(lens)
25642        },
25643    );
25644
25645    // While executing the command, the language server sends the editor
25646    // a `workspaceEdit` request.
25647    fake_server
25648        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
25649            let fake = fake_server.clone();
25650            move |params, _| {
25651                assert_eq!(params.command, "_the/command");
25652                let fake = fake.clone();
25653                async move {
25654                    fake.server
25655                        .request::<lsp::request::ApplyWorkspaceEdit>(
25656                            lsp::ApplyWorkspaceEditParams {
25657                                label: None,
25658                                edit: lsp::WorkspaceEdit {
25659                                    changes: Some(
25660                                        [(
25661                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25662                                            vec![lsp::TextEdit {
25663                                                range: lsp::Range::new(
25664                                                    lsp::Position::new(0, 0),
25665                                                    lsp::Position::new(0, 0),
25666                                                ),
25667                                                new_text: "X".into(),
25668                                            }],
25669                                        )]
25670                                        .into_iter()
25671                                        .collect(),
25672                                    ),
25673                                    ..lsp::WorkspaceEdit::default()
25674                                },
25675                            },
25676                            DEFAULT_LSP_REQUEST_TIMEOUT,
25677                        )
25678                        .await
25679                        .into_response()
25680                        .unwrap();
25681                    Ok(Some(json!(null)))
25682                }
25683            }
25684        })
25685        .next()
25686        .await;
25687
25688    // Applying the code lens command returns a project transaction containing the edits
25689    // sent by the language server in its `workspaceEdit` request.
25690    let transaction = apply.await.unwrap();
25691    assert!(transaction.0.contains_key(&buffer));
25692    buffer.update(cx, |buffer, cx| {
25693        assert_eq!(buffer.text(), "Xa");
25694        buffer.undo(cx);
25695        assert_eq!(buffer.text(), "a");
25696    });
25697
25698    let actions_after_edits = cx
25699        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
25700        .unwrap()
25701        .await;
25702    assert_eq!(
25703        actions, actions_after_edits,
25704        "For the same selection, same code lens actions should be returned"
25705    );
25706
25707    let _responses =
25708        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25709            panic!("No more code lens requests are expected");
25710        });
25711    editor.update_in(cx, |editor, window, cx| {
25712        editor.select_all(&SelectAll, window, cx);
25713    });
25714    cx.executor().run_until_parked();
25715    let new_actions = cx
25716        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
25717        .unwrap()
25718        .await;
25719    assert_eq!(
25720        actions, new_actions,
25721        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25722    );
25723}
25724
25725#[gpui::test]
25726async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25727    init_test(cx, |_| {});
25728
25729    let fs = FakeFs::new(cx.executor());
25730    let main_text = r#"fn main() {
25731println!("1");
25732println!("2");
25733println!("3");
25734println!("4");
25735println!("5");
25736}"#;
25737    let lib_text = "mod foo {}";
25738    fs.insert_tree(
25739        path!("/a"),
25740        json!({
25741            "lib.rs": lib_text,
25742            "main.rs": main_text,
25743        }),
25744    )
25745    .await;
25746
25747    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25748    let (multi_workspace, cx) =
25749        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25750    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
25751    let worktree_id = workspace.update(cx, |workspace, cx| {
25752        workspace.project().update(cx, |project, cx| {
25753            project.worktrees(cx).next().unwrap().read(cx).id()
25754        })
25755    });
25756
25757    let expected_ranges = vec![
25758        Point::new(0, 0)..Point::new(0, 0),
25759        Point::new(1, 0)..Point::new(1, 1),
25760        Point::new(2, 0)..Point::new(2, 2),
25761        Point::new(3, 0)..Point::new(3, 3),
25762    ];
25763
25764    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25765    let editor_1 = workspace
25766        .update_in(cx, |workspace, window, cx| {
25767            workspace.open_path(
25768                (worktree_id, rel_path("main.rs")),
25769                Some(pane_1.downgrade()),
25770                true,
25771                window,
25772                cx,
25773            )
25774        })
25775        .unwrap()
25776        .await
25777        .downcast::<Editor>()
25778        .unwrap();
25779    pane_1.update(cx, |pane, cx| {
25780        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25781        open_editor.update(cx, |editor, cx| {
25782            assert_eq!(
25783                editor.display_text(cx),
25784                main_text,
25785                "Original main.rs text on initial open",
25786            );
25787            assert_eq!(
25788                editor
25789                    .selections
25790                    .all::<Point>(&editor.display_snapshot(cx))
25791                    .into_iter()
25792                    .map(|s| s.range())
25793                    .collect::<Vec<_>>(),
25794                vec![Point::zero()..Point::zero()],
25795                "Default selections on initial open",
25796            );
25797        })
25798    });
25799    editor_1.update_in(cx, |editor, window, cx| {
25800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25801            s.select_ranges(expected_ranges.clone());
25802        });
25803    });
25804
25805    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25806        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25807    });
25808    let editor_2 = workspace
25809        .update_in(cx, |workspace, window, cx| {
25810            workspace.open_path(
25811                (worktree_id, rel_path("main.rs")),
25812                Some(pane_2.downgrade()),
25813                true,
25814                window,
25815                cx,
25816            )
25817        })
25818        .unwrap()
25819        .await
25820        .downcast::<Editor>()
25821        .unwrap();
25822    pane_2.update(cx, |pane, cx| {
25823        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25824        open_editor.update(cx, |editor, cx| {
25825            assert_eq!(
25826                editor.display_text(cx),
25827                main_text,
25828                "Original main.rs text on initial open in another panel",
25829            );
25830            assert_eq!(
25831                editor
25832                    .selections
25833                    .all::<Point>(&editor.display_snapshot(cx))
25834                    .into_iter()
25835                    .map(|s| s.range())
25836                    .collect::<Vec<_>>(),
25837                vec![Point::zero()..Point::zero()],
25838                "Default selections on initial open in another panel",
25839            );
25840        })
25841    });
25842
25843    editor_2.update_in(cx, |editor, window, cx| {
25844        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25845    });
25846
25847    let _other_editor_1 = workspace
25848        .update_in(cx, |workspace, window, cx| {
25849            workspace.open_path(
25850                (worktree_id, rel_path("lib.rs")),
25851                Some(pane_1.downgrade()),
25852                true,
25853                window,
25854                cx,
25855            )
25856        })
25857        .unwrap()
25858        .await
25859        .downcast::<Editor>()
25860        .unwrap();
25861    pane_1
25862        .update_in(cx, |pane, window, cx| {
25863            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25864        })
25865        .await
25866        .unwrap();
25867    drop(editor_1);
25868    pane_1.update(cx, |pane, cx| {
25869        pane.active_item()
25870            .unwrap()
25871            .downcast::<Editor>()
25872            .unwrap()
25873            .update(cx, |editor, cx| {
25874                assert_eq!(
25875                    editor.display_text(cx),
25876                    lib_text,
25877                    "Other file should be open and active",
25878                );
25879            });
25880        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25881    });
25882
25883    let _other_editor_2 = workspace
25884        .update_in(cx, |workspace, window, cx| {
25885            workspace.open_path(
25886                (worktree_id, rel_path("lib.rs")),
25887                Some(pane_2.downgrade()),
25888                true,
25889                window,
25890                cx,
25891            )
25892        })
25893        .unwrap()
25894        .await
25895        .downcast::<Editor>()
25896        .unwrap();
25897    pane_2
25898        .update_in(cx, |pane, window, cx| {
25899            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25900        })
25901        .await
25902        .unwrap();
25903    drop(editor_2);
25904    pane_2.update(cx, |pane, cx| {
25905        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25906        open_editor.update(cx, |editor, cx| {
25907            assert_eq!(
25908                editor.display_text(cx),
25909                lib_text,
25910                "Other file should be open and active in another panel too",
25911            );
25912        });
25913        assert_eq!(
25914            pane.items().count(),
25915            1,
25916            "No other editors should be open in another pane",
25917        );
25918    });
25919
25920    let _editor_1_reopened = workspace
25921        .update_in(cx, |workspace, window, cx| {
25922            workspace.open_path(
25923                (worktree_id, rel_path("main.rs")),
25924                Some(pane_1.downgrade()),
25925                true,
25926                window,
25927                cx,
25928            )
25929        })
25930        .unwrap()
25931        .await
25932        .downcast::<Editor>()
25933        .unwrap();
25934    let _editor_2_reopened = workspace
25935        .update_in(cx, |workspace, window, cx| {
25936            workspace.open_path(
25937                (worktree_id, rel_path("main.rs")),
25938                Some(pane_2.downgrade()),
25939                true,
25940                window,
25941                cx,
25942            )
25943        })
25944        .unwrap()
25945        .await
25946        .downcast::<Editor>()
25947        .unwrap();
25948    pane_1.update(cx, |pane, cx| {
25949        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25950        open_editor.update(cx, |editor, cx| {
25951            assert_eq!(
25952                editor.display_text(cx),
25953                main_text,
25954                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25955            );
25956            assert_eq!(
25957                editor
25958                    .selections
25959                    .all::<Point>(&editor.display_snapshot(cx))
25960                    .into_iter()
25961                    .map(|s| s.range())
25962                    .collect::<Vec<_>>(),
25963                expected_ranges,
25964                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25965            );
25966        })
25967    });
25968    pane_2.update(cx, |pane, cx| {
25969        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25970        open_editor.update(cx, |editor, cx| {
25971            assert_eq!(
25972                editor.display_text(cx),
25973                r#"fn main() {
25974⋯rintln!("1");
25975⋯intln!("2");
25976⋯ntln!("3");
25977println!("4");
25978println!("5");
25979}"#,
25980                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25981            );
25982            assert_eq!(
25983                editor
25984                    .selections
25985                    .all::<Point>(&editor.display_snapshot(cx))
25986                    .into_iter()
25987                    .map(|s| s.range())
25988                    .collect::<Vec<_>>(),
25989                vec![Point::zero()..Point::zero()],
25990                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25991            );
25992        })
25993    });
25994}
25995
25996#[gpui::test]
25997async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25998    init_test(cx, |_| {});
25999
26000    let fs = FakeFs::new(cx.executor());
26001    let main_text = r#"fn main() {
26002println!("1");
26003println!("2");
26004println!("3");
26005println!("4");
26006println!("5");
26007}"#;
26008    let lib_text = "mod foo {}";
26009    fs.insert_tree(
26010        path!("/a"),
26011        json!({
26012            "lib.rs": lib_text,
26013            "main.rs": main_text,
26014        }),
26015    )
26016    .await;
26017
26018    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26019    let (multi_workspace, cx) =
26020        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26021    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26022    let worktree_id = workspace.update(cx, |workspace, cx| {
26023        workspace.project().update(cx, |project, cx| {
26024            project.worktrees(cx).next().unwrap().read(cx).id()
26025        })
26026    });
26027
26028    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26029    let editor = workspace
26030        .update_in(cx, |workspace, window, cx| {
26031            workspace.open_path(
26032                (worktree_id, rel_path("main.rs")),
26033                Some(pane.downgrade()),
26034                true,
26035                window,
26036                cx,
26037            )
26038        })
26039        .unwrap()
26040        .await
26041        .downcast::<Editor>()
26042        .unwrap();
26043    pane.update(cx, |pane, cx| {
26044        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26045        open_editor.update(cx, |editor, cx| {
26046            assert_eq!(
26047                editor.display_text(cx),
26048                main_text,
26049                "Original main.rs text on initial open",
26050            );
26051        })
26052    });
26053    editor.update_in(cx, |editor, window, cx| {
26054        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
26055    });
26056
26057    cx.update_global(|store: &mut SettingsStore, cx| {
26058        store.update_user_settings(cx, |s| {
26059            s.workspace.restore_on_file_reopen = Some(false);
26060        });
26061    });
26062    editor.update_in(cx, |editor, window, cx| {
26063        editor.fold_ranges(
26064            vec![
26065                Point::new(1, 0)..Point::new(1, 1),
26066                Point::new(2, 0)..Point::new(2, 2),
26067                Point::new(3, 0)..Point::new(3, 3),
26068            ],
26069            false,
26070            window,
26071            cx,
26072        );
26073    });
26074    pane.update_in(cx, |pane, window, cx| {
26075        pane.close_all_items(&CloseAllItems::default(), window, cx)
26076    })
26077    .await
26078    .unwrap();
26079    pane.update(cx, |pane, _| {
26080        assert!(pane.active_item().is_none());
26081    });
26082    cx.update_global(|store: &mut SettingsStore, cx| {
26083        store.update_user_settings(cx, |s| {
26084            s.workspace.restore_on_file_reopen = Some(true);
26085        });
26086    });
26087
26088    let _editor_reopened = workspace
26089        .update_in(cx, |workspace, window, cx| {
26090            workspace.open_path(
26091                (worktree_id, rel_path("main.rs")),
26092                Some(pane.downgrade()),
26093                true,
26094                window,
26095                cx,
26096            )
26097        })
26098        .unwrap()
26099        .await
26100        .downcast::<Editor>()
26101        .unwrap();
26102    pane.update(cx, |pane, cx| {
26103        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26104        open_editor.update(cx, |editor, cx| {
26105            assert_eq!(
26106                editor.display_text(cx),
26107                main_text,
26108                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
26109            );
26110        })
26111    });
26112}
26113
26114#[gpui::test]
26115async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
26116    struct EmptyModalView {
26117        focus_handle: gpui::FocusHandle,
26118    }
26119    impl EventEmitter<DismissEvent> for EmptyModalView {}
26120    impl Render for EmptyModalView {
26121        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
26122            div()
26123        }
26124    }
26125    impl Focusable for EmptyModalView {
26126        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
26127            self.focus_handle.clone()
26128        }
26129    }
26130    impl workspace::ModalView for EmptyModalView {}
26131    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
26132        EmptyModalView {
26133            focus_handle: cx.focus_handle(),
26134        }
26135    }
26136
26137    init_test(cx, |_| {});
26138
26139    let fs = FakeFs::new(cx.executor());
26140    let project = Project::test(fs, [], cx).await;
26141    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26142    let workspace = window
26143        .read_with(cx, |mw, _| mw.workspace().clone())
26144        .unwrap();
26145    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
26146    let cx = &mut VisualTestContext::from_window(*window, cx);
26147    let editor = cx.new_window_entity(|window, cx| {
26148        Editor::new(
26149            EditorMode::full(),
26150            buffer,
26151            Some(project.clone()),
26152            window,
26153            cx,
26154        )
26155    });
26156    workspace.update_in(cx, |workspace, window, cx| {
26157        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
26158    });
26159
26160    editor.update_in(cx, |editor, window, cx| {
26161        editor.open_context_menu(&OpenContextMenu, window, cx);
26162        assert!(editor.mouse_context_menu.is_some());
26163    });
26164    workspace.update_in(cx, |workspace, window, cx| {
26165        workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
26166    });
26167
26168    cx.read(|cx| {
26169        assert!(editor.read(cx).mouse_context_menu.is_none());
26170    });
26171}
26172
26173fn set_linked_edit_ranges(
26174    opening: (Point, Point),
26175    closing: (Point, Point),
26176    editor: &mut Editor,
26177    cx: &mut Context<Editor>,
26178) {
26179    let Some((buffer, _)) = editor
26180        .buffer
26181        .read(cx)
26182        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
26183    else {
26184        panic!("Failed to get buffer for selection position");
26185    };
26186    let buffer = buffer.read(cx);
26187    let buffer_id = buffer.remote_id();
26188    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
26189    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
26190    let mut linked_ranges = HashMap::default();
26191    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
26192    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
26193}
26194
26195#[gpui::test]
26196async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
26197    init_test(cx, |_| {});
26198
26199    let fs = FakeFs::new(cx.executor());
26200    fs.insert_file(path!("/file.html"), Default::default())
26201        .await;
26202
26203    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
26204
26205    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26206    let html_language = Arc::new(Language::new(
26207        LanguageConfig {
26208            name: "HTML".into(),
26209            matcher: LanguageMatcher {
26210                path_suffixes: vec!["html".to_string()],
26211                ..LanguageMatcher::default()
26212            },
26213            brackets: BracketPairConfig {
26214                pairs: vec![BracketPair {
26215                    start: "<".into(),
26216                    end: ">".into(),
26217                    close: true,
26218                    ..Default::default()
26219                }],
26220                ..Default::default()
26221            },
26222            ..Default::default()
26223        },
26224        Some(tree_sitter_html::LANGUAGE.into()),
26225    ));
26226    language_registry.add(html_language);
26227    let mut fake_servers = language_registry.register_fake_lsp(
26228        "HTML",
26229        FakeLspAdapter {
26230            capabilities: lsp::ServerCapabilities {
26231                completion_provider: Some(lsp::CompletionOptions {
26232                    resolve_provider: Some(true),
26233                    ..Default::default()
26234                }),
26235                ..Default::default()
26236            },
26237            ..Default::default()
26238        },
26239    );
26240
26241    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26242    let workspace = window
26243        .read_with(cx, |mw, _| mw.workspace().clone())
26244        .unwrap();
26245    let cx = &mut VisualTestContext::from_window(*window, cx);
26246
26247    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
26248        workspace.project().update(cx, |project, cx| {
26249            project.worktrees(cx).next().unwrap().read(cx).id()
26250        })
26251    });
26252
26253    project
26254        .update(cx, |project, cx| {
26255            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
26256        })
26257        .await
26258        .unwrap();
26259    let editor = workspace
26260        .update_in(cx, |workspace, window, cx| {
26261            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
26262        })
26263        .await
26264        .unwrap()
26265        .downcast::<Editor>()
26266        .unwrap();
26267
26268    let fake_server = fake_servers.next().await.unwrap();
26269    cx.run_until_parked();
26270    editor.update_in(cx, |editor, window, cx| {
26271        editor.set_text("<ad></ad>", window, cx);
26272        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26273            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
26274        });
26275        set_linked_edit_ranges(
26276            (Point::new(0, 1), Point::new(0, 3)),
26277            (Point::new(0, 6), Point::new(0, 8)),
26278            editor,
26279            cx,
26280        );
26281    });
26282    let mut completion_handle =
26283        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26284            Ok(Some(lsp::CompletionResponse::Array(vec![
26285                lsp::CompletionItem {
26286                    label: "head".to_string(),
26287                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26288                        lsp::InsertReplaceEdit {
26289                            new_text: "head".to_string(),
26290                            insert: lsp::Range::new(
26291                                lsp::Position::new(0, 1),
26292                                lsp::Position::new(0, 3),
26293                            ),
26294                            replace: lsp::Range::new(
26295                                lsp::Position::new(0, 1),
26296                                lsp::Position::new(0, 3),
26297                            ),
26298                        },
26299                    )),
26300                    ..Default::default()
26301                },
26302            ])))
26303        });
26304    editor.update_in(cx, |editor, window, cx| {
26305        editor.show_completions(&ShowCompletions, window, cx);
26306    });
26307    cx.run_until_parked();
26308    completion_handle.next().await.unwrap();
26309    editor.update(cx, |editor, _| {
26310        assert!(
26311            editor.context_menu_visible(),
26312            "Completion menu should be visible"
26313        );
26314    });
26315    editor.update_in(cx, |editor, window, cx| {
26316        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
26317    });
26318    cx.executor().run_until_parked();
26319    editor.update(cx, |editor, cx| {
26320        assert_eq!(editor.text(cx), "<head></head>");
26321    });
26322}
26323
26324#[gpui::test]
26325async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
26326    init_test(cx, |_| {});
26327
26328    let mut cx = EditorTestContext::new(cx).await;
26329    let language = Arc::new(Language::new(
26330        LanguageConfig {
26331            name: "TSX".into(),
26332            matcher: LanguageMatcher {
26333                path_suffixes: vec!["tsx".to_string()],
26334                ..LanguageMatcher::default()
26335            },
26336            brackets: BracketPairConfig {
26337                pairs: vec![BracketPair {
26338                    start: "<".into(),
26339                    end: ">".into(),
26340                    close: true,
26341                    ..Default::default()
26342                }],
26343                ..Default::default()
26344            },
26345            linked_edit_characters: HashSet::from_iter(['.']),
26346            ..Default::default()
26347        },
26348        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
26349    ));
26350    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26351
26352    // Test typing > does not extend linked pair
26353    cx.set_state("<divˇ<div></div>");
26354    cx.update_editor(|editor, _, cx| {
26355        set_linked_edit_ranges(
26356            (Point::new(0, 1), Point::new(0, 4)),
26357            (Point::new(0, 11), Point::new(0, 14)),
26358            editor,
26359            cx,
26360        );
26361    });
26362    cx.update_editor(|editor, window, cx| {
26363        editor.handle_input(">", window, cx);
26364    });
26365    cx.assert_editor_state("<div>ˇ<div></div>");
26366
26367    // Test typing . do extend linked pair
26368    cx.set_state("<Animatedˇ></Animated>");
26369    cx.update_editor(|editor, _, cx| {
26370        set_linked_edit_ranges(
26371            (Point::new(0, 1), Point::new(0, 9)),
26372            (Point::new(0, 12), Point::new(0, 20)),
26373            editor,
26374            cx,
26375        );
26376    });
26377    cx.update_editor(|editor, window, cx| {
26378        editor.handle_input(".", window, cx);
26379    });
26380    cx.assert_editor_state("<Animated.ˇ></Animated.>");
26381    cx.update_editor(|editor, _, cx| {
26382        set_linked_edit_ranges(
26383            (Point::new(0, 1), Point::new(0, 10)),
26384            (Point::new(0, 13), Point::new(0, 21)),
26385            editor,
26386            cx,
26387        );
26388    });
26389    cx.update_editor(|editor, window, cx| {
26390        editor.handle_input("V", window, cx);
26391    });
26392    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
26393}
26394
26395#[gpui::test]
26396async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
26397    init_test(cx, |_| {});
26398
26399    let fs = FakeFs::new(cx.executor());
26400    fs.insert_tree(
26401        path!("/root"),
26402        json!({
26403            "a": {
26404                "main.rs": "fn main() {}",
26405            },
26406            "foo": {
26407                "bar": {
26408                    "external_file.rs": "pub mod external {}",
26409                }
26410            }
26411        }),
26412    )
26413    .await;
26414
26415    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
26416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26417    language_registry.add(rust_lang());
26418    let _fake_servers = language_registry.register_fake_lsp(
26419        "Rust",
26420        FakeLspAdapter {
26421            ..FakeLspAdapter::default()
26422        },
26423    );
26424    let (multi_workspace, cx) =
26425        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26426    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26427    let worktree_id = workspace.update(cx, |workspace, cx| {
26428        workspace.project().update(cx, |project, cx| {
26429            project.worktrees(cx).next().unwrap().read(cx).id()
26430        })
26431    });
26432
26433    let assert_language_servers_count =
26434        |expected: usize, context: &str, cx: &mut VisualTestContext| {
26435            project.update(cx, |project, cx| {
26436                let current = project
26437                    .lsp_store()
26438                    .read(cx)
26439                    .as_local()
26440                    .unwrap()
26441                    .language_servers
26442                    .len();
26443                assert_eq!(expected, current, "{context}");
26444            });
26445        };
26446
26447    assert_language_servers_count(
26448        0,
26449        "No servers should be running before any file is open",
26450        cx,
26451    );
26452    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26453    let main_editor = workspace
26454        .update_in(cx, |workspace, window, cx| {
26455            workspace.open_path(
26456                (worktree_id, rel_path("main.rs")),
26457                Some(pane.downgrade()),
26458                true,
26459                window,
26460                cx,
26461            )
26462        })
26463        .unwrap()
26464        .await
26465        .downcast::<Editor>()
26466        .unwrap();
26467    pane.update(cx, |pane, cx| {
26468        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26469        open_editor.update(cx, |editor, cx| {
26470            assert_eq!(
26471                editor.display_text(cx),
26472                "fn main() {}",
26473                "Original main.rs text on initial open",
26474            );
26475        });
26476        assert_eq!(open_editor, main_editor);
26477    });
26478    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
26479
26480    let external_editor = workspace
26481        .update_in(cx, |workspace, window, cx| {
26482            workspace.open_abs_path(
26483                PathBuf::from("/root/foo/bar/external_file.rs"),
26484                OpenOptions::default(),
26485                window,
26486                cx,
26487            )
26488        })
26489        .await
26490        .expect("opening external file")
26491        .downcast::<Editor>()
26492        .expect("downcasted external file's open element to editor");
26493    pane.update(cx, |pane, cx| {
26494        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26495        open_editor.update(cx, |editor, cx| {
26496            assert_eq!(
26497                editor.display_text(cx),
26498                "pub mod external {}",
26499                "External file is open now",
26500            );
26501        });
26502        assert_eq!(open_editor, external_editor);
26503    });
26504    assert_language_servers_count(
26505        1,
26506        "Second, external, *.rs file should join the existing server",
26507        cx,
26508    );
26509
26510    pane.update_in(cx, |pane, window, cx| {
26511        pane.close_active_item(&CloseActiveItem::default(), window, cx)
26512    })
26513    .await
26514    .unwrap();
26515    pane.update_in(cx, |pane, window, cx| {
26516        pane.navigate_backward(&Default::default(), window, cx);
26517    });
26518    cx.run_until_parked();
26519    pane.update(cx, |pane, cx| {
26520        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26521        open_editor.update(cx, |editor, cx| {
26522            assert_eq!(
26523                editor.display_text(cx),
26524                "pub mod external {}",
26525                "External file is open now",
26526            );
26527        });
26528    });
26529    assert_language_servers_count(
26530        1,
26531        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
26532        cx,
26533    );
26534
26535    cx.update(|_, cx| {
26536        workspace::reload(cx);
26537    });
26538    assert_language_servers_count(
26539        1,
26540        "After reloading the worktree with local and external files opened, only one project should be started",
26541        cx,
26542    );
26543}
26544
26545#[gpui::test]
26546async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
26547    init_test(cx, |_| {});
26548
26549    let mut cx = EditorTestContext::new(cx).await;
26550    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26551    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26552
26553    // test cursor move to start of each line on tab
26554    // for `if`, `elif`, `else`, `while`, `with` and `for`
26555    cx.set_state(indoc! {"
26556        def main():
26557        ˇ    for item in items:
26558        ˇ        while item.active:
26559        ˇ            if item.value > 10:
26560        ˇ                continue
26561        ˇ            elif item.value < 0:
26562        ˇ                break
26563        ˇ            else:
26564        ˇ                with item.context() as ctx:
26565        ˇ                    yield count
26566        ˇ        else:
26567        ˇ            log('while else')
26568        ˇ    else:
26569        ˇ        log('for else')
26570    "});
26571    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26572    cx.wait_for_autoindent_applied().await;
26573    cx.assert_editor_state(indoc! {"
26574        def main():
26575            ˇfor item in items:
26576                ˇwhile item.active:
26577                    ˇif item.value > 10:
26578                        ˇcontinue
26579                    ˇelif item.value < 0:
26580                        ˇbreak
26581                    ˇelse:
26582                        ˇwith item.context() as ctx:
26583                            ˇyield count
26584                ˇelse:
26585                    ˇlog('while else')
26586            ˇelse:
26587                ˇlog('for else')
26588    "});
26589    // test relative indent is preserved when tab
26590    // for `if`, `elif`, `else`, `while`, `with` and `for`
26591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26592    cx.wait_for_autoindent_applied().await;
26593    cx.assert_editor_state(indoc! {"
26594        def main():
26595                ˇfor item in items:
26596                    ˇwhile item.active:
26597                        ˇif item.value > 10:
26598                            ˇcontinue
26599                        ˇelif item.value < 0:
26600                            ˇbreak
26601                        ˇelse:
26602                            ˇwith item.context() as ctx:
26603                                ˇyield count
26604                    ˇelse:
26605                        ˇlog('while else')
26606                ˇelse:
26607                    ˇlog('for else')
26608    "});
26609
26610    // test cursor move to start of each line on tab
26611    // for `try`, `except`, `else`, `finally`, `match` and `def`
26612    cx.set_state(indoc! {"
26613        def main():
26614        ˇ    try:
26615        ˇ        fetch()
26616        ˇ    except ValueError:
26617        ˇ        handle_error()
26618        ˇ    else:
26619        ˇ        match value:
26620        ˇ            case _:
26621        ˇ    finally:
26622        ˇ        def status():
26623        ˇ            return 0
26624    "});
26625    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26626    cx.wait_for_autoindent_applied().await;
26627    cx.assert_editor_state(indoc! {"
26628        def main():
26629            ˇtry:
26630                ˇfetch()
26631            ˇexcept ValueError:
26632                ˇhandle_error()
26633            ˇelse:
26634                ˇmatch value:
26635                    ˇcase _:
26636            ˇfinally:
26637                ˇdef status():
26638                    ˇreturn 0
26639    "});
26640    // test relative indent is preserved when tab
26641    // for `try`, `except`, `else`, `finally`, `match` and `def`
26642    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26643    cx.wait_for_autoindent_applied().await;
26644    cx.assert_editor_state(indoc! {"
26645        def main():
26646                ˇtry:
26647                    ˇfetch()
26648                ˇexcept ValueError:
26649                    ˇhandle_error()
26650                ˇelse:
26651                    ˇmatch value:
26652                        ˇcase _:
26653                ˇfinally:
26654                    ˇdef status():
26655                        ˇreturn 0
26656    "});
26657}
26658
26659#[gpui::test]
26660async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26661    init_test(cx, |_| {});
26662
26663    let mut cx = EditorTestContext::new(cx).await;
26664    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26665    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26666
26667    // test `else` auto outdents when typed inside `if` block
26668    cx.set_state(indoc! {"
26669        def main():
26670            if i == 2:
26671                return
26672                ˇ
26673    "});
26674    cx.update_editor(|editor, window, cx| {
26675        editor.handle_input("else:", window, cx);
26676    });
26677    cx.wait_for_autoindent_applied().await;
26678    cx.assert_editor_state(indoc! {"
26679        def main():
26680            if i == 2:
26681                return
26682            else:ˇ
26683    "});
26684
26685    // test `except` auto outdents when typed inside `try` block
26686    cx.set_state(indoc! {"
26687        def main():
26688            try:
26689                i = 2
26690                ˇ
26691    "});
26692    cx.update_editor(|editor, window, cx| {
26693        editor.handle_input("except:", window, cx);
26694    });
26695    cx.wait_for_autoindent_applied().await;
26696    cx.assert_editor_state(indoc! {"
26697        def main():
26698            try:
26699                i = 2
26700            except:ˇ
26701    "});
26702
26703    // test `else` auto outdents when typed inside `except` block
26704    cx.set_state(indoc! {"
26705        def main():
26706            try:
26707                i = 2
26708            except:
26709                j = 2
26710                ˇ
26711    "});
26712    cx.update_editor(|editor, window, cx| {
26713        editor.handle_input("else:", window, cx);
26714    });
26715    cx.wait_for_autoindent_applied().await;
26716    cx.assert_editor_state(indoc! {"
26717        def main():
26718            try:
26719                i = 2
26720            except:
26721                j = 2
26722            else:ˇ
26723    "});
26724
26725    // test `finally` auto outdents when typed inside `else` block
26726    cx.set_state(indoc! {"
26727        def main():
26728            try:
26729                i = 2
26730            except:
26731                j = 2
26732            else:
26733                k = 2
26734                ˇ
26735    "});
26736    cx.update_editor(|editor, window, cx| {
26737        editor.handle_input("finally:", window, cx);
26738    });
26739    cx.wait_for_autoindent_applied().await;
26740    cx.assert_editor_state(indoc! {"
26741        def main():
26742            try:
26743                i = 2
26744            except:
26745                j = 2
26746            else:
26747                k = 2
26748            finally:ˇ
26749    "});
26750
26751    // test `else` does not outdents when typed inside `except` block right after for block
26752    cx.set_state(indoc! {"
26753        def main():
26754            try:
26755                i = 2
26756            except:
26757                for i in range(n):
26758                    pass
26759                ˇ
26760    "});
26761    cx.update_editor(|editor, window, cx| {
26762        editor.handle_input("else:", window, cx);
26763    });
26764    cx.wait_for_autoindent_applied().await;
26765    cx.assert_editor_state(indoc! {"
26766        def main():
26767            try:
26768                i = 2
26769            except:
26770                for i in range(n):
26771                    pass
26772                else:ˇ
26773    "});
26774
26775    // test `finally` auto outdents when typed inside `else` block right after for block
26776    cx.set_state(indoc! {"
26777        def main():
26778            try:
26779                i = 2
26780            except:
26781                j = 2
26782            else:
26783                for i in range(n):
26784                    pass
26785                ˇ
26786    "});
26787    cx.update_editor(|editor, window, cx| {
26788        editor.handle_input("finally:", window, cx);
26789    });
26790    cx.wait_for_autoindent_applied().await;
26791    cx.assert_editor_state(indoc! {"
26792        def main():
26793            try:
26794                i = 2
26795            except:
26796                j = 2
26797            else:
26798                for i in range(n):
26799                    pass
26800            finally:ˇ
26801    "});
26802
26803    // test `except` outdents to inner "try" block
26804    cx.set_state(indoc! {"
26805        def main():
26806            try:
26807                i = 2
26808                if i == 2:
26809                    try:
26810                        i = 3
26811                        ˇ
26812    "});
26813    cx.update_editor(|editor, window, cx| {
26814        editor.handle_input("except:", window, cx);
26815    });
26816    cx.wait_for_autoindent_applied().await;
26817    cx.assert_editor_state(indoc! {"
26818        def main():
26819            try:
26820                i = 2
26821                if i == 2:
26822                    try:
26823                        i = 3
26824                    except:ˇ
26825    "});
26826
26827    // test `except` outdents to outer "try" block
26828    cx.set_state(indoc! {"
26829        def main():
26830            try:
26831                i = 2
26832                if i == 2:
26833                    try:
26834                        i = 3
26835                ˇ
26836    "});
26837    cx.update_editor(|editor, window, cx| {
26838        editor.handle_input("except:", window, cx);
26839    });
26840    cx.wait_for_autoindent_applied().await;
26841    cx.assert_editor_state(indoc! {"
26842        def main():
26843            try:
26844                i = 2
26845                if i == 2:
26846                    try:
26847                        i = 3
26848            except:ˇ
26849    "});
26850
26851    // test `else` stays at correct indent when typed after `for` block
26852    cx.set_state(indoc! {"
26853        def main():
26854            for i in range(10):
26855                if i == 3:
26856                    break
26857            ˇ
26858    "});
26859    cx.update_editor(|editor, window, cx| {
26860        editor.handle_input("else:", window, cx);
26861    });
26862    cx.wait_for_autoindent_applied().await;
26863    cx.assert_editor_state(indoc! {"
26864        def main():
26865            for i in range(10):
26866                if i == 3:
26867                    break
26868            else:ˇ
26869    "});
26870
26871    // test does not outdent on typing after line with square brackets
26872    cx.set_state(indoc! {"
26873        def f() -> list[str]:
26874            ˇ
26875    "});
26876    cx.update_editor(|editor, window, cx| {
26877        editor.handle_input("a", window, cx);
26878    });
26879    cx.wait_for_autoindent_applied().await;
26880    cx.assert_editor_state(indoc! {"
26881        def f() -> list[str]:
2688226883    "});
26884
26885    // test does not outdent on typing : after case keyword
26886    cx.set_state(indoc! {"
26887        match 1:
26888            caseˇ
26889    "});
26890    cx.update_editor(|editor, window, cx| {
26891        editor.handle_input(":", window, cx);
26892    });
26893    cx.wait_for_autoindent_applied().await;
26894    cx.assert_editor_state(indoc! {"
26895        match 1:
26896            case:ˇ
26897    "});
26898}
26899
26900#[gpui::test]
26901async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26902    init_test(cx, |_| {});
26903    update_test_language_settings(cx, |settings| {
26904        settings.defaults.extend_comment_on_newline = Some(false);
26905    });
26906    let mut cx = EditorTestContext::new(cx).await;
26907    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26908    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26909
26910    // test correct indent after newline on comment
26911    cx.set_state(indoc! {"
26912        # COMMENT:ˇ
26913    "});
26914    cx.update_editor(|editor, window, cx| {
26915        editor.newline(&Newline, window, cx);
26916    });
26917    cx.wait_for_autoindent_applied().await;
26918    cx.assert_editor_state(indoc! {"
26919        # COMMENT:
26920        ˇ
26921    "});
26922
26923    // test correct indent after newline in brackets
26924    cx.set_state(indoc! {"
26925        {ˇ}
26926    "});
26927    cx.update_editor(|editor, window, cx| {
26928        editor.newline(&Newline, window, cx);
26929    });
26930    cx.wait_for_autoindent_applied().await;
26931    cx.assert_editor_state(indoc! {"
26932        {
26933            ˇ
26934        }
26935    "});
26936
26937    cx.set_state(indoc! {"
26938        (ˇ)
26939    "});
26940    cx.update_editor(|editor, window, cx| {
26941        editor.newline(&Newline, window, cx);
26942    });
26943    cx.run_until_parked();
26944    cx.assert_editor_state(indoc! {"
26945        (
26946            ˇ
26947        )
26948    "});
26949
26950    // do not indent after empty lists or dictionaries
26951    cx.set_state(indoc! {"
26952        a = []ˇ
26953    "});
26954    cx.update_editor(|editor, window, cx| {
26955        editor.newline(&Newline, window, cx);
26956    });
26957    cx.run_until_parked();
26958    cx.assert_editor_state(indoc! {"
26959        a = []
26960        ˇ
26961    "});
26962}
26963
26964#[gpui::test]
26965async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26966    init_test(cx, |_| {});
26967
26968    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26969    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26970    language_registry.add(markdown_lang());
26971    language_registry.add(python_lang);
26972
26973    let mut cx = EditorTestContext::new(cx).await;
26974    cx.update_buffer(|buffer, cx| {
26975        buffer.set_language_registry(language_registry);
26976        buffer.set_language(Some(markdown_lang()), cx);
26977    });
26978
26979    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26980    cx.set_state(indoc! {"
26981        # Heading
26982
26983        ```python
26984        def main():
26985            if condition:
26986                pass
26987                ˇ
26988        ```
26989    "});
26990    cx.update_editor(|editor, window, cx| {
26991        editor.handle_input("else:", window, cx);
26992    });
26993    cx.run_until_parked();
26994    cx.assert_editor_state(indoc! {"
26995        # Heading
26996
26997        ```python
26998        def main():
26999            if condition:
27000                pass
27001            else:ˇ
27002        ```
27003    "});
27004}
27005
27006#[gpui::test]
27007async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
27008    init_test(cx, |_| {});
27009
27010    let mut cx = EditorTestContext::new(cx).await;
27011    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27012    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27013
27014    // test cursor move to start of each line on tab
27015    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
27016    cx.set_state(indoc! {"
27017        function main() {
27018        ˇ    for item in $items; do
27019        ˇ        while [ -n \"$item\" ]; do
27020        ˇ            if [ \"$value\" -gt 10 ]; then
27021        ˇ                continue
27022        ˇ            elif [ \"$value\" -lt 0 ]; then
27023        ˇ                break
27024        ˇ            else
27025        ˇ                echo \"$item\"
27026        ˇ            fi
27027        ˇ        done
27028        ˇ    done
27029        ˇ}
27030    "});
27031    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27032    cx.wait_for_autoindent_applied().await;
27033    cx.assert_editor_state(indoc! {"
27034        function main() {
27035            ˇfor item in $items; do
27036                ˇwhile [ -n \"$item\" ]; do
27037                    ˇif [ \"$value\" -gt 10 ]; then
27038                        ˇcontinue
27039                    ˇelif [ \"$value\" -lt 0 ]; then
27040                        ˇbreak
27041                    ˇelse
27042                        ˇecho \"$item\"
27043                    ˇfi
27044                ˇdone
27045            ˇdone
27046        ˇ}
27047    "});
27048    // test relative indent is preserved when tab
27049    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27050    cx.wait_for_autoindent_applied().await;
27051    cx.assert_editor_state(indoc! {"
27052        function main() {
27053                ˇfor item in $items; do
27054                    ˇwhile [ -n \"$item\" ]; do
27055                        ˇif [ \"$value\" -gt 10 ]; then
27056                            ˇcontinue
27057                        ˇelif [ \"$value\" -lt 0 ]; then
27058                            ˇbreak
27059                        ˇelse
27060                            ˇecho \"$item\"
27061                        ˇfi
27062                    ˇdone
27063                ˇdone
27064            ˇ}
27065    "});
27066
27067    // test cursor move to start of each line on tab
27068    // for `case` statement with patterns
27069    cx.set_state(indoc! {"
27070        function handle() {
27071        ˇ    case \"$1\" in
27072        ˇ        start)
27073        ˇ            echo \"a\"
27074        ˇ            ;;
27075        ˇ        stop)
27076        ˇ            echo \"b\"
27077        ˇ            ;;
27078        ˇ        *)
27079        ˇ            echo \"c\"
27080        ˇ            ;;
27081        ˇ    esac
27082        ˇ}
27083    "});
27084    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27085    cx.wait_for_autoindent_applied().await;
27086    cx.assert_editor_state(indoc! {"
27087        function handle() {
27088            ˇcase \"$1\" in
27089                ˇstart)
27090                    ˇecho \"a\"
27091                    ˇ;;
27092                ˇstop)
27093                    ˇecho \"b\"
27094                    ˇ;;
27095                ˇ*)
27096                    ˇecho \"c\"
27097                    ˇ;;
27098            ˇesac
27099        ˇ}
27100    "});
27101}
27102
27103#[gpui::test]
27104async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
27105    init_test(cx, |_| {});
27106
27107    let mut cx = EditorTestContext::new(cx).await;
27108    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27109    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27110
27111    // test indents on comment insert
27112    cx.set_state(indoc! {"
27113        function main() {
27114        ˇ    for item in $items; do
27115        ˇ        while [ -n \"$item\" ]; do
27116        ˇ            if [ \"$value\" -gt 10 ]; then
27117        ˇ                continue
27118        ˇ            elif [ \"$value\" -lt 0 ]; then
27119        ˇ                break
27120        ˇ            else
27121        ˇ                echo \"$item\"
27122        ˇ            fi
27123        ˇ        done
27124        ˇ    done
27125        ˇ}
27126    "});
27127    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
27128    cx.wait_for_autoindent_applied().await;
27129    cx.assert_editor_state(indoc! {"
27130        function main() {
27131        #ˇ    for item in $items; do
27132        #ˇ        while [ -n \"$item\" ]; do
27133        #ˇ            if [ \"$value\" -gt 10 ]; then
27134        #ˇ                continue
27135        #ˇ            elif [ \"$value\" -lt 0 ]; then
27136        #ˇ                break
27137        #ˇ            else
27138        #ˇ                echo \"$item\"
27139        #ˇ            fi
27140        #ˇ        done
27141        #ˇ    done
27142        #ˇ}
27143    "});
27144}
27145
27146#[gpui::test]
27147async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
27148    init_test(cx, |_| {});
27149
27150    let mut cx = EditorTestContext::new(cx).await;
27151    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27152    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27153
27154    // test `else` auto outdents when typed inside `if` block
27155    cx.set_state(indoc! {"
27156        if [ \"$1\" = \"test\" ]; then
27157            echo \"foo bar\"
27158            ˇ
27159    "});
27160    cx.update_editor(|editor, window, cx| {
27161        editor.handle_input("else", window, cx);
27162    });
27163    cx.wait_for_autoindent_applied().await;
27164    cx.assert_editor_state(indoc! {"
27165        if [ \"$1\" = \"test\" ]; then
27166            echo \"foo bar\"
27167        elseˇ
27168    "});
27169
27170    // test `elif` auto outdents when typed inside `if` block
27171    cx.set_state(indoc! {"
27172        if [ \"$1\" = \"test\" ]; then
27173            echo \"foo bar\"
27174            ˇ
27175    "});
27176    cx.update_editor(|editor, window, cx| {
27177        editor.handle_input("elif", window, cx);
27178    });
27179    cx.wait_for_autoindent_applied().await;
27180    cx.assert_editor_state(indoc! {"
27181        if [ \"$1\" = \"test\" ]; then
27182            echo \"foo bar\"
27183        elifˇ
27184    "});
27185
27186    // test `fi` auto outdents when typed inside `else` block
27187    cx.set_state(indoc! {"
27188        if [ \"$1\" = \"test\" ]; then
27189            echo \"foo bar\"
27190        else
27191            echo \"bar baz\"
27192            ˇ
27193    "});
27194    cx.update_editor(|editor, window, cx| {
27195        editor.handle_input("fi", window, cx);
27196    });
27197    cx.wait_for_autoindent_applied().await;
27198    cx.assert_editor_state(indoc! {"
27199        if [ \"$1\" = \"test\" ]; then
27200            echo \"foo bar\"
27201        else
27202            echo \"bar baz\"
27203        fiˇ
27204    "});
27205
27206    // test `done` auto outdents when typed inside `while` block
27207    cx.set_state(indoc! {"
27208        while read line; do
27209            echo \"$line\"
27210            ˇ
27211    "});
27212    cx.update_editor(|editor, window, cx| {
27213        editor.handle_input("done", window, cx);
27214    });
27215    cx.wait_for_autoindent_applied().await;
27216    cx.assert_editor_state(indoc! {"
27217        while read line; do
27218            echo \"$line\"
27219        doneˇ
27220    "});
27221
27222    // test `done` auto outdents when typed inside `for` block
27223    cx.set_state(indoc! {"
27224        for file in *.txt; do
27225            cat \"$file\"
27226            ˇ
27227    "});
27228    cx.update_editor(|editor, window, cx| {
27229        editor.handle_input("done", window, cx);
27230    });
27231    cx.wait_for_autoindent_applied().await;
27232    cx.assert_editor_state(indoc! {"
27233        for file in *.txt; do
27234            cat \"$file\"
27235        doneˇ
27236    "});
27237
27238    // test `esac` auto outdents when typed inside `case` block
27239    cx.set_state(indoc! {"
27240        case \"$1\" in
27241            start)
27242                echo \"foo bar\"
27243                ;;
27244            stop)
27245                echo \"bar baz\"
27246                ;;
27247            ˇ
27248    "});
27249    cx.update_editor(|editor, window, cx| {
27250        editor.handle_input("esac", window, cx);
27251    });
27252    cx.wait_for_autoindent_applied().await;
27253    cx.assert_editor_state(indoc! {"
27254        case \"$1\" in
27255            start)
27256                echo \"foo bar\"
27257                ;;
27258            stop)
27259                echo \"bar baz\"
27260                ;;
27261        esacˇ
27262    "});
27263
27264    // test `*)` auto outdents when typed inside `case` block
27265    cx.set_state(indoc! {"
27266        case \"$1\" in
27267            start)
27268                echo \"foo bar\"
27269                ;;
27270                ˇ
27271    "});
27272    cx.update_editor(|editor, window, cx| {
27273        editor.handle_input("*)", window, cx);
27274    });
27275    cx.wait_for_autoindent_applied().await;
27276    cx.assert_editor_state(indoc! {"
27277        case \"$1\" in
27278            start)
27279                echo \"foo bar\"
27280                ;;
27281            *)ˇ
27282    "});
27283
27284    // test `fi` outdents to correct level with nested if blocks
27285    cx.set_state(indoc! {"
27286        if [ \"$1\" = \"test\" ]; then
27287            echo \"outer if\"
27288            if [ \"$2\" = \"debug\" ]; then
27289                echo \"inner if\"
27290                ˇ
27291    "});
27292    cx.update_editor(|editor, window, cx| {
27293        editor.handle_input("fi", window, cx);
27294    });
27295    cx.wait_for_autoindent_applied().await;
27296    cx.assert_editor_state(indoc! {"
27297        if [ \"$1\" = \"test\" ]; then
27298            echo \"outer if\"
27299            if [ \"$2\" = \"debug\" ]; then
27300                echo \"inner if\"
27301            fiˇ
27302    "});
27303}
27304
27305#[gpui::test]
27306async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
27307    init_test(cx, |_| {});
27308    update_test_language_settings(cx, |settings| {
27309        settings.defaults.extend_comment_on_newline = Some(false);
27310    });
27311    let mut cx = EditorTestContext::new(cx).await;
27312    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27313    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27314
27315    // test correct indent after newline on comment
27316    cx.set_state(indoc! {"
27317        # COMMENT:ˇ
27318    "});
27319    cx.update_editor(|editor, window, cx| {
27320        editor.newline(&Newline, window, cx);
27321    });
27322    cx.wait_for_autoindent_applied().await;
27323    cx.assert_editor_state(indoc! {"
27324        # COMMENT:
27325        ˇ
27326    "});
27327
27328    // test correct indent after newline after `then`
27329    cx.set_state(indoc! {"
27330
27331        if [ \"$1\" = \"test\" ]; thenˇ
27332    "});
27333    cx.update_editor(|editor, window, cx| {
27334        editor.newline(&Newline, window, cx);
27335    });
27336    cx.wait_for_autoindent_applied().await;
27337    cx.assert_editor_state(indoc! {"
27338
27339        if [ \"$1\" = \"test\" ]; then
27340            ˇ
27341    "});
27342
27343    // test correct indent after newline after `else`
27344    cx.set_state(indoc! {"
27345        if [ \"$1\" = \"test\" ]; then
27346        elseˇ
27347    "});
27348    cx.update_editor(|editor, window, cx| {
27349        editor.newline(&Newline, window, cx);
27350    });
27351    cx.wait_for_autoindent_applied().await;
27352    cx.assert_editor_state(indoc! {"
27353        if [ \"$1\" = \"test\" ]; then
27354        else
27355            ˇ
27356    "});
27357
27358    // test correct indent after newline after `elif`
27359    cx.set_state(indoc! {"
27360        if [ \"$1\" = \"test\" ]; then
27361        elifˇ
27362    "});
27363    cx.update_editor(|editor, window, cx| {
27364        editor.newline(&Newline, window, cx);
27365    });
27366    cx.wait_for_autoindent_applied().await;
27367    cx.assert_editor_state(indoc! {"
27368        if [ \"$1\" = \"test\" ]; then
27369        elif
27370            ˇ
27371    "});
27372
27373    // test correct indent after newline after `do`
27374    cx.set_state(indoc! {"
27375        for file in *.txt; doˇ
27376    "});
27377    cx.update_editor(|editor, window, cx| {
27378        editor.newline(&Newline, window, cx);
27379    });
27380    cx.wait_for_autoindent_applied().await;
27381    cx.assert_editor_state(indoc! {"
27382        for file in *.txt; do
27383            ˇ
27384    "});
27385
27386    // test correct indent after newline after case pattern
27387    cx.set_state(indoc! {"
27388        case \"$1\" in
27389            start)ˇ
27390    "});
27391    cx.update_editor(|editor, window, cx| {
27392        editor.newline(&Newline, window, cx);
27393    });
27394    cx.wait_for_autoindent_applied().await;
27395    cx.assert_editor_state(indoc! {"
27396        case \"$1\" in
27397            start)
27398                ˇ
27399    "});
27400
27401    // test correct indent after newline after case pattern
27402    cx.set_state(indoc! {"
27403        case \"$1\" in
27404            start)
27405                ;;
27406            *)ˇ
27407    "});
27408    cx.update_editor(|editor, window, cx| {
27409        editor.newline(&Newline, window, cx);
27410    });
27411    cx.wait_for_autoindent_applied().await;
27412    cx.assert_editor_state(indoc! {"
27413        case \"$1\" in
27414            start)
27415                ;;
27416            *)
27417                ˇ
27418    "});
27419
27420    // test correct indent after newline after function opening brace
27421    cx.set_state(indoc! {"
27422        function test() {ˇ}
27423    "});
27424    cx.update_editor(|editor, window, cx| {
27425        editor.newline(&Newline, window, cx);
27426    });
27427    cx.wait_for_autoindent_applied().await;
27428    cx.assert_editor_state(indoc! {"
27429        function test() {
27430            ˇ
27431        }
27432    "});
27433
27434    // test no extra indent after semicolon on same line
27435    cx.set_state(indoc! {"
27436        echo \"test\"27437    "});
27438    cx.update_editor(|editor, window, cx| {
27439        editor.newline(&Newline, window, cx);
27440    });
27441    cx.wait_for_autoindent_applied().await;
27442    cx.assert_editor_state(indoc! {"
27443        echo \"test\";
27444        ˇ
27445    "});
27446}
27447
27448fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
27449    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
27450    point..point
27451}
27452
27453#[track_caller]
27454fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
27455    let (text, ranges) = marked_text_ranges(marked_text, true);
27456    assert_eq!(editor.text(cx), text);
27457    assert_eq!(
27458        editor.selections.ranges(&editor.display_snapshot(cx)),
27459        ranges
27460            .iter()
27461            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
27462            .collect::<Vec<_>>(),
27463        "Assert selections are {}",
27464        marked_text
27465    );
27466}
27467
27468pub fn handle_signature_help_request(
27469    cx: &mut EditorLspTestContext,
27470    mocked_response: lsp::SignatureHelp,
27471) -> impl Future<Output = ()> + use<> {
27472    let mut request =
27473        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
27474            let mocked_response = mocked_response.clone();
27475            async move { Ok(Some(mocked_response)) }
27476        });
27477
27478    async move {
27479        request.next().await;
27480    }
27481}
27482
27483#[track_caller]
27484pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
27485    cx.update_editor(|editor, _, _| {
27486        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
27487            let entries = menu.entries.borrow();
27488            let entries = entries
27489                .iter()
27490                .map(|entry| entry.string.as_str())
27491                .collect::<Vec<_>>();
27492            assert_eq!(entries, expected);
27493        } else {
27494            panic!("Expected completions menu");
27495        }
27496    });
27497}
27498
27499#[gpui::test]
27500async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
27501    init_test(cx, |_| {});
27502    let mut cx = EditorLspTestContext::new_rust(
27503        lsp::ServerCapabilities {
27504            completion_provider: Some(lsp::CompletionOptions {
27505                ..Default::default()
27506            }),
27507            ..Default::default()
27508        },
27509        cx,
27510    )
27511    .await;
27512    cx.lsp
27513        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27514            Ok(Some(lsp::CompletionResponse::Array(vec![
27515                lsp::CompletionItem {
27516                    label: "unsafe".into(),
27517                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27518                        range: lsp::Range {
27519                            start: lsp::Position {
27520                                line: 0,
27521                                character: 9,
27522                            },
27523                            end: lsp::Position {
27524                                line: 0,
27525                                character: 11,
27526                            },
27527                        },
27528                        new_text: "unsafe".to_string(),
27529                    })),
27530                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
27531                    ..Default::default()
27532                },
27533            ])))
27534        });
27535
27536    cx.update_editor(|editor, _, cx| {
27537        editor.project().unwrap().update(cx, |project, cx| {
27538            project.snippets().update(cx, |snippets, _cx| {
27539                snippets.add_snippet_for_test(
27540                    None,
27541                    PathBuf::from("test_snippets.json"),
27542                    vec![
27543                        Arc::new(project::snippet_provider::Snippet {
27544                            prefix: vec![
27545                                "unlimited word count".to_string(),
27546                                "unlimit word count".to_string(),
27547                                "unlimited unknown".to_string(),
27548                            ],
27549                            body: "this is many words".to_string(),
27550                            description: Some("description".to_string()),
27551                            name: "multi-word snippet test".to_string(),
27552                        }),
27553                        Arc::new(project::snippet_provider::Snippet {
27554                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
27555                            body: "fewer words".to_string(),
27556                            description: Some("alt description".to_string()),
27557                            name: "other name".to_string(),
27558                        }),
27559                        Arc::new(project::snippet_provider::Snippet {
27560                            prefix: vec!["ab aa".to_string()],
27561                            body: "abcd".to_string(),
27562                            description: None,
27563                            name: "alphabet".to_string(),
27564                        }),
27565                    ],
27566                );
27567            });
27568        })
27569    });
27570
27571    let get_completions = |cx: &mut EditorLspTestContext| {
27572        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
27573            Some(CodeContextMenu::Completions(context_menu)) => {
27574                let entries = context_menu.entries.borrow();
27575                entries
27576                    .iter()
27577                    .map(|entry| entry.string.clone())
27578                    .collect_vec()
27579            }
27580            _ => vec![],
27581        })
27582    };
27583
27584    // snippets:
27585    //  @foo
27586    //  foo bar
27587    //
27588    // when typing:
27589    //
27590    // when typing:
27591    //  - if I type a symbol "open the completions with snippets only"
27592    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
27593    //
27594    // stuff we need:
27595    //  - filtering logic change?
27596    //  - remember how far back the completion started.
27597
27598    let test_cases: &[(&str, &[&str])] = &[
27599        (
27600            "un",
27601            &[
27602                "unsafe",
27603                "unlimit word count",
27604                "unlimited unknown",
27605                "unlimited word count",
27606                "unsnip",
27607            ],
27608        ),
27609        (
27610            "u ",
27611            &[
27612                "unlimit word count",
27613                "unlimited unknown",
27614                "unlimited word count",
27615            ],
27616        ),
27617        ("u a", &["ab aa", "unsafe"]), // unsAfe
27618        (
27619            "u u",
27620            &[
27621                "unsafe",
27622                "unlimit word count",
27623                "unlimited unknown", // ranked highest among snippets
27624                "unlimited word count",
27625                "unsnip",
27626            ],
27627        ),
27628        ("uw c", &["unlimit word count", "unlimited word count"]),
27629        (
27630            "u w",
27631            &[
27632                "unlimit word count",
27633                "unlimited word count",
27634                "unlimited unknown",
27635            ],
27636        ),
27637        ("u w ", &["unlimit word count", "unlimited word count"]),
27638        (
27639            "u ",
27640            &[
27641                "unlimit word count",
27642                "unlimited unknown",
27643                "unlimited word count",
27644            ],
27645        ),
27646        ("wor", &[]),
27647        ("uf", &["unsafe"]),
27648        ("af", &["unsafe"]),
27649        ("afu", &[]),
27650        (
27651            "ue",
27652            &["unsafe", "unlimited unknown", "unlimited word count"],
27653        ),
27654        ("@", &["@few"]),
27655        ("@few", &["@few"]),
27656        ("@ ", &[]),
27657        ("a@", &["@few"]),
27658        ("a@f", &["@few", "unsafe"]),
27659        ("a@fw", &["@few"]),
27660        ("a", &["ab aa", "unsafe"]),
27661        ("aa", &["ab aa"]),
27662        ("aaa", &["ab aa"]),
27663        ("ab", &["ab aa"]),
27664        ("ab ", &["ab aa"]),
27665        ("ab a", &["ab aa", "unsafe"]),
27666        ("ab ab", &["ab aa"]),
27667        ("ab ab aa", &["ab aa"]),
27668    ];
27669
27670    for &(input_to_simulate, expected_completions) in test_cases {
27671        cx.set_state("fn a() { ˇ }\n");
27672        for c in input_to_simulate.split("") {
27673            cx.simulate_input(c);
27674            cx.run_until_parked();
27675        }
27676        let expected_completions = expected_completions
27677            .iter()
27678            .map(|s| s.to_string())
27679            .collect_vec();
27680        assert_eq!(
27681            get_completions(&mut cx),
27682            expected_completions,
27683            "< actual / expected >, input = {input_to_simulate:?}",
27684        );
27685    }
27686}
27687
27688/// Handle completion request passing a marked string specifying where the completion
27689/// should be triggered from using '|' character, what range should be replaced, and what completions
27690/// should be returned using '<' and '>' to delimit the range.
27691///
27692/// Also see `handle_completion_request_with_insert_and_replace`.
27693#[track_caller]
27694pub fn handle_completion_request(
27695    marked_string: &str,
27696    completions: Vec<&'static str>,
27697    is_incomplete: bool,
27698    counter: Arc<AtomicUsize>,
27699    cx: &mut EditorLspTestContext,
27700) -> impl Future<Output = ()> {
27701    let complete_from_marker: TextRangeMarker = '|'.into();
27702    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27703    let (_, mut marked_ranges) = marked_text_ranges_by(
27704        marked_string,
27705        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27706    );
27707
27708    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27709        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27710    ));
27711    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27712    let replace_range =
27713        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27714
27715    let mut request =
27716        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27717            let completions = completions.clone();
27718            counter.fetch_add(1, atomic::Ordering::Release);
27719            async move {
27720                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27721                assert_eq!(
27722                    params.text_document_position.position,
27723                    complete_from_position
27724                );
27725                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27726                    is_incomplete,
27727                    item_defaults: None,
27728                    items: completions
27729                        .iter()
27730                        .map(|completion_text| lsp::CompletionItem {
27731                            label: completion_text.to_string(),
27732                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27733                                range: replace_range,
27734                                new_text: completion_text.to_string(),
27735                            })),
27736                            ..Default::default()
27737                        })
27738                        .collect(),
27739                })))
27740            }
27741        });
27742
27743    async move {
27744        request.next().await;
27745    }
27746}
27747
27748/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27749/// given instead, which also contains an `insert` range.
27750///
27751/// This function uses markers to define ranges:
27752/// - `|` marks the cursor position
27753/// - `<>` marks the replace range
27754/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27755pub fn handle_completion_request_with_insert_and_replace(
27756    cx: &mut EditorLspTestContext,
27757    marked_string: &str,
27758    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27759    counter: Arc<AtomicUsize>,
27760) -> impl Future<Output = ()> {
27761    let complete_from_marker: TextRangeMarker = '|'.into();
27762    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27763    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27764
27765    let (_, mut marked_ranges) = marked_text_ranges_by(
27766        marked_string,
27767        vec![
27768            complete_from_marker.clone(),
27769            replace_range_marker.clone(),
27770            insert_range_marker.clone(),
27771        ],
27772    );
27773
27774    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27775        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27776    ));
27777    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27778    let replace_range =
27779        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27780
27781    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27782        Some(ranges) if !ranges.is_empty() => {
27783            let range1 = ranges[0].clone();
27784            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27785        }
27786        _ => lsp::Range {
27787            start: replace_range.start,
27788            end: complete_from_position,
27789        },
27790    };
27791
27792    let mut request =
27793        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27794            let completions = completions.clone();
27795            counter.fetch_add(1, atomic::Ordering::Release);
27796            async move {
27797                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27798                assert_eq!(
27799                    params.text_document_position.position, complete_from_position,
27800                    "marker `|` position doesn't match",
27801                );
27802                Ok(Some(lsp::CompletionResponse::Array(
27803                    completions
27804                        .iter()
27805                        .map(|(label, new_text)| lsp::CompletionItem {
27806                            label: label.to_string(),
27807                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27808                                lsp::InsertReplaceEdit {
27809                                    insert: insert_range,
27810                                    replace: replace_range,
27811                                    new_text: new_text.to_string(),
27812                                },
27813                            )),
27814                            ..Default::default()
27815                        })
27816                        .collect(),
27817                )))
27818            }
27819        });
27820
27821    async move {
27822        request.next().await;
27823    }
27824}
27825
27826fn handle_resolve_completion_request(
27827    cx: &mut EditorLspTestContext,
27828    edits: Option<Vec<(&'static str, &'static str)>>,
27829) -> impl Future<Output = ()> {
27830    let edits = edits.map(|edits| {
27831        edits
27832            .iter()
27833            .map(|(marked_string, new_text)| {
27834                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27835                let replace_range = cx.to_lsp_range(
27836                    MultiBufferOffset(marked_ranges[0].start)
27837                        ..MultiBufferOffset(marked_ranges[0].end),
27838                );
27839                lsp::TextEdit::new(replace_range, new_text.to_string())
27840            })
27841            .collect::<Vec<_>>()
27842    });
27843
27844    let mut request =
27845        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27846            let edits = edits.clone();
27847            async move {
27848                Ok(lsp::CompletionItem {
27849                    additional_text_edits: edits,
27850                    ..Default::default()
27851                })
27852            }
27853        });
27854
27855    async move {
27856        request.next().await;
27857    }
27858}
27859
27860pub(crate) fn update_test_language_settings(
27861    cx: &mut TestAppContext,
27862    f: impl Fn(&mut AllLanguageSettingsContent),
27863) {
27864    cx.update(|cx| {
27865        SettingsStore::update_global(cx, |store, cx| {
27866            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27867        });
27868    });
27869}
27870
27871pub(crate) fn update_test_project_settings(
27872    cx: &mut TestAppContext,
27873    f: impl Fn(&mut ProjectSettingsContent),
27874) {
27875    cx.update(|cx| {
27876        SettingsStore::update_global(cx, |store, cx| {
27877            store.update_user_settings(cx, |settings| f(&mut settings.project));
27878        });
27879    });
27880}
27881
27882pub(crate) fn update_test_editor_settings(
27883    cx: &mut TestAppContext,
27884    f: impl Fn(&mut EditorSettingsContent),
27885) {
27886    cx.update(|cx| {
27887        SettingsStore::update_global(cx, |store, cx| {
27888            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27889        })
27890    })
27891}
27892
27893pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27894    cx.update(|cx| {
27895        assets::Assets.load_test_fonts(cx);
27896        let store = SettingsStore::test(cx);
27897        cx.set_global(store);
27898        theme::init(theme::LoadThemes::JustBase, cx);
27899        release_channel::init(semver::Version::new(0, 0, 0), cx);
27900        crate::init(cx);
27901    });
27902    zlog::init_test();
27903    update_test_language_settings(cx, f);
27904}
27905
27906#[track_caller]
27907fn assert_hunk_revert(
27908    not_reverted_text_with_selections: &str,
27909    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27910    expected_reverted_text_with_selections: &str,
27911    base_text: &str,
27912    cx: &mut EditorLspTestContext,
27913) {
27914    cx.set_state(not_reverted_text_with_selections);
27915    cx.set_head_text(base_text);
27916    cx.executor().run_until_parked();
27917
27918    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27919        let snapshot = editor.snapshot(window, cx);
27920        let reverted_hunk_statuses = snapshot
27921            .buffer_snapshot()
27922            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27923            .map(|hunk| hunk.status().kind)
27924            .collect::<Vec<_>>();
27925
27926        editor.git_restore(&Default::default(), window, cx);
27927        reverted_hunk_statuses
27928    });
27929    cx.executor().run_until_parked();
27930    cx.assert_editor_state(expected_reverted_text_with_selections);
27931    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27932}
27933
27934#[gpui::test(iterations = 10)]
27935async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27936    init_test(cx, |_| {});
27937
27938    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27939    let counter = diagnostic_requests.clone();
27940
27941    let fs = FakeFs::new(cx.executor());
27942    fs.insert_tree(
27943        path!("/a"),
27944        json!({
27945            "first.rs": "fn main() { let a = 5; }",
27946            "second.rs": "// Test file",
27947        }),
27948    )
27949    .await;
27950
27951    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27952    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27953    let workspace = window
27954        .read_with(cx, |mw, _| mw.workspace().clone())
27955        .unwrap();
27956    let cx = &mut VisualTestContext::from_window(*window, cx);
27957
27958    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27959    language_registry.add(rust_lang());
27960    let mut fake_servers = language_registry.register_fake_lsp(
27961        "Rust",
27962        FakeLspAdapter {
27963            capabilities: lsp::ServerCapabilities {
27964                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27965                    lsp::DiagnosticOptions {
27966                        identifier: None,
27967                        inter_file_dependencies: true,
27968                        workspace_diagnostics: true,
27969                        work_done_progress_options: Default::default(),
27970                    },
27971                )),
27972                ..Default::default()
27973            },
27974            ..Default::default()
27975        },
27976    );
27977
27978    let editor = workspace
27979        .update_in(cx, |workspace, window, cx| {
27980            workspace.open_abs_path(
27981                PathBuf::from(path!("/a/first.rs")),
27982                OpenOptions::default(),
27983                window,
27984                cx,
27985            )
27986        })
27987        .await
27988        .unwrap()
27989        .downcast::<Editor>()
27990        .unwrap();
27991    let fake_server = fake_servers.next().await.unwrap();
27992    let server_id = fake_server.server.server_id();
27993    let mut first_request = fake_server
27994        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27995            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27996            let result_id = Some(new_result_id.to_string());
27997            assert_eq!(
27998                params.text_document.uri,
27999                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
28000            );
28001            async move {
28002                Ok(lsp::DocumentDiagnosticReportResult::Report(
28003                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
28004                        related_documents: None,
28005                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
28006                            items: Vec::new(),
28007                            result_id,
28008                        },
28009                    }),
28010                ))
28011            }
28012        });
28013
28014    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
28015        project.update(cx, |project, cx| {
28016            let buffer_id = editor
28017                .read(cx)
28018                .buffer()
28019                .read(cx)
28020                .as_singleton()
28021                .expect("created a singleton buffer")
28022                .read(cx)
28023                .remote_id();
28024            let buffer_result_id = project
28025                .lsp_store()
28026                .read(cx)
28027                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
28028            assert_eq!(expected_result_id, buffer_result_id);
28029        });
28030    };
28031
28032    ensure_result_id(None, cx);
28033    cx.executor().advance_clock(Duration::from_millis(60));
28034    cx.executor().run_until_parked();
28035    assert_eq!(
28036        diagnostic_requests.load(atomic::Ordering::Acquire),
28037        1,
28038        "Opening file should trigger diagnostic request"
28039    );
28040    first_request
28041        .next()
28042        .await
28043        .expect("should have sent the first diagnostics pull request");
28044    ensure_result_id(Some(SharedString::new_static("1")), cx);
28045
28046    // Editing should trigger diagnostics
28047    editor.update_in(cx, |editor, window, cx| {
28048        editor.handle_input("2", window, cx)
28049    });
28050    cx.executor().advance_clock(Duration::from_millis(60));
28051    cx.executor().run_until_parked();
28052    assert_eq!(
28053        diagnostic_requests.load(atomic::Ordering::Acquire),
28054        2,
28055        "Editing should trigger diagnostic request"
28056    );
28057    ensure_result_id(Some(SharedString::new_static("2")), cx);
28058
28059    // Moving cursor should not trigger diagnostic request
28060    editor.update_in(cx, |editor, window, cx| {
28061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28062            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
28063        });
28064    });
28065    cx.executor().advance_clock(Duration::from_millis(60));
28066    cx.executor().run_until_parked();
28067    assert_eq!(
28068        diagnostic_requests.load(atomic::Ordering::Acquire),
28069        2,
28070        "Cursor movement should not trigger diagnostic request"
28071    );
28072    ensure_result_id(Some(SharedString::new_static("2")), cx);
28073    // Multiple rapid edits should be debounced
28074    for _ in 0..5 {
28075        editor.update_in(cx, |editor, window, cx| {
28076            editor.handle_input("x", window, cx)
28077        });
28078    }
28079    cx.executor().advance_clock(Duration::from_millis(60));
28080    cx.executor().run_until_parked();
28081
28082    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
28083    assert!(
28084        final_requests <= 4,
28085        "Multiple rapid edits should be debounced (got {final_requests} requests)",
28086    );
28087    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
28088}
28089
28090#[gpui::test]
28091async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
28092    // Regression test for issue #11671
28093    // Previously, adding a cursor after moving multiple cursors would reset
28094    // the cursor count instead of adding to the existing cursors.
28095    init_test(cx, |_| {});
28096    let mut cx = EditorTestContext::new(cx).await;
28097
28098    // Create a simple buffer with cursor at start
28099    cx.set_state(indoc! {"
28100        ˇaaaa
28101        bbbb
28102        cccc
28103        dddd
28104        eeee
28105        ffff
28106        gggg
28107        hhhh"});
28108
28109    // Add 2 cursors below (so we have 3 total)
28110    cx.update_editor(|editor, window, cx| {
28111        editor.add_selection_below(&Default::default(), window, cx);
28112        editor.add_selection_below(&Default::default(), window, cx);
28113    });
28114
28115    // Verify we have 3 cursors
28116    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
28117    assert_eq!(
28118        initial_count, 3,
28119        "Should have 3 cursors after adding 2 below"
28120    );
28121
28122    // Move down one line
28123    cx.update_editor(|editor, window, cx| {
28124        editor.move_down(&MoveDown, window, cx);
28125    });
28126
28127    // Add another cursor below
28128    cx.update_editor(|editor, window, cx| {
28129        editor.add_selection_below(&Default::default(), window, cx);
28130    });
28131
28132    // Should now have 4 cursors (3 original + 1 new)
28133    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
28134    assert_eq!(
28135        final_count, 4,
28136        "Should have 4 cursors after moving and adding another"
28137    );
28138}
28139
28140#[gpui::test]
28141async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
28142    init_test(cx, |_| {});
28143
28144    let mut cx = EditorTestContext::new(cx).await;
28145
28146    cx.set_state(indoc!(
28147        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
28148           Second line here"#
28149    ));
28150
28151    cx.update_editor(|editor, window, cx| {
28152        // Enable soft wrapping with a narrow width to force soft wrapping and
28153        // confirm that more than 2 rows are being displayed.
28154        editor.set_wrap_width(Some(100.0.into()), cx);
28155        assert!(editor.display_text(cx).lines().count() > 2);
28156
28157        editor.add_selection_below(
28158            &AddSelectionBelow {
28159                skip_soft_wrap: true,
28160            },
28161            window,
28162            cx,
28163        );
28164
28165        assert_eq!(
28166            display_ranges(editor, cx),
28167            &[
28168                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
28169                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
28170            ]
28171        );
28172
28173        editor.add_selection_above(
28174            &AddSelectionAbove {
28175                skip_soft_wrap: true,
28176            },
28177            window,
28178            cx,
28179        );
28180
28181        assert_eq!(
28182            display_ranges(editor, cx),
28183            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
28184        );
28185
28186        editor.add_selection_below(
28187            &AddSelectionBelow {
28188                skip_soft_wrap: false,
28189            },
28190            window,
28191            cx,
28192        );
28193
28194        assert_eq!(
28195            display_ranges(editor, cx),
28196            &[
28197                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
28198                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
28199            ]
28200        );
28201
28202        editor.add_selection_above(
28203            &AddSelectionAbove {
28204                skip_soft_wrap: false,
28205            },
28206            window,
28207            cx,
28208        );
28209
28210        assert_eq!(
28211            display_ranges(editor, cx),
28212            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
28213        );
28214    });
28215
28216    // Set up text where selections are in the middle of a soft-wrapped line.
28217    // When adding selection below with `skip_soft_wrap` set to `true`, the new
28218    // selection should be at the same buffer column, not the same pixel
28219    // position.
28220    cx.set_state(indoc!(
28221        r#"1. Very long line to show «howˇ» a wrapped line would look
28222           2. Very long line to show how a wrapped line would look"#
28223    ));
28224
28225    cx.update_editor(|editor, window, cx| {
28226        // Enable soft wrapping with a narrow width to force soft wrapping and
28227        // confirm that more than 2 rows are being displayed.
28228        editor.set_wrap_width(Some(100.0.into()), cx);
28229        assert!(editor.display_text(cx).lines().count() > 2);
28230
28231        editor.add_selection_below(
28232            &AddSelectionBelow {
28233                skip_soft_wrap: true,
28234            },
28235            window,
28236            cx,
28237        );
28238
28239        // Assert that there's now 2 selections, both selecting the same column
28240        // range in the buffer row.
28241        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
28242        let selections = editor.selections.all::<Point>(&display_map);
28243        assert_eq!(selections.len(), 2);
28244        assert_eq!(selections[0].start.column, selections[1].start.column);
28245        assert_eq!(selections[0].end.column, selections[1].end.column);
28246    });
28247}
28248
28249#[gpui::test]
28250async fn test_insert_snippet(cx: &mut TestAppContext) {
28251    init_test(cx, |_| {});
28252    let mut cx = EditorTestContext::new(cx).await;
28253
28254    cx.update_editor(|editor, _, cx| {
28255        editor.project().unwrap().update(cx, |project, cx| {
28256            project.snippets().update(cx, |snippets, _cx| {
28257                let snippet = project::snippet_provider::Snippet {
28258                    prefix: vec![], // no prefix needed!
28259                    body: "an Unspecified".to_string(),
28260                    description: Some("shhhh it's a secret".to_string()),
28261                    name: "super secret snippet".to_string(),
28262                };
28263                snippets.add_snippet_for_test(
28264                    None,
28265                    PathBuf::from("test_snippets.json"),
28266                    vec![Arc::new(snippet)],
28267                );
28268
28269                let snippet = project::snippet_provider::Snippet {
28270                    prefix: vec![], // no prefix needed!
28271                    body: " Location".to_string(),
28272                    description: Some("the word 'location'".to_string()),
28273                    name: "location word".to_string(),
28274                };
28275                snippets.add_snippet_for_test(
28276                    Some("Markdown".to_string()),
28277                    PathBuf::from("test_snippets.json"),
28278                    vec![Arc::new(snippet)],
28279                );
28280            });
28281        })
28282    });
28283
28284    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
28285
28286    cx.update_editor(|editor, window, cx| {
28287        editor.insert_snippet_at_selections(
28288            &InsertSnippet {
28289                language: None,
28290                name: Some("super secret snippet".to_string()),
28291                snippet: None,
28292            },
28293            window,
28294            cx,
28295        );
28296
28297        // Language is specified in the action,
28298        // so the buffer language does not need to match
28299        editor.insert_snippet_at_selections(
28300            &InsertSnippet {
28301                language: Some("Markdown".to_string()),
28302                name: Some("location word".to_string()),
28303                snippet: None,
28304            },
28305            window,
28306            cx,
28307        );
28308
28309        editor.insert_snippet_at_selections(
28310            &InsertSnippet {
28311                language: None,
28312                name: None,
28313                snippet: Some("$0 after".to_string()),
28314            },
28315            window,
28316            cx,
28317        );
28318    });
28319
28320    cx.assert_editor_state(
28321        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
28322    );
28323}
28324
28325#[gpui::test]
28326async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
28327    use crate::inlays::inlay_hints::InlayHintRefreshReason;
28328    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
28329    use settings::InlayHintSettingsContent;
28330    use std::sync::atomic::AtomicU32;
28331    use std::time::Duration;
28332
28333    const BASE_TIMEOUT_SECS: u64 = 1;
28334
28335    let request_count = Arc::new(AtomicU32::new(0));
28336    let closure_request_count = request_count.clone();
28337
28338    init_test(cx, |settings| {
28339        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
28340            enabled: Some(true),
28341            ..InlayHintSettingsContent::default()
28342        })
28343    });
28344    cx.update(|cx| {
28345        SettingsStore::update_global(cx, |store, cx| {
28346            store.update_user_settings(cx, |settings| {
28347                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
28348                    request_timeout: Some(BASE_TIMEOUT_SECS),
28349                    button: Some(true),
28350                    notifications: None,
28351                    semantic_token_rules: None,
28352                });
28353            });
28354        });
28355    });
28356
28357    let fs = FakeFs::new(cx.executor());
28358    fs.insert_tree(
28359        path!("/a"),
28360        json!({
28361            "main.rs": "fn main() { let a = 5; }",
28362        }),
28363    )
28364    .await;
28365
28366    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28367    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28368    language_registry.add(rust_lang());
28369    let mut fake_servers = language_registry.register_fake_lsp(
28370        "Rust",
28371        FakeLspAdapter {
28372            capabilities: lsp::ServerCapabilities {
28373                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
28374                ..lsp::ServerCapabilities::default()
28375            },
28376            initializer: Some(Box::new(move |fake_server| {
28377                let request_count = closure_request_count.clone();
28378                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
28379                    move |params, cx| {
28380                        let request_count = request_count.clone();
28381                        async move {
28382                            cx.background_executor()
28383                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
28384                                .await;
28385                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
28386                            assert_eq!(
28387                                params.text_document.uri,
28388                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
28389                            );
28390                            Ok(Some(vec![lsp::InlayHint {
28391                                position: lsp::Position::new(0, 1),
28392                                label: lsp::InlayHintLabel::String(count.to_string()),
28393                                kind: None,
28394                                text_edits: None,
28395                                tooltip: None,
28396                                padding_left: None,
28397                                padding_right: None,
28398                                data: None,
28399                            }]))
28400                        }
28401                    },
28402                );
28403            })),
28404            ..FakeLspAdapter::default()
28405        },
28406    );
28407
28408    let buffer = project
28409        .update(cx, |project, cx| {
28410            project.open_local_buffer(path!("/a/main.rs"), cx)
28411        })
28412        .await
28413        .unwrap();
28414    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
28415
28416    cx.executor().run_until_parked();
28417    let fake_server = fake_servers.next().await.unwrap();
28418
28419    cx.executor()
28420        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28421    cx.executor().run_until_parked();
28422    editor
28423        .update(cx, |editor, _window, cx| {
28424            assert!(
28425                cached_hint_labels(editor, cx).is_empty(),
28426                "First request should time out, no hints cached"
28427            );
28428        })
28429        .unwrap();
28430
28431    editor
28432        .update(cx, |editor, _window, cx| {
28433            editor.refresh_inlay_hints(
28434                InlayHintRefreshReason::RefreshRequested {
28435                    server_id: fake_server.server.server_id(),
28436                    request_id: Some(1),
28437                },
28438                cx,
28439            );
28440        })
28441        .unwrap();
28442    cx.executor()
28443        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
28444    cx.executor().run_until_parked();
28445    editor
28446        .update(cx, |editor, _window, cx| {
28447            assert!(
28448                cached_hint_labels(editor, cx).is_empty(),
28449                "Second request should also time out with BASE_TIMEOUT, no hints cached"
28450            );
28451        })
28452        .unwrap();
28453
28454    cx.update(|cx| {
28455        SettingsStore::update_global(cx, |store, cx| {
28456            store.update_user_settings(cx, |settings| {
28457                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
28458                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
28459                    button: Some(true),
28460                    notifications: None,
28461                    semantic_token_rules: None,
28462                });
28463            });
28464        });
28465    });
28466    editor
28467        .update(cx, |editor, _window, cx| {
28468            editor.refresh_inlay_hints(
28469                InlayHintRefreshReason::RefreshRequested {
28470                    server_id: fake_server.server.server_id(),
28471                    request_id: Some(2),
28472                },
28473                cx,
28474            );
28475        })
28476        .unwrap();
28477    cx.executor()
28478        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
28479    cx.executor().run_until_parked();
28480    editor
28481        .update(cx, |editor, _window, cx| {
28482            assert_eq!(
28483                vec!["1".to_string()],
28484                cached_hint_labels(editor, cx),
28485                "With extended timeout (BASE * 4), hints should arrive successfully"
28486            );
28487            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
28488        })
28489        .unwrap();
28490}
28491
28492#[gpui::test]
28493async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
28494    init_test(cx, |_| {});
28495    let (editor, cx) = cx.add_window_view(Editor::single_line);
28496    editor.update_in(cx, |editor, window, cx| {
28497        editor.set_text("oops\n\nwow\n", window, cx)
28498    });
28499    cx.run_until_parked();
28500    editor.update(cx, |editor, cx| {
28501        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
28502    });
28503    editor.update(cx, |editor, cx| {
28504        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
28505    });
28506    cx.run_until_parked();
28507    editor.update(cx, |editor, cx| {
28508        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28509    });
28510}
28511
28512#[gpui::test]
28513async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28514    init_test(cx, |_| {});
28515
28516    cx.update(|cx| {
28517        register_project_item::<Editor>(cx);
28518    });
28519
28520    let fs = FakeFs::new(cx.executor());
28521    fs.insert_tree("/root1", json!({})).await;
28522    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28523        .await;
28524
28525    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28526    let (multi_workspace, cx) =
28527        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28528    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
28529
28530    let worktree_id = project.update(cx, |project, cx| {
28531        project.worktrees(cx).next().unwrap().read(cx).id()
28532    });
28533
28534    let handle = workspace
28535        .update_in(cx, |workspace, window, cx| {
28536            let project_path = (worktree_id, rel_path("one.pdf"));
28537            workspace.open_path(project_path, None, true, window, cx)
28538        })
28539        .await
28540        .unwrap();
28541    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28542    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28543    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28544    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28545}
28546
28547#[gpui::test]
28548async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28549    init_test(cx, |_| {});
28550
28551    let language = Arc::new(Language::new(
28552        LanguageConfig::default(),
28553        Some(tree_sitter_rust::LANGUAGE.into()),
28554    ));
28555
28556    // Test hierarchical sibling navigation
28557    let text = r#"
28558        fn outer() {
28559            if condition {
28560                let a = 1;
28561            }
28562            let b = 2;
28563        }
28564
28565        fn another() {
28566            let c = 3;
28567        }
28568    "#;
28569
28570    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28571    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28572    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28573
28574    // Wait for parsing to complete
28575    editor
28576        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28577        .await;
28578
28579    editor.update_in(cx, |editor, window, cx| {
28580        // Start by selecting "let a = 1;" inside the if block
28581        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28582            s.select_display_ranges([
28583                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28584            ]);
28585        });
28586
28587        let initial_selection = editor
28588            .selections
28589            .display_ranges(&editor.display_snapshot(cx));
28590        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28591
28592        // Test select next sibling - should move up levels to find the next sibling
28593        // Since "let a = 1;" has no siblings in the if block, it should move up
28594        // to find "let b = 2;" which is a sibling of the if block
28595        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28596        let next_selection = editor
28597            .selections
28598            .display_ranges(&editor.display_snapshot(cx));
28599
28600        // Should have a selection and it should be different from the initial
28601        assert_eq!(
28602            next_selection.len(),
28603            1,
28604            "Should have one selection after next"
28605        );
28606        assert_ne!(
28607            next_selection[0], initial_selection[0],
28608            "Next sibling selection should be different"
28609        );
28610
28611        // Test hierarchical navigation by going to the end of the current function
28612        // and trying to navigate to the next function
28613        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28614            s.select_display_ranges([
28615                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28616            ]);
28617        });
28618
28619        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28620        let function_next_selection = editor
28621            .selections
28622            .display_ranges(&editor.display_snapshot(cx));
28623
28624        // Should move to the next function
28625        assert_eq!(
28626            function_next_selection.len(),
28627            1,
28628            "Should have one selection after function next"
28629        );
28630
28631        // Test select previous sibling navigation
28632        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28633        let prev_selection = editor
28634            .selections
28635            .display_ranges(&editor.display_snapshot(cx));
28636
28637        // Should have a selection and it should be different
28638        assert_eq!(
28639            prev_selection.len(),
28640            1,
28641            "Should have one selection after prev"
28642        );
28643        assert_ne!(
28644            prev_selection[0], function_next_selection[0],
28645            "Previous sibling selection should be different from next"
28646        );
28647    });
28648}
28649
28650#[gpui::test]
28651async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28652    init_test(cx, |_| {});
28653
28654    let mut cx = EditorTestContext::new(cx).await;
28655    cx.set_state(
28656        "let ˇvariable = 42;
28657let another = variable + 1;
28658let result = variable * 2;",
28659    );
28660
28661    // Set up document highlights manually (simulating LSP response)
28662    cx.update_editor(|editor, _window, cx| {
28663        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28664
28665        // Create highlights for "variable" occurrences
28666        let highlight_ranges = [
28667            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28668            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28669            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28670        ];
28671
28672        let anchor_ranges: Vec<_> = highlight_ranges
28673            .iter()
28674            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28675            .collect();
28676
28677        editor.highlight_background(
28678            HighlightKey::DocumentHighlightRead,
28679            &anchor_ranges,
28680            |_, theme| theme.colors().editor_document_highlight_read_background,
28681            cx,
28682        );
28683    });
28684
28685    // Go to next highlight - should move to second "variable"
28686    cx.update_editor(|editor, window, cx| {
28687        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28688    });
28689    cx.assert_editor_state(
28690        "let variable = 42;
28691let another = ˇvariable + 1;
28692let result = variable * 2;",
28693    );
28694
28695    // Go to next highlight - should move to third "variable"
28696    cx.update_editor(|editor, window, cx| {
28697        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28698    });
28699    cx.assert_editor_state(
28700        "let variable = 42;
28701let another = variable + 1;
28702let result = ˇvariable * 2;",
28703    );
28704
28705    // Go to next highlight - should stay at third "variable" (no wrap-around)
28706    cx.update_editor(|editor, window, cx| {
28707        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28708    });
28709    cx.assert_editor_state(
28710        "let variable = 42;
28711let another = variable + 1;
28712let result = ˇvariable * 2;",
28713    );
28714
28715    // Now test going backwards from third position
28716    cx.update_editor(|editor, window, cx| {
28717        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28718    });
28719    cx.assert_editor_state(
28720        "let variable = 42;
28721let another = ˇvariable + 1;
28722let result = variable * 2;",
28723    );
28724
28725    // Go to previous highlight - should move to first "variable"
28726    cx.update_editor(|editor, window, cx| {
28727        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28728    });
28729    cx.assert_editor_state(
28730        "let ˇvariable = 42;
28731let another = variable + 1;
28732let result = variable * 2;",
28733    );
28734
28735    // Go to previous highlight - should stay on first "variable"
28736    cx.update_editor(|editor, window, cx| {
28737        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28738    });
28739    cx.assert_editor_state(
28740        "let ˇvariable = 42;
28741let another = variable + 1;
28742let result = variable * 2;",
28743    );
28744}
28745
28746#[gpui::test]
28747async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28748    cx: &mut gpui::TestAppContext,
28749) {
28750    init_test(cx, |_| {});
28751
28752    let url = "https://zed.dev";
28753
28754    let markdown_language = Arc::new(Language::new(
28755        LanguageConfig {
28756            name: "Markdown".into(),
28757            ..LanguageConfig::default()
28758        },
28759        None,
28760    ));
28761
28762    let mut cx = EditorTestContext::new(cx).await;
28763    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28764    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28765
28766    cx.update_editor(|editor, window, cx| {
28767        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28768        editor.paste(&Paste, window, cx);
28769    });
28770
28771    cx.assert_editor_state(&format!(
28772        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28773    ));
28774}
28775
28776#[gpui::test]
28777async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28778    init_test(cx, |_| {});
28779
28780    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28781    let mut cx = EditorTestContext::new(cx).await;
28782
28783    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28784
28785    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28786    cx.set_state(&indoc! {"
28787        - [ ] Item 1
28788            - [ ] Item 1.a
28789        - [ˇ] Item 2
28790            - [ˇ] Item 2.a
28791            - [ˇ] Item 2.b
28792        "
28793    });
28794    cx.update_editor(|editor, window, cx| {
28795        editor.handle_input("x", window, cx);
28796    });
28797    cx.run_until_parked();
28798    cx.assert_editor_state(indoc! {"
28799        - [ ] Item 1
28800            - [ ] Item 1.a
28801        - [xˇ] Item 2
28802            - [xˇ] Item 2.a
28803            - [xˇ] Item 2.b
28804        "
28805    });
28806
28807    // Case 2: Test adding new line after nested list continues the list with unchecked task
28808    cx.set_state(&indoc! {"
28809        - [ ] Item 1
28810            - [ ] Item 1.a
28811        - [x] Item 2
28812            - [x] Item 2.a
28813            - [x] Item 2.bˇ"
28814    });
28815    cx.update_editor(|editor, window, cx| {
28816        editor.newline(&Newline, window, cx);
28817    });
28818    cx.assert_editor_state(indoc! {"
28819        - [ ] Item 1
28820            - [ ] Item 1.a
28821        - [x] Item 2
28822            - [x] Item 2.a
28823            - [x] Item 2.b
28824            - [ ] ˇ"
28825    });
28826
28827    // Case 3: Test adding content to continued list item
28828    cx.update_editor(|editor, window, cx| {
28829        editor.handle_input("Item 2.c", window, cx);
28830    });
28831    cx.run_until_parked();
28832    cx.assert_editor_state(indoc! {"
28833        - [ ] Item 1
28834            - [ ] Item 1.a
28835        - [x] Item 2
28836            - [x] Item 2.a
28837            - [x] Item 2.b
28838            - [ ] Item 2.cˇ"
28839    });
28840
28841    // Case 4: Test adding new line after nested ordered list continues with next number
28842    cx.set_state(indoc! {"
28843        1. Item 1
28844            1. Item 1.a
28845        2. Item 2
28846            1. Item 2.a
28847            2. Item 2.bˇ"
28848    });
28849    cx.update_editor(|editor, window, cx| {
28850        editor.newline(&Newline, window, cx);
28851    });
28852    cx.assert_editor_state(indoc! {"
28853        1. Item 1
28854            1. Item 1.a
28855        2. Item 2
28856            1. Item 2.a
28857            2. Item 2.b
28858            3. ˇ"
28859    });
28860
28861    // Case 5: Adding content to continued ordered list item
28862    cx.update_editor(|editor, window, cx| {
28863        editor.handle_input("Item 2.c", window, cx);
28864    });
28865    cx.run_until_parked();
28866    cx.assert_editor_state(indoc! {"
28867        1. Item 1
28868            1. Item 1.a
28869        2. Item 2
28870            1. Item 2.a
28871            2. Item 2.b
28872            3. Item 2.cˇ"
28873    });
28874
28875    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28876    cx.set_state(indoc! {"
28877        - Item 1
28878            - Item 1.a
28879            - Item 1.a
28880        ˇ"});
28881    cx.update_editor(|editor, window, cx| {
28882        editor.handle_input("-", window, cx);
28883    });
28884    cx.run_until_parked();
28885    cx.assert_editor_state(indoc! {"
28886        - Item 1
28887            - Item 1.a
28888            - Item 1.a
28889"});
28890
28891    // Case 7: Test blockquote newline preserves something
28892    cx.set_state(indoc! {"
28893        > Item 1ˇ"
28894    });
28895    cx.update_editor(|editor, window, cx| {
28896        editor.newline(&Newline, window, cx);
28897    });
28898    cx.assert_editor_state(indoc! {"
28899        > Item 1
28900        ˇ"
28901    });
28902}
28903
28904#[gpui::test]
28905async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28906    cx: &mut gpui::TestAppContext,
28907) {
28908    init_test(cx, |_| {});
28909
28910    let url = "https://zed.dev";
28911
28912    let markdown_language = Arc::new(Language::new(
28913        LanguageConfig {
28914            name: "Markdown".into(),
28915            ..LanguageConfig::default()
28916        },
28917        None,
28918    ));
28919
28920    let mut cx = EditorTestContext::new(cx).await;
28921    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28922    cx.set_state(&format!(
28923        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28924    ));
28925
28926    cx.update_editor(|editor, window, cx| {
28927        editor.copy(&Copy, window, cx);
28928    });
28929
28930    cx.set_state(&format!(
28931        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28932    ));
28933
28934    cx.update_editor(|editor, window, cx| {
28935        editor.paste(&Paste, window, cx);
28936    });
28937
28938    cx.assert_editor_state(&format!(
28939        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28940    ));
28941}
28942
28943#[gpui::test]
28944async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28945    cx: &mut gpui::TestAppContext,
28946) {
28947    init_test(cx, |_| {});
28948
28949    let url = "https://zed.dev";
28950
28951    let markdown_language = Arc::new(Language::new(
28952        LanguageConfig {
28953            name: "Markdown".into(),
28954            ..LanguageConfig::default()
28955        },
28956        None,
28957    ));
28958
28959    let mut cx = EditorTestContext::new(cx).await;
28960    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28961    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28962
28963    cx.update_editor(|editor, window, cx| {
28964        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28965        editor.paste(&Paste, window, cx);
28966    });
28967
28968    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28969}
28970
28971#[gpui::test]
28972async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28973    cx: &mut gpui::TestAppContext,
28974) {
28975    init_test(cx, |_| {});
28976
28977    let text = "Awesome";
28978
28979    let markdown_language = Arc::new(Language::new(
28980        LanguageConfig {
28981            name: "Markdown".into(),
28982            ..LanguageConfig::default()
28983        },
28984        None,
28985    ));
28986
28987    let mut cx = EditorTestContext::new(cx).await;
28988    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28989    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28990
28991    cx.update_editor(|editor, window, cx| {
28992        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28993        editor.paste(&Paste, window, cx);
28994    });
28995
28996    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28997}
28998
28999#[gpui::test]
29000async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
29001    cx: &mut gpui::TestAppContext,
29002) {
29003    init_test(cx, |_| {});
29004
29005    let url = "https://zed.dev";
29006
29007    let markdown_language = Arc::new(Language::new(
29008        LanguageConfig {
29009            name: "Rust".into(),
29010            ..LanguageConfig::default()
29011        },
29012        None,
29013    ));
29014
29015    let mut cx = EditorTestContext::new(cx).await;
29016    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29017    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
29018
29019    cx.update_editor(|editor, window, cx| {
29020        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29021        editor.paste(&Paste, window, cx);
29022    });
29023
29024    cx.assert_editor_state(&format!(
29025        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
29026    ));
29027}
29028
29029#[gpui::test]
29030async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
29031    cx: &mut TestAppContext,
29032) {
29033    init_test(cx, |_| {});
29034
29035    let url = "https://zed.dev";
29036
29037    let markdown_language = Arc::new(Language::new(
29038        LanguageConfig {
29039            name: "Markdown".into(),
29040            ..LanguageConfig::default()
29041        },
29042        None,
29043    ));
29044
29045    let (editor, cx) = cx.add_window_view(|window, cx| {
29046        let multi_buffer = MultiBuffer::build_multi(
29047            [
29048                ("this will embed -> link", vec![Point::row_range(0..1)]),
29049                ("this will replace -> link", vec![Point::row_range(0..1)]),
29050            ],
29051            cx,
29052        );
29053        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
29054        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29055            s.select_ranges(vec![
29056                Point::new(0, 19)..Point::new(0, 23),
29057                Point::new(1, 21)..Point::new(1, 25),
29058            ])
29059        });
29060        let first_buffer_id = multi_buffer
29061            .read(cx)
29062            .excerpt_buffer_ids()
29063            .into_iter()
29064            .next()
29065            .unwrap();
29066        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
29067        first_buffer.update(cx, |buffer, cx| {
29068            buffer.set_language(Some(markdown_language.clone()), cx);
29069        });
29070
29071        editor
29072    });
29073    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29074
29075    cx.update_editor(|editor, window, cx| {
29076        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29077        editor.paste(&Paste, window, cx);
29078    });
29079
29080    cx.assert_editor_state(&format!(
29081        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
29082    ));
29083}
29084
29085#[gpui::test]
29086async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
29087    init_test(cx, |_| {});
29088
29089    let fs = FakeFs::new(cx.executor());
29090    fs.insert_tree(
29091        path!("/project"),
29092        json!({
29093            "first.rs": "# First Document\nSome content here.",
29094            "second.rs": "Plain text content for second file.",
29095        }),
29096    )
29097    .await;
29098
29099    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
29100    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
29101    let cx = &mut VisualTestContext::from_window(*window, cx);
29102
29103    let language = rust_lang();
29104    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29105    language_registry.add(language.clone());
29106    let mut fake_servers = language_registry.register_fake_lsp(
29107        "Rust",
29108        FakeLspAdapter {
29109            ..FakeLspAdapter::default()
29110        },
29111    );
29112
29113    let buffer1 = project
29114        .update(cx, |project, cx| {
29115            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
29116        })
29117        .await
29118        .unwrap();
29119    let buffer2 = project
29120        .update(cx, |project, cx| {
29121            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
29122        })
29123        .await
29124        .unwrap();
29125
29126    let multi_buffer = cx.new(|cx| {
29127        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
29128        multi_buffer.set_excerpts_for_path(
29129            PathKey::for_buffer(&buffer1, cx),
29130            buffer1.clone(),
29131            [Point::zero()..buffer1.read(cx).max_point()],
29132            3,
29133            cx,
29134        );
29135        multi_buffer.set_excerpts_for_path(
29136            PathKey::for_buffer(&buffer2, cx),
29137            buffer2.clone(),
29138            [Point::zero()..buffer1.read(cx).max_point()],
29139            3,
29140            cx,
29141        );
29142        multi_buffer
29143    });
29144
29145    let (editor, cx) = cx.add_window_view(|window, cx| {
29146        Editor::new(
29147            EditorMode::full(),
29148            multi_buffer,
29149            Some(project.clone()),
29150            window,
29151            cx,
29152        )
29153    });
29154
29155    let fake_language_server = fake_servers.next().await.unwrap();
29156
29157    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
29158
29159    let save = editor.update_in(cx, |editor, window, cx| {
29160        assert!(editor.is_dirty(cx));
29161
29162        editor.save(
29163            SaveOptions {
29164                format: true,
29165                autosave: true,
29166            },
29167            project,
29168            window,
29169            cx,
29170        )
29171    });
29172    let (start_edit_tx, start_edit_rx) = oneshot::channel();
29173    let (done_edit_tx, done_edit_rx) = oneshot::channel();
29174    let mut done_edit_rx = Some(done_edit_rx);
29175    let mut start_edit_tx = Some(start_edit_tx);
29176
29177    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
29178        start_edit_tx.take().unwrap().send(()).unwrap();
29179        let done_edit_rx = done_edit_rx.take().unwrap();
29180        async move {
29181            done_edit_rx.await.unwrap();
29182            Ok(None)
29183        }
29184    });
29185
29186    start_edit_rx.await.unwrap();
29187    buffer2
29188        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
29189        .unwrap();
29190
29191    done_edit_tx.send(()).unwrap();
29192
29193    save.await.unwrap();
29194    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
29195}
29196
29197#[gpui::test]
29198fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
29199    init_test(cx, |_| {});
29200
29201    let editor = cx.add_window(|window, cx| {
29202        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
29203        build_editor(buffer, window, cx)
29204    });
29205
29206    editor
29207        .update(cx, |editor, window, cx| {
29208            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29209                s.select_display_ranges([
29210                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
29211                ])
29212            });
29213
29214            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
29215
29216            assert_eq!(
29217                editor.display_text(cx),
29218                "line1\nline2\nline2",
29219                "Duplicating last line upward should create duplicate above, not on same line"
29220            );
29221
29222            assert_eq!(
29223                editor
29224                    .selections
29225                    .display_ranges(&editor.display_snapshot(cx)),
29226                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
29227                "Selection should move to the duplicated line"
29228            );
29229        })
29230        .unwrap();
29231}
29232
29233#[gpui::test]
29234async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
29235    init_test(cx, |_| {});
29236
29237    let mut cx = EditorTestContext::new(cx).await;
29238
29239    cx.set_state("line1\nline2ˇ");
29240
29241    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
29242
29243    let clipboard_text = cx
29244        .read_from_clipboard()
29245        .and_then(|item| item.text().as_deref().map(str::to_string));
29246
29247    assert_eq!(
29248        clipboard_text,
29249        Some("line2\n".to_string()),
29250        "Copying a line without trailing newline should include a newline"
29251    );
29252
29253    cx.set_state("line1\nˇ");
29254
29255    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
29256
29257    cx.assert_editor_state("line1\nline2\nˇ");
29258}
29259
29260#[gpui::test]
29261async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
29262    init_test(cx, |_| {});
29263
29264    let mut cx = EditorTestContext::new(cx).await;
29265
29266    cx.set_state("ˇline1\nˇline2\nˇline3\n");
29267
29268    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
29269
29270    let clipboard_text = cx
29271        .read_from_clipboard()
29272        .and_then(|item| item.text().as_deref().map(str::to_string));
29273
29274    assert_eq!(
29275        clipboard_text,
29276        Some("line1\nline2\nline3\n".to_string()),
29277        "Copying multiple lines should include a single newline between lines"
29278    );
29279
29280    cx.set_state("lineA\nˇ");
29281
29282    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
29283
29284    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
29285}
29286
29287#[gpui::test]
29288async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
29289    init_test(cx, |_| {});
29290
29291    let mut cx = EditorTestContext::new(cx).await;
29292
29293    cx.set_state("ˇline1\nˇline2\nˇline3\n");
29294
29295    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
29296
29297    let clipboard_text = cx
29298        .read_from_clipboard()
29299        .and_then(|item| item.text().as_deref().map(str::to_string));
29300
29301    assert_eq!(
29302        clipboard_text,
29303        Some("line1\nline2\nline3\n".to_string()),
29304        "Copying multiple lines should include a single newline between lines"
29305    );
29306
29307    cx.set_state("lineA\nˇ");
29308
29309    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
29310
29311    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
29312}
29313
29314#[gpui::test]
29315async fn test_end_of_editor_context(cx: &mut TestAppContext) {
29316    init_test(cx, |_| {});
29317
29318    let mut cx = EditorTestContext::new(cx).await;
29319
29320    cx.set_state("line1\nline2ˇ");
29321    cx.update_editor(|e, window, cx| {
29322        e.set_mode(EditorMode::SingleLine);
29323        assert!(e.key_context(window, cx).contains("end_of_input"));
29324    });
29325    cx.set_state("ˇline1\nline2");
29326    cx.update_editor(|e, window, cx| {
29327        assert!(!e.key_context(window, cx).contains("end_of_input"));
29328    });
29329    cx.set_state("line1ˇ\nline2");
29330    cx.update_editor(|e, window, cx| {
29331        assert!(!e.key_context(window, cx).contains("end_of_input"));
29332    });
29333}
29334
29335#[gpui::test]
29336async fn test_sticky_scroll(cx: &mut TestAppContext) {
29337    init_test(cx, |_| {});
29338    let mut cx = EditorTestContext::new(cx).await;
29339
29340    let buffer = indoc! {"
29341            ˇfn foo() {
29342                let abc = 123;
29343            }
29344            struct Bar;
29345            impl Bar {
29346                fn new() -> Self {
29347                    Self
29348                }
29349            }
29350            fn baz() {
29351            }
29352        "};
29353    cx.set_state(&buffer);
29354
29355    cx.update_editor(|e, _, cx| {
29356        e.buffer()
29357            .read(cx)
29358            .as_singleton()
29359            .unwrap()
29360            .update(cx, |buffer, cx| {
29361                buffer.set_language(Some(rust_lang()), cx);
29362            })
29363    });
29364
29365    let mut sticky_headers = |offset: ScrollOffset| {
29366        cx.update_editor(|e, window, cx| {
29367            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
29368        });
29369        cx.run_until_parked();
29370        cx.update_editor(|e, window, cx| {
29371            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
29372                .into_iter()
29373                .map(
29374                    |StickyHeader {
29375                         start_point,
29376                         offset,
29377                         ..
29378                     }| { (start_point, offset) },
29379                )
29380                .collect::<Vec<_>>()
29381        })
29382    };
29383
29384    let fn_foo = Point { row: 0, column: 0 };
29385    let impl_bar = Point { row: 4, column: 0 };
29386    let fn_new = Point { row: 5, column: 4 };
29387
29388    assert_eq!(sticky_headers(0.0), vec![]);
29389    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
29390    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
29391    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
29392    assert_eq!(sticky_headers(2.0), vec![]);
29393    assert_eq!(sticky_headers(2.5), vec![]);
29394    assert_eq!(sticky_headers(3.0), vec![]);
29395    assert_eq!(sticky_headers(3.5), vec![]);
29396    assert_eq!(sticky_headers(4.0), vec![]);
29397    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29398    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
29399    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
29400    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
29401    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
29402    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
29403    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
29404    assert_eq!(sticky_headers(8.0), vec![]);
29405    assert_eq!(sticky_headers(8.5), vec![]);
29406    assert_eq!(sticky_headers(9.0), vec![]);
29407    assert_eq!(sticky_headers(9.5), vec![]);
29408    assert_eq!(sticky_headers(10.0), vec![]);
29409}
29410
29411#[gpui::test]
29412async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(
29413    executor: BackgroundExecutor,
29414    cx: &mut TestAppContext,
29415) {
29416    init_test(cx, |_| {});
29417    let mut cx = EditorTestContext::new(cx).await;
29418
29419    let diff_base = indoc! {"
29420        fn foo() {
29421            let a = 1;
29422            let b = 2;
29423            let c = 3;
29424            let d = 4;
29425            let e = 5;
29426        }
29427    "};
29428
29429    let buffer = indoc! {"
29430        ˇfn foo() {
29431        }
29432    "};
29433
29434    cx.set_state(&buffer);
29435
29436    cx.update_editor(|e, _, cx| {
29437        e.buffer()
29438            .read(cx)
29439            .as_singleton()
29440            .unwrap()
29441            .update(cx, |buffer, cx| {
29442                buffer.set_language(Some(rust_lang()), cx);
29443            })
29444    });
29445
29446    cx.set_head_text(diff_base);
29447    executor.run_until_parked();
29448
29449    cx.update_editor(|editor, window, cx| {
29450        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
29451    });
29452    executor.run_until_parked();
29453
29454    // After expanding, the display should look like:
29455    //   row 0: fn foo() {
29456    //   row 1: -    let a = 1;   (deleted)
29457    //   row 2: -    let b = 2;   (deleted)
29458    //   row 3: -    let c = 3;   (deleted)
29459    //   row 4: -    let d = 4;   (deleted)
29460    //   row 5: -    let e = 5;   (deleted)
29461    //   row 6: }
29462    //
29463    // fn foo() spans display rows 0-6. Scrolling into the deleted region
29464    // (rows 1-5) should still show fn foo() as a sticky header.
29465
29466    let fn_foo = Point { row: 0, column: 0 };
29467
29468    let mut sticky_headers = |offset: ScrollOffset| {
29469        cx.update_editor(|e, window, cx| {
29470            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
29471        });
29472        cx.run_until_parked();
29473        cx.update_editor(|e, window, cx| {
29474            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
29475                .into_iter()
29476                .map(
29477                    |StickyHeader {
29478                         start_point,
29479                         offset,
29480                         ..
29481                     }| { (start_point, offset) },
29482                )
29483                .collect::<Vec<_>>()
29484        })
29485    };
29486
29487    assert_eq!(sticky_headers(0.0), vec![]);
29488    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
29489    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
29490    // Scrolling into deleted lines: fn foo() should still be a sticky header.
29491    assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]);
29492    assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]);
29493    assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]);
29494    assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]);
29495    assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]);
29496    // Past the closing brace: no more sticky header.
29497    assert_eq!(sticky_headers(6.0), vec![]);
29498}
29499
29500#[gpui::test]
29501fn test_relative_line_numbers(cx: &mut TestAppContext) {
29502    init_test(cx, |_| {});
29503
29504    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
29505    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
29506    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
29507
29508    let multibuffer = cx.new(|cx| {
29509        let mut multibuffer = MultiBuffer::new(ReadWrite);
29510        multibuffer.push_excerpts(
29511            buffer_1.clone(),
29512            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29513            cx,
29514        );
29515        multibuffer.push_excerpts(
29516            buffer_2.clone(),
29517            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29518            cx,
29519        );
29520        multibuffer.push_excerpts(
29521            buffer_3.clone(),
29522            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
29523            cx,
29524        );
29525        multibuffer
29526    });
29527
29528    // wrapped contents of multibuffer:
29529    //    aaa
29530    //    aaa
29531    //    aaa
29532    //    a
29533    //    bbb
29534    //
29535    //    ccc
29536    //    ccc
29537    //    ccc
29538    //    c
29539    //    ddd
29540    //
29541    //    eee
29542    //    fff
29543    //    fff
29544    //    fff
29545    //    f
29546
29547    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
29548    _ = editor.update(cx, |editor, window, cx| {
29549        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
29550
29551        // includes trailing newlines.
29552        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
29553        let expected_wrapped_line_numbers = [
29554            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
29555        ];
29556
29557        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29558            s.select_ranges([
29559                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
29560            ]);
29561        });
29562
29563        let snapshot = editor.snapshot(window, cx);
29564
29565        // these are all 0-indexed
29566        let base_display_row = DisplayRow(11);
29567        let base_row = 3;
29568        let wrapped_base_row = 7;
29569
29570        // test not counting wrapped lines
29571        let expected_relative_numbers = expected_line_numbers
29572            .into_iter()
29573            .enumerate()
29574            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
29575            .filter(|(_, relative_line_number)| *relative_line_number != 0)
29576            .collect_vec();
29577        let actual_relative_numbers = snapshot
29578            .calculate_relative_line_numbers(
29579                &(DisplayRow(0)..DisplayRow(24)),
29580                base_display_row,
29581                false,
29582            )
29583            .into_iter()
29584            .sorted()
29585            .collect_vec();
29586        assert_eq!(expected_relative_numbers, actual_relative_numbers);
29587        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
29588        for (display_row, relative_number) in expected_relative_numbers {
29589            assert_eq!(
29590                relative_number,
29591                snapshot
29592                    .relative_line_delta(display_row, base_display_row, false)
29593                    .unsigned_abs() as u32,
29594            );
29595        }
29596
29597        // test counting wrapped lines
29598        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29599            .into_iter()
29600            .enumerate()
29601            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29602            .filter(|(row, _)| *row != base_display_row)
29603            .collect_vec();
29604        let actual_relative_numbers = snapshot
29605            .calculate_relative_line_numbers(
29606                &(DisplayRow(0)..DisplayRow(24)),
29607                base_display_row,
29608                true,
29609            )
29610            .into_iter()
29611            .sorted()
29612            .collect_vec();
29613        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29614        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29615        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29616            assert_eq!(
29617                relative_number,
29618                snapshot
29619                    .relative_line_delta(display_row, base_display_row, true)
29620                    .unsigned_abs() as u32,
29621            );
29622        }
29623    });
29624}
29625
29626#[gpui::test]
29627async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29628    init_test(cx, |_| {});
29629    cx.update(|cx| {
29630        SettingsStore::update_global(cx, |store, cx| {
29631            store.update_user_settings(cx, |settings| {
29632                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29633                    enabled: Some(true),
29634                })
29635            });
29636        });
29637    });
29638    let mut cx = EditorTestContext::new(cx).await;
29639
29640    let line_height = cx.update_editor(|editor, window, cx| {
29641        editor
29642            .style(cx)
29643            .text
29644            .line_height_in_pixels(window.rem_size())
29645    });
29646
29647    let buffer = indoc! {"
29648            ˇfn foo() {
29649                let abc = 123;
29650            }
29651            struct Bar;
29652            impl Bar {
29653                fn new() -> Self {
29654                    Self
29655                }
29656            }
29657            fn baz() {
29658            }
29659        "};
29660    cx.set_state(&buffer);
29661
29662    cx.update_editor(|e, _, cx| {
29663        e.buffer()
29664            .read(cx)
29665            .as_singleton()
29666            .unwrap()
29667            .update(cx, |buffer, cx| {
29668                buffer.set_language(Some(rust_lang()), cx);
29669            })
29670    });
29671
29672    let fn_foo = || empty_range(0, 0);
29673    let impl_bar = || empty_range(4, 0);
29674    let fn_new = || empty_range(5, 4);
29675
29676    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29677        cx.update_editor(|e, window, cx| {
29678            e.scroll(
29679                gpui::Point {
29680                    x: 0.,
29681                    y: scroll_offset,
29682                },
29683                None,
29684                window,
29685                cx,
29686            );
29687        });
29688        cx.run_until_parked();
29689        cx.simulate_click(
29690            gpui::Point {
29691                x: px(0.),
29692                y: click_offset as f32 * line_height,
29693            },
29694            Modifiers::none(),
29695        );
29696        cx.run_until_parked();
29697        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29698    };
29699    assert_eq!(
29700        scroll_and_click(
29701            4.5, // impl Bar is halfway off the screen
29702            0.0  // click top of screen
29703        ),
29704        // scrolled to impl Bar
29705        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29706    );
29707
29708    assert_eq!(
29709        scroll_and_click(
29710            4.5,  // impl Bar is halfway off the screen
29711            0.25  // click middle of impl Bar
29712        ),
29713        // scrolled to impl Bar
29714        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29715    );
29716
29717    assert_eq!(
29718        scroll_and_click(
29719            4.5, // impl Bar is halfway off the screen
29720            1.5  // click below impl Bar (e.g. fn new())
29721        ),
29722        // scrolled to fn new() - this is below the impl Bar header which has persisted
29723        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29724    );
29725
29726    assert_eq!(
29727        scroll_and_click(
29728            5.5,  // fn new is halfway underneath impl Bar
29729            0.75  // click on the overlap of impl Bar and fn new()
29730        ),
29731        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29732    );
29733
29734    assert_eq!(
29735        scroll_and_click(
29736            5.5,  // fn new is halfway underneath impl Bar
29737            1.25  // click on the visible part of fn new()
29738        ),
29739        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29740    );
29741
29742    assert_eq!(
29743        scroll_and_click(
29744            1.5, // fn foo is halfway off the screen
29745            0.0  // click top of screen
29746        ),
29747        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29748    );
29749
29750    assert_eq!(
29751        scroll_and_click(
29752            1.5,  // fn foo is halfway off the screen
29753            0.75  // click visible part of let abc...
29754        )
29755        .0,
29756        // no change in scroll
29757        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29758        (gpui::Point { x: 0., y: 1.5 })
29759    );
29760}
29761
29762#[gpui::test]
29763async fn test_next_prev_reference(cx: &mut TestAppContext) {
29764    const CYCLE_POSITIONS: &[&'static str] = &[
29765        indoc! {"
29766            fn foo() {
29767                let ˇabc = 123;
29768                let x = abc + 1;
29769                let y = abc + 2;
29770                let z = abc + 2;
29771            }
29772        "},
29773        indoc! {"
29774            fn foo() {
29775                let abc = 123;
29776                let x = ˇabc + 1;
29777                let y = abc + 2;
29778                let z = abc + 2;
29779            }
29780        "},
29781        indoc! {"
29782            fn foo() {
29783                let abc = 123;
29784                let x = abc + 1;
29785                let y = ˇabc + 2;
29786                let z = abc + 2;
29787            }
29788        "},
29789        indoc! {"
29790            fn foo() {
29791                let abc = 123;
29792                let x = abc + 1;
29793                let y = abc + 2;
29794                let z = ˇabc + 2;
29795            }
29796        "},
29797    ];
29798
29799    init_test(cx, |_| {});
29800
29801    let mut cx = EditorLspTestContext::new_rust(
29802        lsp::ServerCapabilities {
29803            references_provider: Some(lsp::OneOf::Left(true)),
29804            ..Default::default()
29805        },
29806        cx,
29807    )
29808    .await;
29809
29810    // importantly, the cursor is in the middle
29811    cx.set_state(indoc! {"
29812        fn foo() {
29813            let aˇbc = 123;
29814            let x = abc + 1;
29815            let y = abc + 2;
29816            let z = abc + 2;
29817        }
29818    "});
29819
29820    let reference_ranges = [
29821        lsp::Position::new(1, 8),
29822        lsp::Position::new(2, 12),
29823        lsp::Position::new(3, 12),
29824        lsp::Position::new(4, 12),
29825    ]
29826    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29827
29828    cx.lsp
29829        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29830            Ok(Some(
29831                reference_ranges
29832                    .map(|range| lsp::Location {
29833                        uri: params.text_document_position.text_document.uri.clone(),
29834                        range,
29835                    })
29836                    .to_vec(),
29837            ))
29838        });
29839
29840    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29841        cx.update_editor(|editor, window, cx| {
29842            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29843        })
29844        .unwrap()
29845        .await
29846        .unwrap()
29847    };
29848
29849    _move(Direction::Next, 1, &mut cx).await;
29850    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29851
29852    _move(Direction::Next, 1, &mut cx).await;
29853    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29854
29855    _move(Direction::Next, 1, &mut cx).await;
29856    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29857
29858    // loops back to the start
29859    _move(Direction::Next, 1, &mut cx).await;
29860    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29861
29862    // loops back to the end
29863    _move(Direction::Prev, 1, &mut cx).await;
29864    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29865
29866    _move(Direction::Prev, 1, &mut cx).await;
29867    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29868
29869    _move(Direction::Prev, 1, &mut cx).await;
29870    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29871
29872    _move(Direction::Prev, 1, &mut cx).await;
29873    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29874
29875    _move(Direction::Next, 3, &mut cx).await;
29876    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29877
29878    _move(Direction::Prev, 2, &mut cx).await;
29879    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29880}
29881
29882#[gpui::test]
29883async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29884    init_test(cx, |_| {});
29885
29886    let (editor, cx) = cx.add_window_view(|window, cx| {
29887        let multi_buffer = MultiBuffer::build_multi(
29888            [
29889                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29890                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29891            ],
29892            cx,
29893        );
29894        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29895    });
29896
29897    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29898    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29899
29900    cx.assert_excerpts_with_selections(indoc! {"
29901        [EXCERPT]
29902        ˇ1
29903        2
29904        3
29905        [EXCERPT]
29906        1
29907        2
29908        3
29909        "});
29910
29911    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29912    cx.update_editor(|editor, window, cx| {
29913        editor.change_selections(None.into(), window, cx, |s| {
29914            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29915        });
29916    });
29917    cx.assert_excerpts_with_selections(indoc! {"
29918        [EXCERPT]
29919        1
2992029921        3
29922        [EXCERPT]
29923        1
29924        2
29925        3
29926        "});
29927
29928    cx.update_editor(|editor, window, cx| {
29929        editor
29930            .select_all_matches(&SelectAllMatches, window, cx)
29931            .unwrap();
29932    });
29933    cx.assert_excerpts_with_selections(indoc! {"
29934        [EXCERPT]
29935        1
2993629937        3
29938        [EXCERPT]
29939        1
2994029941        3
29942        "});
29943
29944    cx.update_editor(|editor, window, cx| {
29945        editor.handle_input("X", window, cx);
29946    });
29947    cx.assert_excerpts_with_selections(indoc! {"
29948        [EXCERPT]
29949        1
2995029951        3
29952        [EXCERPT]
29953        1
2995429955        3
29956        "});
29957
29958    // Scenario 2: Select "2", then fold second buffer before insertion
29959    cx.update_multibuffer(|mb, cx| {
29960        for buffer_id in buffer_ids.iter() {
29961            let buffer = mb.buffer(*buffer_id).unwrap();
29962            buffer.update(cx, |buffer, cx| {
29963                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29964            });
29965        }
29966    });
29967
29968    // Select "2" and select all matches
29969    cx.update_editor(|editor, window, cx| {
29970        editor.change_selections(None.into(), window, cx, |s| {
29971            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29972        });
29973        editor
29974            .select_all_matches(&SelectAllMatches, window, cx)
29975            .unwrap();
29976    });
29977
29978    // Fold second buffer - should remove selections from folded buffer
29979    cx.update_editor(|editor, _, cx| {
29980        editor.fold_buffer(buffer_ids[1], cx);
29981    });
29982    cx.assert_excerpts_with_selections(indoc! {"
29983        [EXCERPT]
29984        1
2998529986        3
29987        [EXCERPT]
29988        [FOLDED]
29989        "});
29990
29991    // Insert text - should only affect first buffer
29992    cx.update_editor(|editor, window, cx| {
29993        editor.handle_input("Y", window, cx);
29994    });
29995    cx.update_editor(|editor, _, cx| {
29996        editor.unfold_buffer(buffer_ids[1], cx);
29997    });
29998    cx.assert_excerpts_with_selections(indoc! {"
29999        [EXCERPT]
30000        1
3000130002        3
30003        [EXCERPT]
30004        1
30005        2
30006        3
30007        "});
30008
30009    // Scenario 3: Select "2", then fold first buffer before insertion
30010    cx.update_multibuffer(|mb, cx| {
30011        for buffer_id in buffer_ids.iter() {
30012            let buffer = mb.buffer(*buffer_id).unwrap();
30013            buffer.update(cx, |buffer, cx| {
30014                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
30015            });
30016        }
30017    });
30018
30019    // Select "2" and select all matches
30020    cx.update_editor(|editor, window, cx| {
30021        editor.change_selections(None.into(), window, cx, |s| {
30022            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
30023        });
30024        editor
30025            .select_all_matches(&SelectAllMatches, window, cx)
30026            .unwrap();
30027    });
30028
30029    // Fold first buffer - should remove selections from folded buffer
30030    cx.update_editor(|editor, _, cx| {
30031        editor.fold_buffer(buffer_ids[0], cx);
30032    });
30033    cx.assert_excerpts_with_selections(indoc! {"
30034        [EXCERPT]
30035        [FOLDED]
30036        [EXCERPT]
30037        1
3003830039        3
30040        "});
30041
30042    // Insert text - should only affect second buffer
30043    cx.update_editor(|editor, window, cx| {
30044        editor.handle_input("Z", window, cx);
30045    });
30046    cx.update_editor(|editor, _, cx| {
30047        editor.unfold_buffer(buffer_ids[0], cx);
30048    });
30049    cx.assert_excerpts_with_selections(indoc! {"
30050        [EXCERPT]
30051        1
30052        2
30053        3
30054        [EXCERPT]
30055        1
3005630057        3
30058        "});
30059
30060    // Test correct folded header is selected upon fold
30061    cx.update_editor(|editor, _, cx| {
30062        editor.fold_buffer(buffer_ids[0], cx);
30063        editor.fold_buffer(buffer_ids[1], cx);
30064    });
30065    cx.assert_excerpts_with_selections(indoc! {"
30066        [EXCERPT]
30067        [FOLDED]
30068        [EXCERPT]
30069        ˇ[FOLDED]
30070        "});
30071
30072    // Test selection inside folded buffer unfolds it on type
30073    cx.update_editor(|editor, window, cx| {
30074        editor.handle_input("W", window, cx);
30075    });
30076    cx.update_editor(|editor, _, cx| {
30077        editor.unfold_buffer(buffer_ids[0], cx);
30078    });
30079    cx.assert_excerpts_with_selections(indoc! {"
30080        [EXCERPT]
30081        1
30082        2
30083        3
30084        [EXCERPT]
30085        Wˇ1
30086        Z
30087        3
30088        "});
30089}
30090
30091#[gpui::test]
30092async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
30093    init_test(cx, |_| {});
30094
30095    let (editor, cx) = cx.add_window_view(|window, cx| {
30096        let multi_buffer = MultiBuffer::build_multi(
30097            [
30098                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
30099                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
30100            ],
30101            cx,
30102        );
30103        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30104    });
30105
30106    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
30107
30108    cx.assert_excerpts_with_selections(indoc! {"
30109        [EXCERPT]
30110        ˇ1
30111        2
30112        3
30113        [EXCERPT]
30114        1
30115        2
30116        3
30117        4
30118        5
30119        6
30120        7
30121        8
30122        9
30123        "});
30124
30125    cx.update_editor(|editor, window, cx| {
30126        editor.change_selections(None.into(), window, cx, |s| {
30127            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
30128        });
30129    });
30130
30131    cx.assert_excerpts_with_selections(indoc! {"
30132        [EXCERPT]
30133        1
30134        2
30135        3
30136        [EXCERPT]
30137        1
30138        2
30139        3
30140        4
30141        5
30142        6
30143        ˇ7
30144        8
30145        9
30146        "});
30147
30148    cx.update_editor(|editor, _window, cx| {
30149        editor.set_vertical_scroll_margin(0, cx);
30150    });
30151
30152    cx.update_editor(|editor, window, cx| {
30153        assert_eq!(editor.vertical_scroll_margin(), 0);
30154        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
30155        assert_eq!(
30156            editor.snapshot(window, cx).scroll_position(),
30157            gpui::Point::new(0., 12.0)
30158        );
30159    });
30160
30161    cx.update_editor(|editor, _window, cx| {
30162        editor.set_vertical_scroll_margin(3, cx);
30163    });
30164
30165    cx.update_editor(|editor, window, cx| {
30166        assert_eq!(editor.vertical_scroll_margin(), 3);
30167        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
30168        assert_eq!(
30169            editor.snapshot(window, cx).scroll_position(),
30170            gpui::Point::new(0., 9.0)
30171        );
30172    });
30173}
30174
30175#[gpui::test]
30176async fn test_find_references_single_case(cx: &mut TestAppContext) {
30177    init_test(cx, |_| {});
30178    let mut cx = EditorLspTestContext::new_rust(
30179        lsp::ServerCapabilities {
30180            references_provider: Some(lsp::OneOf::Left(true)),
30181            ..lsp::ServerCapabilities::default()
30182        },
30183        cx,
30184    )
30185    .await;
30186
30187    let before = indoc!(
30188        r#"
30189        fn main() {
30190            let aˇbc = 123;
30191            let xyz = abc;
30192        }
30193        "#
30194    );
30195    let after = indoc!(
30196        r#"
30197        fn main() {
30198            let abc = 123;
30199            let xyz = ˇabc;
30200        }
30201        "#
30202    );
30203
30204    cx.lsp
30205        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
30206            Ok(Some(vec![
30207                lsp::Location {
30208                    uri: params.text_document_position.text_document.uri.clone(),
30209                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
30210                },
30211                lsp::Location {
30212                    uri: params.text_document_position.text_document.uri,
30213                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
30214                },
30215            ]))
30216        });
30217
30218    cx.set_state(before);
30219
30220    let action = FindAllReferences {
30221        always_open_multibuffer: false,
30222    };
30223
30224    let navigated = cx
30225        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
30226        .expect("should have spawned a task")
30227        .await
30228        .unwrap();
30229
30230    assert_eq!(navigated, Navigated::No);
30231
30232    cx.run_until_parked();
30233
30234    cx.assert_editor_state(after);
30235}
30236
30237#[gpui::test]
30238async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
30239    init_test(cx, |settings| {
30240        settings.defaults.tab_size = Some(2.try_into().unwrap());
30241    });
30242
30243    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30244    let mut cx = EditorTestContext::new(cx).await;
30245    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30246
30247    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
30248    cx.set_state(indoc! {"
30249        - [ ] taskˇ
30250    "});
30251    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30252    cx.wait_for_autoindent_applied().await;
30253    cx.assert_editor_state(indoc! {"
30254        - [ ] task
30255        - [ ] ˇ
30256    "});
30257
30258    // Case 2: Works with checked task items too
30259    cx.set_state(indoc! {"
30260        - [x] completed taskˇ
30261    "});
30262    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30263    cx.wait_for_autoindent_applied().await;
30264    cx.assert_editor_state(indoc! {"
30265        - [x] completed task
30266        - [ ] ˇ
30267    "});
30268
30269    // Case 2.1: Works with uppercase checked marker too
30270    cx.set_state(indoc! {"
30271        - [X] completed taskˇ
30272    "});
30273    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30274    cx.wait_for_autoindent_applied().await;
30275    cx.assert_editor_state(indoc! {"
30276        - [X] completed task
30277        - [ ] ˇ
30278    "});
30279
30280    // Case 3: Cursor position doesn't matter - content after marker is what counts
30281    cx.set_state(indoc! {"
30282        - [ ] taˇsk
30283    "});
30284    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30285    cx.wait_for_autoindent_applied().await;
30286    cx.assert_editor_state(indoc! {"
30287        - [ ] ta
30288        - [ ] ˇsk
30289    "});
30290
30291    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
30292    cx.set_state(indoc! {"
30293        - [ ]  ˇ
30294    "});
30295    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30296    cx.wait_for_autoindent_applied().await;
30297    cx.assert_editor_state(
30298        indoc! {"
30299        - [ ]$$
30300        ˇ
30301    "}
30302        .replace("$", " ")
30303        .as_str(),
30304    );
30305
30306    // Case 5: Adding newline with content adds marker preserving indentation
30307    cx.set_state(indoc! {"
30308        - [ ] task
30309          - [ ] indentedˇ
30310    "});
30311    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30312    cx.wait_for_autoindent_applied().await;
30313    cx.assert_editor_state(indoc! {"
30314        - [ ] task
30315          - [ ] indented
30316          - [ ] ˇ
30317    "});
30318
30319    // Case 6: Adding newline with cursor right after prefix, unindents
30320    cx.set_state(indoc! {"
30321        - [ ] task
30322          - [ ] sub task
30323            - [ ] ˇ
30324    "});
30325    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30326    cx.wait_for_autoindent_applied().await;
30327    cx.assert_editor_state(indoc! {"
30328        - [ ] task
30329          - [ ] sub task
30330          - [ ] ˇ
30331    "});
30332    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30333    cx.wait_for_autoindent_applied().await;
30334
30335    // Case 7: Adding newline with cursor right after prefix, removes marker
30336    cx.assert_editor_state(indoc! {"
30337        - [ ] task
30338          - [ ] sub task
30339        - [ ] ˇ
30340    "});
30341    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30342    cx.wait_for_autoindent_applied().await;
30343    cx.assert_editor_state(indoc! {"
30344        - [ ] task
30345          - [ ] sub task
30346        ˇ
30347    "});
30348
30349    // Case 8: Cursor before or inside prefix does not add marker
30350    cx.set_state(indoc! {"
30351        ˇ- [ ] task
30352    "});
30353    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30354    cx.wait_for_autoindent_applied().await;
30355    cx.assert_editor_state(indoc! {"
30356
30357        ˇ- [ ] task
30358    "});
30359
30360    cx.set_state(indoc! {"
30361        - [ˇ ] task
30362    "});
30363    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30364    cx.wait_for_autoindent_applied().await;
30365    cx.assert_editor_state(indoc! {"
30366        - [
30367        ˇ
30368        ] task
30369    "});
30370}
30371
30372#[gpui::test]
30373async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
30374    init_test(cx, |settings| {
30375        settings.defaults.tab_size = Some(2.try_into().unwrap());
30376    });
30377
30378    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30379    let mut cx = EditorTestContext::new(cx).await;
30380    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30381
30382    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
30383    cx.set_state(indoc! {"
30384        - itemˇ
30385    "});
30386    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30387    cx.wait_for_autoindent_applied().await;
30388    cx.assert_editor_state(indoc! {"
30389        - item
30390        - ˇ
30391    "});
30392
30393    // Case 2: Works with different markers
30394    cx.set_state(indoc! {"
30395        * starred itemˇ
30396    "});
30397    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30398    cx.wait_for_autoindent_applied().await;
30399    cx.assert_editor_state(indoc! {"
30400        * starred item
30401        * ˇ
30402    "});
30403
30404    cx.set_state(indoc! {"
30405        + plus itemˇ
30406    "});
30407    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30408    cx.wait_for_autoindent_applied().await;
30409    cx.assert_editor_state(indoc! {"
30410        + plus item
30411        + ˇ
30412    "});
30413
30414    // Case 3: Cursor position doesn't matter - content after marker is what counts
30415    cx.set_state(indoc! {"
30416        - itˇem
30417    "});
30418    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30419    cx.wait_for_autoindent_applied().await;
30420    cx.assert_editor_state(indoc! {"
30421        - it
30422        - ˇem
30423    "});
30424
30425    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30426    cx.set_state(indoc! {"
30427        -  ˇ
30428    "});
30429    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30430    cx.wait_for_autoindent_applied().await;
30431    cx.assert_editor_state(
30432        indoc! {"
30433        - $
30434        ˇ
30435    "}
30436        .replace("$", " ")
30437        .as_str(),
30438    );
30439
30440    // Case 5: Adding newline with content adds marker preserving indentation
30441    cx.set_state(indoc! {"
30442        - item
30443          - indentedˇ
30444    "});
30445    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30446    cx.wait_for_autoindent_applied().await;
30447    cx.assert_editor_state(indoc! {"
30448        - item
30449          - indented
30450          - ˇ
30451    "});
30452
30453    // Case 6: Adding newline with cursor right after marker, unindents
30454    cx.set_state(indoc! {"
30455        - item
30456          - sub item
30457            - ˇ
30458    "});
30459    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30460    cx.wait_for_autoindent_applied().await;
30461    cx.assert_editor_state(indoc! {"
30462        - item
30463          - sub item
30464          - ˇ
30465    "});
30466    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30467    cx.wait_for_autoindent_applied().await;
30468
30469    // Case 7: Adding newline with cursor right after marker, removes marker
30470    cx.assert_editor_state(indoc! {"
30471        - item
30472          - sub item
30473        - ˇ
30474    "});
30475    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30476    cx.wait_for_autoindent_applied().await;
30477    cx.assert_editor_state(indoc! {"
30478        - item
30479          - sub item
30480        ˇ
30481    "});
30482
30483    // Case 8: Cursor before or inside prefix does not add marker
30484    cx.set_state(indoc! {"
30485        ˇ- item
30486    "});
30487    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30488    cx.wait_for_autoindent_applied().await;
30489    cx.assert_editor_state(indoc! {"
30490
30491        ˇ- item
30492    "});
30493
30494    cx.set_state(indoc! {"
30495        -ˇ item
30496    "});
30497    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30498    cx.wait_for_autoindent_applied().await;
30499    cx.assert_editor_state(indoc! {"
30500        -
30501        ˇitem
30502    "});
30503}
30504
30505#[gpui::test]
30506async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
30507    init_test(cx, |settings| {
30508        settings.defaults.tab_size = Some(2.try_into().unwrap());
30509    });
30510
30511    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30512    let mut cx = EditorTestContext::new(cx).await;
30513    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30514
30515    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30516    cx.set_state(indoc! {"
30517        1. first itemˇ
30518    "});
30519    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30520    cx.wait_for_autoindent_applied().await;
30521    cx.assert_editor_state(indoc! {"
30522        1. first item
30523        2. ˇ
30524    "});
30525
30526    // Case 2: Works with larger numbers
30527    cx.set_state(indoc! {"
30528        10. tenth itemˇ
30529    "});
30530    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30531    cx.wait_for_autoindent_applied().await;
30532    cx.assert_editor_state(indoc! {"
30533        10. tenth item
30534        11. ˇ
30535    "});
30536
30537    // Case 3: Cursor position doesn't matter - content after marker is what counts
30538    cx.set_state(indoc! {"
30539        1. itˇem
30540    "});
30541    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30542    cx.wait_for_autoindent_applied().await;
30543    cx.assert_editor_state(indoc! {"
30544        1. it
30545        2. ˇem
30546    "});
30547
30548    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
30549    cx.set_state(indoc! {"
30550        1.  ˇ
30551    "});
30552    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30553    cx.wait_for_autoindent_applied().await;
30554    cx.assert_editor_state(
30555        indoc! {"
30556        1. $
30557        ˇ
30558    "}
30559        .replace("$", " ")
30560        .as_str(),
30561    );
30562
30563    // Case 5: Adding newline with content adds marker preserving indentation
30564    cx.set_state(indoc! {"
30565        1. item
30566          2. indentedˇ
30567    "});
30568    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30569    cx.wait_for_autoindent_applied().await;
30570    cx.assert_editor_state(indoc! {"
30571        1. item
30572          2. indented
30573          3. ˇ
30574    "});
30575
30576    // Case 6: Adding newline with cursor right after marker, unindents
30577    cx.set_state(indoc! {"
30578        1. item
30579          2. sub item
30580            3. ˇ
30581    "});
30582    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30583    cx.wait_for_autoindent_applied().await;
30584    cx.assert_editor_state(indoc! {"
30585        1. item
30586          2. sub item
30587          1. ˇ
30588    "});
30589    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30590    cx.wait_for_autoindent_applied().await;
30591
30592    // Case 7: Adding newline with cursor right after marker, removes marker
30593    cx.assert_editor_state(indoc! {"
30594        1. item
30595          2. sub item
30596        1. ˇ
30597    "});
30598    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30599    cx.wait_for_autoindent_applied().await;
30600    cx.assert_editor_state(indoc! {"
30601        1. item
30602          2. sub item
30603        ˇ
30604    "});
30605
30606    // Case 8: Cursor before or inside prefix does not add marker
30607    cx.set_state(indoc! {"
30608        ˇ1. item
30609    "});
30610    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30611    cx.wait_for_autoindent_applied().await;
30612    cx.assert_editor_state(indoc! {"
30613
30614        ˇ1. item
30615    "});
30616
30617    cx.set_state(indoc! {"
30618        1ˇ. item
30619    "});
30620    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30621    cx.wait_for_autoindent_applied().await;
30622    cx.assert_editor_state(indoc! {"
30623        1
30624        ˇ. item
30625    "});
30626}
30627
30628#[gpui::test]
30629async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30630    init_test(cx, |settings| {
30631        settings.defaults.tab_size = Some(2.try_into().unwrap());
30632    });
30633
30634    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30635    let mut cx = EditorTestContext::new(cx).await;
30636    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30637
30638    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30639    cx.set_state(indoc! {"
30640        1. first item
30641          1. sub first item
30642          2. sub second item
30643          3. ˇ
30644    "});
30645    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30646    cx.wait_for_autoindent_applied().await;
30647    cx.assert_editor_state(indoc! {"
30648        1. first item
30649          1. sub first item
30650          2. sub second item
30651        1. ˇ
30652    "});
30653}
30654
30655#[gpui::test]
30656async fn test_tab_list_indent(cx: &mut TestAppContext) {
30657    init_test(cx, |settings| {
30658        settings.defaults.tab_size = Some(2.try_into().unwrap());
30659    });
30660
30661    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30662    let mut cx = EditorTestContext::new(cx).await;
30663    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30664
30665    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30666    cx.set_state(indoc! {"
30667        - ˇitem
30668    "});
30669    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30670    cx.wait_for_autoindent_applied().await;
30671    let expected = indoc! {"
30672        $$- ˇitem
30673    "};
30674    cx.assert_editor_state(expected.replace("$", " ").as_str());
30675
30676    // Case 2: Task list - cursor after prefix
30677    cx.set_state(indoc! {"
30678        - [ ] ˇtask
30679    "});
30680    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30681    cx.wait_for_autoindent_applied().await;
30682    let expected = indoc! {"
30683        $$- [ ] ˇtask
30684    "};
30685    cx.assert_editor_state(expected.replace("$", " ").as_str());
30686
30687    // Case 3: Ordered list - cursor after prefix
30688    cx.set_state(indoc! {"
30689        1. ˇfirst
30690    "});
30691    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30692    cx.wait_for_autoindent_applied().await;
30693    let expected = indoc! {"
30694        $$1. ˇfirst
30695    "};
30696    cx.assert_editor_state(expected.replace("$", " ").as_str());
30697
30698    // Case 4: With existing indentation - adds more indent
30699    let initial = indoc! {"
30700        $$- ˇitem
30701    "};
30702    cx.set_state(initial.replace("$", " ").as_str());
30703    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30704    cx.wait_for_autoindent_applied().await;
30705    let expected = indoc! {"
30706        $$$$- ˇitem
30707    "};
30708    cx.assert_editor_state(expected.replace("$", " ").as_str());
30709
30710    // Case 5: Empty list item
30711    cx.set_state(indoc! {"
30712        - ˇ
30713    "});
30714    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30715    cx.wait_for_autoindent_applied().await;
30716    let expected = indoc! {"
30717        $$- ˇ
30718    "};
30719    cx.assert_editor_state(expected.replace("$", " ").as_str());
30720
30721    // Case 6: Cursor at end of line with content
30722    cx.set_state(indoc! {"
30723        - itemˇ
30724    "});
30725    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30726    cx.wait_for_autoindent_applied().await;
30727    let expected = indoc! {"
30728        $$- itemˇ
30729    "};
30730    cx.assert_editor_state(expected.replace("$", " ").as_str());
30731
30732    // Case 7: Cursor at start of list item, indents it
30733    cx.set_state(indoc! {"
30734        - item
30735        ˇ  - sub item
30736    "});
30737    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30738    cx.wait_for_autoindent_applied().await;
30739    let expected = indoc! {"
30740        - item
30741          ˇ  - sub item
30742    "};
30743    cx.assert_editor_state(expected);
30744
30745    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30746    cx.update_editor(|_, _, cx| {
30747        SettingsStore::update_global(cx, |store, cx| {
30748            store.update_user_settings(cx, |settings| {
30749                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30750            });
30751        });
30752    });
30753    cx.set_state(indoc! {"
30754        - item
30755        ˇ  - sub item
30756    "});
30757    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30758    cx.wait_for_autoindent_applied().await;
30759    let expected = indoc! {"
30760        - item
30761          ˇ- sub item
30762    "};
30763    cx.assert_editor_state(expected);
30764}
30765
30766#[gpui::test]
30767async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30768    init_test(cx, |_| {});
30769    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30770
30771    cx.update(|cx| {
30772        SettingsStore::update_global(cx, |store, cx| {
30773            store.update_user_settings(cx, |settings| {
30774                settings.project.all_languages.defaults.inlay_hints =
30775                    Some(InlayHintSettingsContent {
30776                        enabled: Some(true),
30777                        ..InlayHintSettingsContent::default()
30778                    });
30779            });
30780        });
30781    });
30782
30783    let fs = FakeFs::new(cx.executor());
30784    fs.insert_tree(
30785        path!("/project"),
30786        json!({
30787            ".zed": {
30788                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30789            },
30790            "main.rs": "fn main() {}"
30791        }),
30792    )
30793    .await;
30794
30795    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30796    let server_name = "override-rust-analyzer";
30797    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30798
30799    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30800    language_registry.add(rust_lang());
30801
30802    let capabilities = lsp::ServerCapabilities {
30803        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30804        ..lsp::ServerCapabilities::default()
30805    };
30806    let mut fake_language_servers = language_registry.register_fake_lsp(
30807        "Rust",
30808        FakeLspAdapter {
30809            name: server_name,
30810            capabilities,
30811            initializer: Some(Box::new({
30812                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30813                move |fake_server| {
30814                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30815                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30816                        move |_params, _| {
30817                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30818                            async move {
30819                                Ok(Some(vec![lsp::InlayHint {
30820                                    position: lsp::Position::new(0, 0),
30821                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30822                                    kind: None,
30823                                    text_edits: None,
30824                                    tooltip: None,
30825                                    padding_left: None,
30826                                    padding_right: None,
30827                                    data: None,
30828                                }]))
30829                            }
30830                        },
30831                    );
30832                }
30833            })),
30834            ..FakeLspAdapter::default()
30835        },
30836    );
30837
30838    cx.run_until_parked();
30839
30840    let worktree_id = project.read_with(cx, |project, cx| {
30841        project
30842            .worktrees(cx)
30843            .next()
30844            .map(|wt| wt.read(cx).id())
30845            .expect("should have a worktree")
30846    });
30847    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30848
30849    let trusted_worktrees =
30850        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30851
30852    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30853        store.can_trust(&worktree_store, worktree_id, cx)
30854    });
30855    assert!(!can_trust, "worktree should be restricted initially");
30856
30857    let buffer_before_approval = project
30858        .update(cx, |project, cx| {
30859            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30860        })
30861        .await
30862        .unwrap();
30863
30864    let (editor, cx) = cx.add_window_view(|window, cx| {
30865        Editor::new(
30866            EditorMode::full(),
30867            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30868            Some(project.clone()),
30869            window,
30870            cx,
30871        )
30872    });
30873    cx.run_until_parked();
30874    let fake_language_server = fake_language_servers.next();
30875
30876    cx.read(|cx| {
30877        let file = buffer_before_approval.read(cx).file();
30878        assert_eq!(
30879            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30880                .language_servers,
30881            ["...".to_string()],
30882            "local .zed/settings.json must not apply before trust approval"
30883        )
30884    });
30885
30886    editor.update_in(cx, |editor, window, cx| {
30887        editor.handle_input("1", window, cx);
30888    });
30889    cx.run_until_parked();
30890    cx.executor()
30891        .advance_clock(std::time::Duration::from_secs(1));
30892    assert_eq!(
30893        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30894        0,
30895        "inlay hints must not be queried before trust approval"
30896    );
30897
30898    trusted_worktrees.update(cx, |store, cx| {
30899        store.trust(
30900            &worktree_store,
30901            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30902            cx,
30903        );
30904    });
30905    cx.run_until_parked();
30906
30907    cx.read(|cx| {
30908        let file = buffer_before_approval.read(cx).file();
30909        assert_eq!(
30910            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30911                .language_servers,
30912            ["override-rust-analyzer".to_string()],
30913            "local .zed/settings.json should apply after trust approval"
30914        )
30915    });
30916    let _fake_language_server = fake_language_server.await.unwrap();
30917    editor.update_in(cx, |editor, window, cx| {
30918        editor.handle_input("1", window, cx);
30919    });
30920    cx.run_until_parked();
30921    cx.executor()
30922        .advance_clock(std::time::Duration::from_secs(1));
30923    assert!(
30924        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30925        "inlay hints should be queried after trust approval"
30926    );
30927
30928    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30929        store.can_trust(&worktree_store, worktree_id, cx)
30930    });
30931    assert!(can_trust_after, "worktree should be trusted after trust()");
30932}
30933
30934#[gpui::test]
30935fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30936    // This test reproduces a bug where drawing an editor at a position above the viewport
30937    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30938    // causes an infinite loop in blocks_in_range.
30939    //
30940    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30941    // the content mask intersection produces visible_bounds with origin at the viewport top.
30942    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30943    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30944    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30945    init_test(cx, |_| {});
30946
30947    let window = cx.add_window(|_, _| gpui::Empty);
30948    let mut cx = VisualTestContext::from_window(*window, cx);
30949
30950    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30951    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30952
30953    // Simulate a small viewport (500x500 pixels at origin 0,0)
30954    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30955
30956    // Draw the editor at a very negative Y position, simulating an editor that's been
30957    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30958    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30959    // This should NOT hang - it should just render nothing.
30960    cx.draw(
30961        gpui::point(px(0.), px(-10000.)),
30962        gpui::size(px(500.), px(3000.)),
30963        |_, _| editor.clone().into_any_element(),
30964    );
30965
30966    // If we get here without hanging, the test passes
30967}
30968
30969#[gpui::test]
30970async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30971    init_test(cx, |_| {});
30972
30973    let fs = FakeFs::new(cx.executor());
30974    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30975        .await;
30976
30977    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30978    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30979    let workspace = window
30980        .read_with(cx, |mw, _| mw.workspace().clone())
30981        .unwrap();
30982    let cx = &mut VisualTestContext::from_window(*window, cx);
30983
30984    let editor = workspace
30985        .update_in(cx, |workspace, window, cx| {
30986            workspace.open_abs_path(
30987                PathBuf::from(path!("/root/file.txt")),
30988                OpenOptions::default(),
30989                window,
30990                cx,
30991            )
30992        })
30993        .await
30994        .unwrap()
30995        .downcast::<Editor>()
30996        .unwrap();
30997
30998    // Enable diff review button mode
30999    editor.update(cx, |editor, cx| {
31000        editor.set_show_diff_review_button(true, cx);
31001    });
31002
31003    // Initially, no indicator should be present
31004    editor.update(cx, |editor, _cx| {
31005        assert!(
31006            editor.gutter_diff_review_indicator.0.is_none(),
31007            "Indicator should be None initially"
31008        );
31009    });
31010}
31011
31012#[gpui::test]
31013async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
31014    init_test(cx, |_| {});
31015
31016    // Register DisableAiSettings and set disable_ai to true
31017    cx.update(|cx| {
31018        project::DisableAiSettings::register(cx);
31019        project::DisableAiSettings::override_global(
31020            project::DisableAiSettings { disable_ai: true },
31021            cx,
31022        );
31023    });
31024
31025    let fs = FakeFs::new(cx.executor());
31026    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
31027        .await;
31028
31029    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
31030    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31031    let workspace = window
31032        .read_with(cx, |mw, _| mw.workspace().clone())
31033        .unwrap();
31034    let cx = &mut VisualTestContext::from_window(*window, cx);
31035
31036    let editor = workspace
31037        .update_in(cx, |workspace, window, cx| {
31038            workspace.open_abs_path(
31039                PathBuf::from(path!("/root/file.txt")),
31040                OpenOptions::default(),
31041                window,
31042                cx,
31043            )
31044        })
31045        .await
31046        .unwrap()
31047        .downcast::<Editor>()
31048        .unwrap();
31049
31050    // Enable diff review button mode
31051    editor.update(cx, |editor, cx| {
31052        editor.set_show_diff_review_button(true, cx);
31053    });
31054
31055    // Verify AI is disabled
31056    cx.read(|cx| {
31057        assert!(
31058            project::DisableAiSettings::get_global(cx).disable_ai,
31059            "AI should be disabled"
31060        );
31061    });
31062
31063    // The indicator should not be created when AI is disabled
31064    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
31065    editor.update(cx, |editor, _cx| {
31066        assert!(
31067            editor.gutter_diff_review_indicator.0.is_none(),
31068            "Indicator should be None when AI is disabled"
31069        );
31070    });
31071}
31072
31073#[gpui::test]
31074async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
31075    init_test(cx, |_| {});
31076
31077    // Register DisableAiSettings and set disable_ai to false
31078    cx.update(|cx| {
31079        project::DisableAiSettings::register(cx);
31080        project::DisableAiSettings::override_global(
31081            project::DisableAiSettings { disable_ai: false },
31082            cx,
31083        );
31084    });
31085
31086    let fs = FakeFs::new(cx.executor());
31087    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
31088        .await;
31089
31090    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
31091    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31092    let workspace = window
31093        .read_with(cx, |mw, _| mw.workspace().clone())
31094        .unwrap();
31095    let cx = &mut VisualTestContext::from_window(*window, cx);
31096
31097    let editor = workspace
31098        .update_in(cx, |workspace, window, cx| {
31099            workspace.open_abs_path(
31100                PathBuf::from(path!("/root/file.txt")),
31101                OpenOptions::default(),
31102                window,
31103                cx,
31104            )
31105        })
31106        .await
31107        .unwrap()
31108        .downcast::<Editor>()
31109        .unwrap();
31110
31111    // Enable diff review button mode
31112    editor.update(cx, |editor, cx| {
31113        editor.set_show_diff_review_button(true, cx);
31114    });
31115
31116    // Verify AI is enabled
31117    cx.read(|cx| {
31118        assert!(
31119            !project::DisableAiSettings::get_global(cx).disable_ai,
31120            "AI should be enabled"
31121        );
31122    });
31123
31124    // The show_diff_review_button flag should be true
31125    editor.update(cx, |editor, _cx| {
31126        assert!(
31127            editor.show_diff_review_button(),
31128            "show_diff_review_button should be true"
31129        );
31130    });
31131}
31132
31133/// Helper function to create a DiffHunkKey for testing.
31134/// Uses Anchor::min() as a placeholder anchor since these tests don't need
31135/// real buffer positioning.
31136fn test_hunk_key(file_path: &str) -> DiffHunkKey {
31137    DiffHunkKey {
31138        file_path: if file_path.is_empty() {
31139            Arc::from(util::rel_path::RelPath::empty())
31140        } else {
31141            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
31142        },
31143        hunk_start_anchor: Anchor::min(),
31144    }
31145}
31146
31147/// Helper function to create a DiffHunkKey with a specific anchor for testing.
31148fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
31149    DiffHunkKey {
31150        file_path: if file_path.is_empty() {
31151            Arc::from(util::rel_path::RelPath::empty())
31152        } else {
31153            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
31154        },
31155        hunk_start_anchor: anchor,
31156    }
31157}
31158
31159/// Helper function to add a review comment with default anchors for testing.
31160fn add_test_comment(
31161    editor: &mut Editor,
31162    key: DiffHunkKey,
31163    comment: &str,
31164    cx: &mut Context<Editor>,
31165) -> usize {
31166    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
31167}
31168
31169#[gpui::test]
31170fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
31171    init_test(cx, |_| {});
31172
31173    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31174
31175    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31176        let key = test_hunk_key("");
31177
31178        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
31179
31180        let snapshot = editor.buffer().read(cx).snapshot(cx);
31181        assert_eq!(editor.total_review_comment_count(), 1);
31182        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
31183
31184        let comments = editor.comments_for_hunk(&key, &snapshot);
31185        assert_eq!(comments.len(), 1);
31186        assert_eq!(comments[0].comment, "Test comment");
31187        assert_eq!(comments[0].id, id);
31188    });
31189}
31190
31191#[gpui::test]
31192fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
31193    init_test(cx, |_| {});
31194
31195    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31196
31197    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31198        let snapshot = editor.buffer().read(cx).snapshot(cx);
31199        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
31200        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
31201        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
31202        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
31203
31204        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
31205        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
31206
31207        let snapshot = editor.buffer().read(cx).snapshot(cx);
31208        assert_eq!(editor.total_review_comment_count(), 2);
31209        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
31210        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
31211
31212        assert_eq!(
31213            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
31214            "Comment for file1"
31215        );
31216        assert_eq!(
31217            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
31218            "Comment for file2"
31219        );
31220    });
31221}
31222
31223#[gpui::test]
31224fn test_review_comment_remove(cx: &mut TestAppContext) {
31225    init_test(cx, |_| {});
31226
31227    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31228
31229    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31230        let key = test_hunk_key("");
31231
31232        let id = add_test_comment(editor, key, "To be removed", cx);
31233
31234        assert_eq!(editor.total_review_comment_count(), 1);
31235
31236        let removed = editor.remove_review_comment(id, cx);
31237        assert!(removed);
31238        assert_eq!(editor.total_review_comment_count(), 0);
31239
31240        // Try to remove again
31241        let removed_again = editor.remove_review_comment(id, cx);
31242        assert!(!removed_again);
31243    });
31244}
31245
31246#[gpui::test]
31247fn test_review_comment_update(cx: &mut TestAppContext) {
31248    init_test(cx, |_| {});
31249
31250    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31251
31252    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31253        let key = test_hunk_key("");
31254
31255        let id = add_test_comment(editor, key.clone(), "Original text", cx);
31256
31257        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
31258        assert!(updated);
31259
31260        let snapshot = editor.buffer().read(cx).snapshot(cx);
31261        let comments = editor.comments_for_hunk(&key, &snapshot);
31262        assert_eq!(comments[0].comment, "Updated text");
31263        assert!(!comments[0].is_editing); // Should clear editing flag
31264    });
31265}
31266
31267#[gpui::test]
31268fn test_review_comment_take_all(cx: &mut TestAppContext) {
31269    init_test(cx, |_| {});
31270
31271    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31272
31273    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31274        let snapshot = editor.buffer().read(cx).snapshot(cx);
31275        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
31276        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
31277        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
31278        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
31279
31280        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
31281        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
31282        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
31283
31284        // IDs should be sequential starting from 0
31285        assert_eq!(id1, 0);
31286        assert_eq!(id2, 1);
31287        assert_eq!(id3, 2);
31288
31289        assert_eq!(editor.total_review_comment_count(), 3);
31290
31291        let taken = editor.take_all_review_comments(cx);
31292
31293        // Should have 2 entries (one per hunk)
31294        assert_eq!(taken.len(), 2);
31295
31296        // Total comments should be 3
31297        let total: usize = taken
31298            .iter()
31299            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
31300            .sum();
31301        assert_eq!(total, 3);
31302
31303        // Storage should be empty
31304        assert_eq!(editor.total_review_comment_count(), 0);
31305
31306        // After taking all comments, ID counter should reset
31307        // New comments should get IDs starting from 0 again
31308        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
31309        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
31310
31311        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
31312        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
31313    });
31314}
31315
31316#[gpui::test]
31317fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
31318    init_test(cx, |_| {});
31319
31320    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31321
31322    // Show overlay
31323    editor
31324        .update(cx, |editor, window, cx| {
31325            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31326        })
31327        .unwrap();
31328
31329    // Verify overlay is shown
31330    editor
31331        .update(cx, |editor, _window, cx| {
31332            assert!(!editor.diff_review_overlays.is_empty());
31333            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
31334            assert!(editor.diff_review_prompt_editor().is_some());
31335        })
31336        .unwrap();
31337
31338    // Dismiss overlay
31339    editor
31340        .update(cx, |editor, _window, cx| {
31341            editor.dismiss_all_diff_review_overlays(cx);
31342        })
31343        .unwrap();
31344
31345    // Verify overlay is dismissed
31346    editor
31347        .update(cx, |editor, _window, cx| {
31348            assert!(editor.diff_review_overlays.is_empty());
31349            assert_eq!(editor.diff_review_line_range(cx), None);
31350            assert!(editor.diff_review_prompt_editor().is_none());
31351        })
31352        .unwrap();
31353}
31354
31355#[gpui::test]
31356fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
31357    init_test(cx, |_| {});
31358
31359    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31360
31361    // Show overlay
31362    editor
31363        .update(cx, |editor, window, cx| {
31364            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31365        })
31366        .unwrap();
31367
31368    // Verify overlay is shown
31369    editor
31370        .update(cx, |editor, _window, _cx| {
31371            assert!(!editor.diff_review_overlays.is_empty());
31372        })
31373        .unwrap();
31374
31375    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
31376    editor
31377        .update(cx, |editor, window, cx| {
31378            editor.dismiss_menus_and_popups(true, window, cx);
31379        })
31380        .unwrap();
31381
31382    // Verify overlay is dismissed
31383    editor
31384        .update(cx, |editor, _window, _cx| {
31385            assert!(editor.diff_review_overlays.is_empty());
31386        })
31387        .unwrap();
31388}
31389
31390#[gpui::test]
31391fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
31392    init_test(cx, |_| {});
31393
31394    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31395
31396    // Show overlay
31397    editor
31398        .update(cx, |editor, window, cx| {
31399            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31400        })
31401        .unwrap();
31402
31403    // Try to submit without typing anything (empty comment)
31404    editor
31405        .update(cx, |editor, window, cx| {
31406            editor.submit_diff_review_comment(window, cx);
31407        })
31408        .unwrap();
31409
31410    // Verify no comment was added
31411    editor
31412        .update(cx, |editor, _window, _cx| {
31413            assert_eq!(editor.total_review_comment_count(), 0);
31414        })
31415        .unwrap();
31416
31417    // Try to submit with whitespace-only comment
31418    editor
31419        .update(cx, |editor, window, cx| {
31420            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
31421                prompt_editor.update(cx, |pe, cx| {
31422                    pe.insert("   \n\t  ", window, cx);
31423                });
31424            }
31425            editor.submit_diff_review_comment(window, cx);
31426        })
31427        .unwrap();
31428
31429    // Verify still no comment was added
31430    editor
31431        .update(cx, |editor, _window, _cx| {
31432            assert_eq!(editor.total_review_comment_count(), 0);
31433        })
31434        .unwrap();
31435}
31436
31437#[gpui::test]
31438fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
31439    init_test(cx, |_| {});
31440
31441    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31442
31443    // Add a comment directly
31444    let comment_id = editor
31445        .update(cx, |editor, _window, cx| {
31446            let key = test_hunk_key("");
31447            add_test_comment(editor, key, "Original comment", cx)
31448        })
31449        .unwrap();
31450
31451    // Set comment to editing mode
31452    editor
31453        .update(cx, |editor, _window, cx| {
31454            editor.set_comment_editing(comment_id, true, cx);
31455        })
31456        .unwrap();
31457
31458    // Verify editing flag is set
31459    editor
31460        .update(cx, |editor, _window, cx| {
31461            let key = test_hunk_key("");
31462            let snapshot = editor.buffer().read(cx).snapshot(cx);
31463            let comments = editor.comments_for_hunk(&key, &snapshot);
31464            assert_eq!(comments.len(), 1);
31465            assert!(comments[0].is_editing);
31466        })
31467        .unwrap();
31468
31469    // Update the comment
31470    editor
31471        .update(cx, |editor, _window, cx| {
31472            let updated =
31473                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
31474            assert!(updated);
31475        })
31476        .unwrap();
31477
31478    // Verify comment was updated and editing flag is cleared
31479    editor
31480        .update(cx, |editor, _window, cx| {
31481            let key = test_hunk_key("");
31482            let snapshot = editor.buffer().read(cx).snapshot(cx);
31483            let comments = editor.comments_for_hunk(&key, &snapshot);
31484            assert_eq!(comments[0].comment, "Updated comment");
31485            assert!(!comments[0].is_editing);
31486        })
31487        .unwrap();
31488}
31489
31490#[gpui::test]
31491fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
31492    init_test(cx, |_| {});
31493
31494    // Create an editor with some text
31495    let editor = cx.add_window(|window, cx| {
31496        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31497        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31498        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31499    });
31500
31501    // Add a comment with an anchor on line 2
31502    editor
31503        .update(cx, |editor, _window, cx| {
31504            let snapshot = editor.buffer().read(cx).snapshot(cx);
31505            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31506            let key = DiffHunkKey {
31507                file_path: Arc::from(util::rel_path::RelPath::empty()),
31508                hunk_start_anchor: anchor,
31509            };
31510            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31511            assert_eq!(editor.total_review_comment_count(), 1);
31512        })
31513        .unwrap();
31514
31515    // Delete all content (this should orphan the comment's anchor)
31516    editor
31517        .update(cx, |editor, window, cx| {
31518            editor.select_all(&SelectAll, window, cx);
31519            editor.insert("completely new content", window, cx);
31520        })
31521        .unwrap();
31522
31523    // Trigger cleanup
31524    editor
31525        .update(cx, |editor, _window, cx| {
31526            editor.cleanup_orphaned_review_comments(cx);
31527            // Comment should be removed because its anchor is invalid
31528            assert_eq!(editor.total_review_comment_count(), 0);
31529        })
31530        .unwrap();
31531}
31532
31533#[gpui::test]
31534fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
31535    init_test(cx, |_| {});
31536
31537    // Create an editor with some text
31538    let editor = cx.add_window(|window, cx| {
31539        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31540        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31541        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31542    });
31543
31544    // Add a comment with an anchor on line 2
31545    editor
31546        .update(cx, |editor, _window, cx| {
31547            let snapshot = editor.buffer().read(cx).snapshot(cx);
31548            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
31549            let key = DiffHunkKey {
31550                file_path: Arc::from(util::rel_path::RelPath::empty()),
31551                hunk_start_anchor: anchor,
31552            };
31553            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
31554            assert_eq!(editor.total_review_comment_count(), 1);
31555        })
31556        .unwrap();
31557
31558    // Edit the buffer - this should trigger cleanup via on_buffer_event
31559    // Delete all content which orphans the anchor
31560    editor
31561        .update(cx, |editor, window, cx| {
31562            editor.select_all(&SelectAll, window, cx);
31563            editor.insert("completely new content", window, cx);
31564            // The cleanup is called automatically in on_buffer_event when Edited fires
31565        })
31566        .unwrap();
31567
31568    // Verify cleanup happened automatically (not manually triggered)
31569    editor
31570        .update(cx, |editor, _window, _cx| {
31571            // Comment should be removed because its anchor became invalid
31572            // and cleanup was called automatically on buffer edit
31573            assert_eq!(editor.total_review_comment_count(), 0);
31574        })
31575        .unwrap();
31576}
31577
31578#[gpui::test]
31579fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
31580    init_test(cx, |_| {});
31581
31582    // This test verifies that comments can be stored for multiple different hunks
31583    // and that hunk_comment_count correctly identifies comments per hunk.
31584    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31585
31586    _ = editor.update(cx, |editor, _window, cx| {
31587        let snapshot = editor.buffer().read(cx).snapshot(cx);
31588
31589        // Create two different hunk keys (simulating two different files)
31590        let anchor = snapshot.anchor_before(Point::new(0, 0));
31591        let key1 = DiffHunkKey {
31592            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
31593            hunk_start_anchor: anchor,
31594        };
31595        let key2 = DiffHunkKey {
31596            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31597            hunk_start_anchor: anchor,
31598        };
31599
31600        // Add comments to first hunk
31601        editor.add_review_comment(
31602            key1.clone(),
31603            "Comment 1 for file1".to_string(),
31604            anchor..anchor,
31605            cx,
31606        );
31607        editor.add_review_comment(
31608            key1.clone(),
31609            "Comment 2 for file1".to_string(),
31610            anchor..anchor,
31611            cx,
31612        );
31613
31614        // Add comment to second hunk
31615        editor.add_review_comment(
31616            key2.clone(),
31617            "Comment for file2".to_string(),
31618            anchor..anchor,
31619            cx,
31620        );
31621
31622        // Verify total count
31623        assert_eq!(editor.total_review_comment_count(), 3);
31624
31625        // Verify per-hunk counts
31626        let snapshot = editor.buffer().read(cx).snapshot(cx);
31627        assert_eq!(
31628            editor.hunk_comment_count(&key1, &snapshot),
31629            2,
31630            "file1 should have 2 comments"
31631        );
31632        assert_eq!(
31633            editor.hunk_comment_count(&key2, &snapshot),
31634            1,
31635            "file2 should have 1 comment"
31636        );
31637
31638        // Verify comments_for_hunk returns correct comments
31639        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31640        assert_eq!(file1_comments.len(), 2);
31641        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31642        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31643
31644        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31645        assert_eq!(file2_comments.len(), 1);
31646        assert_eq!(file2_comments[0].comment, "Comment for file2");
31647    });
31648}
31649
31650#[gpui::test]
31651fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31652    init_test(cx, |_| {});
31653
31654    // This test verifies that hunk_keys_match correctly identifies when two
31655    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31656    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31657
31658    _ = editor.update(cx, |editor, _window, cx| {
31659        let snapshot = editor.buffer().read(cx).snapshot(cx);
31660        let anchor = snapshot.anchor_before(Point::new(0, 0));
31661
31662        // Create two keys with the same file path and anchor
31663        let key1 = DiffHunkKey {
31664            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31665            hunk_start_anchor: anchor,
31666        };
31667        let key2 = DiffHunkKey {
31668            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31669            hunk_start_anchor: anchor,
31670        };
31671
31672        // Add comment to first key
31673        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
31674
31675        // Verify second key (same hunk) finds the comment
31676        let snapshot = editor.buffer().read(cx).snapshot(cx);
31677        assert_eq!(
31678            editor.hunk_comment_count(&key2, &snapshot),
31679            1,
31680            "Same hunk should find the comment"
31681        );
31682
31683        // Create a key with different file path
31684        let different_file_key = DiffHunkKey {
31685            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31686            hunk_start_anchor: anchor,
31687        };
31688
31689        // Different file should not find the comment
31690        assert_eq!(
31691            editor.hunk_comment_count(&different_file_key, &snapshot),
31692            0,
31693            "Different file should not find the comment"
31694        );
31695    });
31696}
31697
31698#[gpui::test]
31699fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31700    init_test(cx, |_| {});
31701
31702    // This test verifies that set_diff_review_comments_expanded correctly
31703    // updates the expanded state of overlays.
31704    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31705
31706    // Show overlay
31707    editor
31708        .update(cx, |editor, window, cx| {
31709            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31710        })
31711        .unwrap();
31712
31713    // Verify initially expanded (default)
31714    editor
31715        .update(cx, |editor, _window, _cx| {
31716            assert!(
31717                editor.diff_review_overlays[0].comments_expanded,
31718                "Should be expanded by default"
31719            );
31720        })
31721        .unwrap();
31722
31723    // Set to collapsed using the public method
31724    editor
31725        .update(cx, |editor, _window, cx| {
31726            editor.set_diff_review_comments_expanded(false, cx);
31727        })
31728        .unwrap();
31729
31730    // Verify collapsed
31731    editor
31732        .update(cx, |editor, _window, _cx| {
31733            assert!(
31734                !editor.diff_review_overlays[0].comments_expanded,
31735                "Should be collapsed after setting to false"
31736            );
31737        })
31738        .unwrap();
31739
31740    // Set back to expanded
31741    editor
31742        .update(cx, |editor, _window, cx| {
31743            editor.set_diff_review_comments_expanded(true, cx);
31744        })
31745        .unwrap();
31746
31747    // Verify expanded again
31748    editor
31749        .update(cx, |editor, _window, _cx| {
31750            assert!(
31751                editor.diff_review_overlays[0].comments_expanded,
31752                "Should be expanded after setting to true"
31753            );
31754        })
31755        .unwrap();
31756}
31757
31758#[gpui::test]
31759fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
31760    init_test(cx, |_| {});
31761
31762    // Create an editor with multiple lines of text
31763    let editor = cx.add_window(|window, cx| {
31764        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
31765        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31766        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31767    });
31768
31769    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
31770    editor
31771        .update(cx, |editor, window, cx| {
31772            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
31773        })
31774        .unwrap();
31775
31776    // Verify line range
31777    editor
31778        .update(cx, |editor, _window, cx| {
31779            assert!(!editor.diff_review_overlays.is_empty());
31780            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
31781        })
31782        .unwrap();
31783
31784    // Dismiss and test with reversed range (end < start)
31785    editor
31786        .update(cx, |editor, _window, cx| {
31787            editor.dismiss_all_diff_review_overlays(cx);
31788        })
31789        .unwrap();
31790
31791    // Show overlay with reversed range - should normalize it
31792    editor
31793        .update(cx, |editor, window, cx| {
31794            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
31795        })
31796        .unwrap();
31797
31798    // Verify range is normalized (start <= end)
31799    editor
31800        .update(cx, |editor, _window, cx| {
31801            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31802        })
31803        .unwrap();
31804}
31805
31806#[gpui::test]
31807fn test_diff_review_drag_state(cx: &mut TestAppContext) {
31808    init_test(cx, |_| {});
31809
31810    let editor = cx.add_window(|window, cx| {
31811        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31812        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31813        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31814    });
31815
31816    // Initially no drag state
31817    editor
31818        .update(cx, |editor, _window, _cx| {
31819            assert!(editor.diff_review_drag_state.is_none());
31820        })
31821        .unwrap();
31822
31823    // Start drag at row 1
31824    editor
31825        .update(cx, |editor, window, cx| {
31826            editor.start_diff_review_drag(DisplayRow(1), window, cx);
31827        })
31828        .unwrap();
31829
31830    // Verify drag state is set
31831    editor
31832        .update(cx, |editor, window, cx| {
31833            assert!(editor.diff_review_drag_state.is_some());
31834            let snapshot = editor.snapshot(window, cx);
31835            let range = editor
31836                .diff_review_drag_state
31837                .as_ref()
31838                .unwrap()
31839                .row_range(&snapshot.display_snapshot);
31840            assert_eq!(*range.start(), DisplayRow(1));
31841            assert_eq!(*range.end(), DisplayRow(1));
31842        })
31843        .unwrap();
31844
31845    // Update drag to row 3
31846    editor
31847        .update(cx, |editor, window, cx| {
31848            editor.update_diff_review_drag(DisplayRow(3), window, cx);
31849        })
31850        .unwrap();
31851
31852    // Verify drag state is updated
31853    editor
31854        .update(cx, |editor, window, cx| {
31855            assert!(editor.diff_review_drag_state.is_some());
31856            let snapshot = editor.snapshot(window, cx);
31857            let range = editor
31858                .diff_review_drag_state
31859                .as_ref()
31860                .unwrap()
31861                .row_range(&snapshot.display_snapshot);
31862            assert_eq!(*range.start(), DisplayRow(1));
31863            assert_eq!(*range.end(), DisplayRow(3));
31864        })
31865        .unwrap();
31866
31867    // End drag - should show overlay
31868    editor
31869        .update(cx, |editor, window, cx| {
31870            editor.end_diff_review_drag(window, cx);
31871        })
31872        .unwrap();
31873
31874    // Verify drag state is cleared and overlay is shown
31875    editor
31876        .update(cx, |editor, _window, cx| {
31877            assert!(editor.diff_review_drag_state.is_none());
31878            assert!(!editor.diff_review_overlays.is_empty());
31879            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31880        })
31881        .unwrap();
31882}
31883
31884#[gpui::test]
31885fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
31886    init_test(cx, |_| {});
31887
31888    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31889
31890    // Start drag
31891    editor
31892        .update(cx, |editor, window, cx| {
31893            editor.start_diff_review_drag(DisplayRow(0), window, cx);
31894        })
31895        .unwrap();
31896
31897    // Verify drag state is set
31898    editor
31899        .update(cx, |editor, _window, _cx| {
31900            assert!(editor.diff_review_drag_state.is_some());
31901        })
31902        .unwrap();
31903
31904    // Cancel drag
31905    editor
31906        .update(cx, |editor, _window, cx| {
31907            editor.cancel_diff_review_drag(cx);
31908        })
31909        .unwrap();
31910
31911    // Verify drag state is cleared and no overlay was created
31912    editor
31913        .update(cx, |editor, _window, _cx| {
31914            assert!(editor.diff_review_drag_state.is_none());
31915            assert!(editor.diff_review_overlays.is_empty());
31916        })
31917        .unwrap();
31918}
31919
31920#[gpui::test]
31921fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31922    init_test(cx, |_| {});
31923
31924    // This test verifies that calculate_overlay_height returns correct heights
31925    // based on comment count and expanded state.
31926    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31927
31928    _ = editor.update(cx, |editor, _window, cx| {
31929        let snapshot = editor.buffer().read(cx).snapshot(cx);
31930        let anchor = snapshot.anchor_before(Point::new(0, 0));
31931        let key = DiffHunkKey {
31932            file_path: Arc::from(util::rel_path::RelPath::empty()),
31933            hunk_start_anchor: anchor,
31934        };
31935
31936        // No comments: base height of 2
31937        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31938        assert_eq!(
31939            height_no_comments, 2,
31940            "Base height should be 2 with no comments"
31941        );
31942
31943        // Add one comment
31944        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
31945
31946        let snapshot = editor.buffer().read(cx).snapshot(cx);
31947
31948        // With comments expanded: base (2) + header (1) + 2 per comment
31949        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31950        assert_eq!(
31951            height_expanded,
31952            2 + 1 + 2, // base + header + 1 comment * 2
31953            "Height with 1 comment expanded"
31954        );
31955
31956        // With comments collapsed: base (2) + header (1)
31957        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31958        assert_eq!(
31959            height_collapsed,
31960            2 + 1, // base + header only
31961            "Height with comments collapsed"
31962        );
31963
31964        // Add more comments
31965        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
31966        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
31967
31968        let snapshot = editor.buffer().read(cx).snapshot(cx);
31969
31970        // With 3 comments expanded
31971        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31972        assert_eq!(
31973            height_3_expanded,
31974            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31975            "Height with 3 comments expanded"
31976        );
31977
31978        // Collapsed height stays the same regardless of comment count
31979        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31980        assert_eq!(
31981            height_3_collapsed,
31982            2 + 1, // base + header only
31983            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31984        );
31985    });
31986}
31987
31988#[gpui::test]
31989async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31990    init_test(cx, |_| {});
31991
31992    let language = Arc::new(Language::new(
31993        LanguageConfig::default(),
31994        Some(tree_sitter_rust::LANGUAGE.into()),
31995    ));
31996
31997    let text = r#"
31998        fn main() {
31999            let x = foo(1, 2);
32000        }
32001    "#
32002    .unindent();
32003
32004    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
32005    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32006    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32007
32008    editor
32009        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32010        .await;
32011
32012    // Test case 1: Move to end of syntax nodes
32013    editor.update_in(cx, |editor, window, cx| {
32014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32015            s.select_display_ranges([
32016                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
32017            ]);
32018        });
32019    });
32020    editor.update(cx, |editor, cx| {
32021        assert_text_with_selections(
32022            editor,
32023            indoc! {r#"
32024                fn main() {
32025                    let x = foo(ˇ1, 2);
32026                }
32027            "#},
32028            cx,
32029        );
32030    });
32031    editor.update_in(cx, |editor, window, cx| {
32032        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32033    });
32034    editor.update(cx, |editor, cx| {
32035        assert_text_with_selections(
32036            editor,
32037            indoc! {r#"
32038                fn main() {
32039                    let x = foo(1ˇ, 2);
32040                }
32041            "#},
32042            cx,
32043        );
32044    });
32045    editor.update_in(cx, |editor, window, cx| {
32046        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32047    });
32048    editor.update(cx, |editor, cx| {
32049        assert_text_with_selections(
32050            editor,
32051            indoc! {r#"
32052                fn main() {
32053                    let x = foo(1, 2)ˇ;
32054                }
32055            "#},
32056            cx,
32057        );
32058    });
32059    editor.update_in(cx, |editor, window, cx| {
32060        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32061    });
32062    editor.update(cx, |editor, cx| {
32063        assert_text_with_selections(
32064            editor,
32065            indoc! {r#"
32066                fn main() {
32067                    let x = foo(1, 2);ˇ
32068                }
32069            "#},
32070            cx,
32071        );
32072    });
32073    editor.update_in(cx, |editor, window, cx| {
32074        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32075    });
32076    editor.update(cx, |editor, cx| {
32077        assert_text_with_selections(
32078            editor,
32079            indoc! {r#"
32080                fn main() {
32081                    let x = foo(1, 2);
3208232083            "#},
32084            cx,
32085        );
32086    });
32087
32088    // Test case 2: Move to start of syntax nodes
32089    editor.update_in(cx, |editor, window, cx| {
32090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32091            s.select_display_ranges([
32092                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
32093            ]);
32094        });
32095    });
32096    editor.update(cx, |editor, cx| {
32097        assert_text_with_selections(
32098            editor,
32099            indoc! {r#"
32100                fn main() {
32101                    let x = foo(1, 2ˇ);
32102                }
32103            "#},
32104            cx,
32105        );
32106    });
32107    editor.update_in(cx, |editor, window, cx| {
32108        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32109    });
32110    editor.update(cx, |editor, cx| {
32111        assert_text_with_selections(
32112            editor,
32113            indoc! {r#"
32114                fn main() {
32115                    let x = fooˇ(1, 2);
32116                }
32117            "#},
32118            cx,
32119        );
32120    });
32121    editor.update_in(cx, |editor, window, cx| {
32122        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32123    });
32124    editor.update(cx, |editor, cx| {
32125        assert_text_with_selections(
32126            editor,
32127            indoc! {r#"
32128                fn main() {
32129                    let x = ˇfoo(1, 2);
32130                }
32131            "#},
32132            cx,
32133        );
32134    });
32135    editor.update_in(cx, |editor, window, cx| {
32136        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32137    });
32138    editor.update(cx, |editor, cx| {
32139        assert_text_with_selections(
32140            editor,
32141            indoc! {r#"
32142                fn main() {
32143                    ˇlet x = foo(1, 2);
32144                }
32145            "#},
32146            cx,
32147        );
32148    });
32149    editor.update_in(cx, |editor, window, cx| {
32150        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32151    });
32152    editor.update(cx, |editor, cx| {
32153        assert_text_with_selections(
32154            editor,
32155            indoc! {r#"
32156                fn main() ˇ{
32157                    let x = foo(1, 2);
32158                }
32159            "#},
32160            cx,
32161        );
32162    });
32163    editor.update_in(cx, |editor, window, cx| {
32164        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32165    });
32166    editor.update(cx, |editor, cx| {
32167        assert_text_with_selections(
32168            editor,
32169            indoc! {r#"
32170                ˇfn main() {
32171                    let x = foo(1, 2);
32172                }
32173            "#},
32174            cx,
32175        );
32176    });
32177}
32178
32179#[gpui::test]
32180async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
32181    init_test(cx, |_| {});
32182
32183    let language = Arc::new(Language::new(
32184        LanguageConfig::default(),
32185        Some(tree_sitter_rust::LANGUAGE.into()),
32186    ));
32187
32188    let text = r#"
32189        fn main() {
32190            let x = foo(1, 2);
32191            let y = bar(3, 4);
32192        }
32193    "#
32194    .unindent();
32195
32196    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
32197    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32198    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32199
32200    editor
32201        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32202        .await;
32203
32204    // Test case 1: Move to end of syntax nodes with two cursors
32205    editor.update_in(cx, |editor, window, cx| {
32206        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32207            s.select_display_ranges([
32208                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
32209                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
32210            ]);
32211        });
32212    });
32213    editor.update(cx, |editor, cx| {
32214        assert_text_with_selections(
32215            editor,
32216            indoc! {r#"
32217                fn main() {
32218                    let x = foo(1, 2ˇ);
32219                    let y = bar(3, 4ˇ);
32220                }
32221            "#},
32222            cx,
32223        );
32224    });
32225    editor.update_in(cx, |editor, window, cx| {
32226        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32227    });
32228    editor.update(cx, |editor, cx| {
32229        assert_text_with_selections(
32230            editor,
32231            indoc! {r#"
32232                fn main() {
32233                    let x = foo(1, 2)ˇ;
32234                    let y = bar(3, 4)ˇ;
32235                }
32236            "#},
32237            cx,
32238        );
32239    });
32240    editor.update_in(cx, |editor, window, cx| {
32241        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32242    });
32243    editor.update(cx, |editor, cx| {
32244        assert_text_with_selections(
32245            editor,
32246            indoc! {r#"
32247                fn main() {
32248                    let x = foo(1, 2);ˇ
32249                    let y = bar(3, 4);ˇ
32250                }
32251            "#},
32252            cx,
32253        );
32254    });
32255
32256    // Test case 2: Move to start of syntax nodes with two cursors
32257    editor.update_in(cx, |editor, window, cx| {
32258        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32259            s.select_display_ranges([
32260                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
32261                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
32262            ]);
32263        });
32264    });
32265    editor.update(cx, |editor, cx| {
32266        assert_text_with_selections(
32267            editor,
32268            indoc! {r#"
32269                fn main() {
32270                    let x = foo(1, ˇ2);
32271                    let y = bar(3, ˇ4);
32272                }
32273            "#},
32274            cx,
32275        );
32276    });
32277    editor.update_in(cx, |editor, window, cx| {
32278        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32279    });
32280    editor.update(cx, |editor, cx| {
32281        assert_text_with_selections(
32282            editor,
32283            indoc! {r#"
32284                fn main() {
32285                    let x = fooˇ(1, 2);
32286                    let y = barˇ(3, 4);
32287                }
32288            "#},
32289            cx,
32290        );
32291    });
32292    editor.update_in(cx, |editor, window, cx| {
32293        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32294    });
32295    editor.update(cx, |editor, cx| {
32296        assert_text_with_selections(
32297            editor,
32298            indoc! {r#"
32299                fn main() {
32300                    let x = ˇfoo(1, 2);
32301                    let y = ˇbar(3, 4);
32302                }
32303            "#},
32304            cx,
32305        );
32306    });
32307    editor.update_in(cx, |editor, window, cx| {
32308        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32309    });
32310    editor.update(cx, |editor, cx| {
32311        assert_text_with_selections(
32312            editor,
32313            indoc! {r#"
32314                fn main() {
32315                    ˇlet x = foo(1, 2);
32316                    ˇlet y = bar(3, 4);
32317                }
32318            "#},
32319            cx,
32320        );
32321    });
32322}
32323
32324#[gpui::test]
32325async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
32326    cx: &mut TestAppContext,
32327) {
32328    init_test(cx, |_| {});
32329
32330    let language = Arc::new(Language::new(
32331        LanguageConfig::default(),
32332        Some(tree_sitter_rust::LANGUAGE.into()),
32333    ));
32334
32335    let text = r#"
32336        fn main() {
32337            let x = foo(1, 2);
32338            let msg = "hello world";
32339        }
32340    "#
32341    .unindent();
32342
32343    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
32344    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32345    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32346
32347    editor
32348        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32349        .await;
32350
32351    // Test case 1: With existing selection, move_to_end keeps selection
32352    editor.update_in(cx, |editor, window, cx| {
32353        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32354            s.select_display_ranges([
32355                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
32356            ]);
32357        });
32358    });
32359    editor.update(cx, |editor, cx| {
32360        assert_text_with_selections(
32361            editor,
32362            indoc! {r#"
32363                fn main() {
32364                    let x = «foo(1, 2)ˇ»;
32365                    let msg = "hello world";
32366                }
32367            "#},
32368            cx,
32369        );
32370    });
32371    editor.update_in(cx, |editor, window, cx| {
32372        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32373    });
32374    editor.update(cx, |editor, cx| {
32375        assert_text_with_selections(
32376            editor,
32377            indoc! {r#"
32378                fn main() {
32379                    let x = «foo(1, 2)ˇ»;
32380                    let msg = "hello world";
32381                }
32382            "#},
32383            cx,
32384        );
32385    });
32386
32387    // Test case 2: Move to end within a string
32388    editor.update_in(cx, |editor, window, cx| {
32389        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32390            s.select_display_ranges([
32391                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
32392            ]);
32393        });
32394    });
32395    editor.update(cx, |editor, cx| {
32396        assert_text_with_selections(
32397            editor,
32398            indoc! {r#"
32399                fn main() {
32400                    let x = foo(1, 2);
32401                    let msg = "ˇhello world";
32402                }
32403            "#},
32404            cx,
32405        );
32406    });
32407    editor.update_in(cx, |editor, window, cx| {
32408        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32409    });
32410    editor.update(cx, |editor, cx| {
32411        assert_text_with_selections(
32412            editor,
32413            indoc! {r#"
32414                fn main() {
32415                    let x = foo(1, 2);
32416                    let msg = "hello worldˇ";
32417                }
32418            "#},
32419            cx,
32420        );
32421    });
32422
32423    // Test case 3: Move to start within a string
32424    editor.update_in(cx, |editor, window, cx| {
32425        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32426            s.select_display_ranges([
32427                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
32428            ]);
32429        });
32430    });
32431    editor.update(cx, |editor, cx| {
32432        assert_text_with_selections(
32433            editor,
32434            indoc! {r#"
32435                fn main() {
32436                    let x = foo(1, 2);
32437                    let msg = "hello ˇworld";
32438                }
32439            "#},
32440            cx,
32441        );
32442    });
32443    editor.update_in(cx, |editor, window, cx| {
32444        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32445    });
32446    editor.update(cx, |editor, cx| {
32447        assert_text_with_selections(
32448            editor,
32449            indoc! {r#"
32450                fn main() {
32451                    let x = foo(1, 2);
32452                    let msg = "ˇhello world";
32453                }
32454            "#},
32455            cx,
32456        );
32457    });
32458}
32459
32460#[gpui::test]
32461async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
32462    init_test(cx, |_| {});
32463
32464    let language = Arc::new(Language::new(
32465        LanguageConfig::default(),
32466        Some(tree_sitter_rust::LANGUAGE.into()),
32467    ));
32468
32469    // Test Group 1.1: Cursor in String - First Jump (Select to End)
32470    let text = r#"let msg = "foo bar baz";"#.unindent();
32471
32472    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32473    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32474    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32475
32476    editor
32477        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32478        .await;
32479
32480    editor.update_in(cx, |editor, window, cx| {
32481        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32482            s.select_display_ranges([
32483                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32484            ]);
32485        });
32486    });
32487    editor.update(cx, |editor, cx| {
32488        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
32489    });
32490    editor.update_in(cx, |editor, window, cx| {
32491        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32492    });
32493    editor.update(cx, |editor, cx| {
32494        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
32495    });
32496
32497    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
32498    editor.update_in(cx, |editor, window, cx| {
32499        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32500    });
32501    editor.update(cx, |editor, cx| {
32502        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
32503    });
32504
32505    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
32506    editor.update_in(cx, |editor, window, cx| {
32507        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32508    });
32509    editor.update(cx, |editor, cx| {
32510        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
32511    });
32512
32513    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
32514    editor.update_in(cx, |editor, window, cx| {
32515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32516            s.select_display_ranges([
32517                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
32518            ]);
32519        });
32520    });
32521    editor.update(cx, |editor, cx| {
32522        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
32523    });
32524    editor.update_in(cx, |editor, window, cx| {
32525        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32526    });
32527    editor.update(cx, |editor, cx| {
32528        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
32529    });
32530
32531    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
32532    editor.update_in(cx, |editor, window, cx| {
32533        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32534    });
32535    editor.update(cx, |editor, cx| {
32536        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
32537    });
32538
32539    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
32540    editor.update_in(cx, |editor, window, cx| {
32541        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32542    });
32543    editor.update(cx, |editor, cx| {
32544        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
32545    });
32546
32547    // Test Group 2.1: Let Statement Progression (Select to End)
32548    let text = r#"
32549fn main() {
32550    let x = "hello";
32551}
32552"#
32553    .unindent();
32554
32555    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32556    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32557    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32558
32559    editor
32560        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32561        .await;
32562
32563    editor.update_in(cx, |editor, window, cx| {
32564        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32565            s.select_display_ranges([
32566                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
32567            ]);
32568        });
32569    });
32570    editor.update(cx, |editor, cx| {
32571        assert_text_with_selections(
32572            editor,
32573            indoc! {r#"
32574                fn main() {
32575                    let xˇ = "hello";
32576                }
32577            "#},
32578            cx,
32579        );
32580    });
32581    editor.update_in(cx, |editor, window, cx| {
32582        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32583    });
32584    editor.update(cx, |editor, cx| {
32585        assert_text_with_selections(
32586            editor,
32587            indoc! {r##"
32588                fn main() {
32589                    let x« = "hello";ˇ»
32590                }
32591            "##},
32592            cx,
32593        );
32594    });
32595    editor.update_in(cx, |editor, window, cx| {
32596        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32597    });
32598    editor.update(cx, |editor, cx| {
32599        assert_text_with_selections(
32600            editor,
32601            indoc! {r#"
32602                fn main() {
32603                    let x« = "hello";
32604                }ˇ»
32605            "#},
32606            cx,
32607        );
32608    });
32609
32610    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
32611    let text = r#"let x = "hello";"#.unindent();
32612
32613    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32614    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32615    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32616
32617    editor
32618        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32619        .await;
32620
32621    editor.update_in(cx, |editor, window, cx| {
32622        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32623            s.select_display_ranges([
32624                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
32625            ]);
32626        });
32627    });
32628    editor.update(cx, |editor, cx| {
32629        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
32630    });
32631    editor.update_in(cx, |editor, window, cx| {
32632        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32633    });
32634    editor.update(cx, |editor, cx| {
32635        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
32636    });
32637
32638    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
32639    editor.update_in(cx, |editor, window, cx| {
32640        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32641            s.select_display_ranges([
32642                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
32643            ]);
32644        });
32645    });
32646    editor.update(cx, |editor, cx| {
32647        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
32648    });
32649    editor.update_in(cx, |editor, window, cx| {
32650        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
32651    });
32652    editor.update(cx, |editor, cx| {
32653        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
32654    });
32655
32656    // Test Group 3.1: Create Selection from Cursor (Select to End)
32657    let text = r#"let x = "hello world";"#.unindent();
32658
32659    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32660    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32661    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32662
32663    editor
32664        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32665        .await;
32666
32667    editor.update_in(cx, |editor, window, cx| {
32668        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32669            s.select_display_ranges([
32670                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
32671            ]);
32672        });
32673    });
32674    editor.update(cx, |editor, cx| {
32675        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
32676    });
32677    editor.update_in(cx, |editor, window, cx| {
32678        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32679    });
32680    editor.update(cx, |editor, cx| {
32681        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
32682    });
32683
32684    // Test Group 3.2: Extend Existing Selection (Select to End)
32685    editor.update_in(cx, |editor, window, cx| {
32686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32687            s.select_display_ranges([
32688                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
32689            ]);
32690        });
32691    });
32692    editor.update(cx, |editor, cx| {
32693        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
32694    });
32695    editor.update_in(cx, |editor, window, cx| {
32696        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32697    });
32698    editor.update(cx, |editor, cx| {
32699        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
32700    });
32701
32702    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
32703    let text = r#"let x = "hello"; let y = 42;"#.unindent();
32704
32705    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32706    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32708
32709    editor
32710        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32711        .await;
32712
32713    editor.update_in(cx, |editor, window, cx| {
32714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32715            s.select_display_ranges([
32716                // Cursor inside string content
32717                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32718                // Cursor at let statement semicolon
32719                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
32720                // Cursor inside integer literal
32721                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
32722            ]);
32723        });
32724    });
32725    editor.update(cx, |editor, cx| {
32726        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
32727    });
32728    editor.update_in(cx, |editor, window, cx| {
32729        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32730    });
32731    editor.update(cx, |editor, cx| {
32732        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
32733    });
32734
32735    // Test Group 4.2: Multiple Cursors on Separate Lines
32736    let text = r#"
32737let x = "hello";
32738let y = 42;
32739"#
32740    .unindent();
32741
32742    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32743    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32744    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32745
32746    editor
32747        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32748        .await;
32749
32750    editor.update_in(cx, |editor, window, cx| {
32751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32752            s.select_display_ranges([
32753                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
32754                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
32755            ]);
32756        });
32757    });
32758
32759    editor.update(cx, |editor, cx| {
32760        assert_text_with_selections(
32761            editor,
32762            indoc! {r#"
32763                let x = "helˇlo";
32764                let y = 4ˇ2;
32765            "#},
32766            cx,
32767        );
32768    });
32769    editor.update_in(cx, |editor, window, cx| {
32770        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32771    });
32772    editor.update(cx, |editor, cx| {
32773        assert_text_with_selections(
32774            editor,
32775            indoc! {r#"
32776                let x = "hel«loˇ»";
32777                let y = 4«2ˇ»;
32778            "#},
32779            cx,
32780        );
32781    });
32782
32783    // Test Group 5.1: Nested Function Calls
32784    let text = r#"let result = foo(bar("arg"));"#.unindent();
32785
32786    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32789
32790    editor
32791        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32792        .await;
32793
32794    editor.update_in(cx, |editor, window, cx| {
32795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32796            s.select_display_ranges([
32797                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
32798            ]);
32799        });
32800    });
32801    editor.update(cx, |editor, cx| {
32802        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
32803    });
32804    editor.update_in(cx, |editor, window, cx| {
32805        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32806    });
32807    editor.update(cx, |editor, cx| {
32808        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
32809    });
32810    editor.update_in(cx, |editor, window, cx| {
32811        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32812    });
32813    editor.update(cx, |editor, cx| {
32814        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
32815    });
32816    editor.update_in(cx, |editor, window, cx| {
32817        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32818    });
32819    editor.update(cx, |editor, cx| {
32820        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
32821    });
32822
32823    // Test Group 6.1: Block Comments
32824    let text = r#"let x = /* multi
32825                             line
32826                             comment */;"#
32827        .unindent();
32828
32829    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32830    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32831    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32832
32833    editor
32834        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32835        .await;
32836
32837    editor.update_in(cx, |editor, window, cx| {
32838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32839            s.select_display_ranges([
32840                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
32841            ]);
32842        });
32843    });
32844    editor.update(cx, |editor, cx| {
32845        assert_text_with_selections(
32846            editor,
32847            indoc! {r#"
32848let x = /* multiˇ
32849line
32850comment */;"#},
32851            cx,
32852        );
32853    });
32854    editor.update_in(cx, |editor, window, cx| {
32855        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32856    });
32857    editor.update(cx, |editor, cx| {
32858        assert_text_with_selections(
32859            editor,
32860            indoc! {r#"
32861let x = /* multi«
32862line
32863comment */ˇ»;"#},
32864            cx,
32865        );
32866    });
32867
32868    // Test Group 6.2: Array/Vector Literals
32869    let text = r#"let arr = [1, 2, 3];"#.unindent();
32870
32871    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
32872    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32873    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32874
32875    editor
32876        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32877        .await;
32878
32879    editor.update_in(cx, |editor, window, cx| {
32880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32881            s.select_display_ranges([
32882                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
32883            ]);
32884        });
32885    });
32886    editor.update(cx, |editor, cx| {
32887        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
32888    });
32889    editor.update_in(cx, |editor, window, cx| {
32890        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32891    });
32892    editor.update(cx, |editor, cx| {
32893        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
32894    });
32895    editor.update_in(cx, |editor, window, cx| {
32896        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
32897    });
32898    editor.update(cx, |editor, cx| {
32899        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
32900    });
32901}