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, Rgba, 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;
   38use multi_buffer::{
   39    IndentGuide, MultiBufferFilterMode, 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, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    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, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    invalid_item_view::InvalidItemView,
   73    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   74    register_project_item,
   75};
   76
   77fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   78    editor
   79        .selections
   80        .display_ranges(&editor.display_snapshot(cx))
   81}
   82
   83#[gpui::test]
   84fn test_edit_events(cx: &mut TestAppContext) {
   85    init_test(cx, |_| {});
   86
   87    let buffer = cx.new(|cx| {
   88        let mut buffer = language::Buffer::local("123456", cx);
   89        buffer.set_group_interval(Duration::from_secs(1));
   90        buffer
   91    });
   92
   93    let events = Rc::new(RefCell::new(Vec::new()));
   94    let editor1 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            let entity = cx.entity();
   98            cx.subscribe_in(
   99                &entity,
  100                window,
  101                move |_, _, event: &EditorEvent, _, _| match event {
  102                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  103                    EditorEvent::BufferEdited => {
  104                        events.borrow_mut().push(("editor1", "buffer edited"))
  105                    }
  106                    _ => {}
  107                },
  108            )
  109            .detach();
  110            Editor::for_buffer(buffer.clone(), None, window, cx)
  111        }
  112    });
  113
  114    let editor2 = cx.add_window({
  115        let events = events.clone();
  116        |window, cx| {
  117            cx.subscribe_in(
  118                &cx.entity(),
  119                window,
  120                move |_, _, event: &EditorEvent, _, _| match event {
  121                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  122                    EditorEvent::BufferEdited => {
  123                        events.borrow_mut().push(("editor2", "buffer edited"))
  124                    }
  125                    _ => {}
  126                },
  127            )
  128            .detach();
  129            Editor::for_buffer(buffer.clone(), None, window, cx)
  130        }
  131    });
  132
  133    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  134
  135    // Mutating editor 1 will emit an `Edited` event only for that editor.
  136    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  137    assert_eq!(
  138        mem::take(&mut *events.borrow_mut()),
  139        [
  140            ("editor1", "edited"),
  141            ("editor1", "buffer edited"),
  142            ("editor2", "buffer edited"),
  143        ]
  144    );
  145
  146    // Mutating editor 2 will emit an `Edited` event only for that editor.
  147    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  148    assert_eq!(
  149        mem::take(&mut *events.borrow_mut()),
  150        [
  151            ("editor2", "edited"),
  152            ("editor1", "buffer edited"),
  153            ("editor2", "buffer edited"),
  154        ]
  155    );
  156
  157    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  158    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  159    assert_eq!(
  160        mem::take(&mut *events.borrow_mut()),
  161        [
  162            ("editor1", "edited"),
  163            ("editor1", "buffer edited"),
  164            ("editor2", "buffer edited"),
  165        ]
  166    );
  167
  168    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  169    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  170    assert_eq!(
  171        mem::take(&mut *events.borrow_mut()),
  172        [
  173            ("editor1", "edited"),
  174            ("editor1", "buffer edited"),
  175            ("editor2", "buffer edited"),
  176        ]
  177    );
  178
  179    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  180    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  181    assert_eq!(
  182        mem::take(&mut *events.borrow_mut()),
  183        [
  184            ("editor2", "edited"),
  185            ("editor1", "buffer edited"),
  186            ("editor2", "buffer edited"),
  187        ]
  188    );
  189
  190    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  191    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  192    assert_eq!(
  193        mem::take(&mut *events.borrow_mut()),
  194        [
  195            ("editor2", "edited"),
  196            ("editor1", "buffer edited"),
  197            ("editor2", "buffer edited"),
  198        ]
  199    );
  200
  201    // No event is emitted when the mutation is a no-op.
  202    _ = editor2.update(cx, |editor, window, cx| {
  203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  204            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  205        });
  206
  207        editor.backspace(&Backspace, window, cx);
  208    });
  209    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  210}
  211
  212#[gpui::test]
  213fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  214    init_test(cx, |_| {});
  215
  216    let mut now = Instant::now();
  217    let group_interval = Duration::from_millis(1);
  218    let buffer = cx.new(|cx| {
  219        let mut buf = language::Buffer::local("123456", cx);
  220        buf.set_group_interval(group_interval);
  221        buf
  222    });
  223    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  224    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  225
  226    _ = editor.update(cx, |editor, window, cx| {
  227        editor.start_transaction_at(now, window, cx);
  228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  229            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  230        });
  231
  232        editor.insert("cd", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cd56");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  238        );
  239
  240        editor.start_transaction_at(now, window, cx);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  243        });
  244        editor.insert("e", window, cx);
  245        editor.end_transaction_at(now, cx);
  246        assert_eq!(editor.text(cx), "12cde6");
  247        assert_eq!(
  248            editor.selections.ranges(&editor.display_snapshot(cx)),
  249            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  250        );
  251
  252        now += group_interval + Duration::from_millis(1);
  253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  254            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  255        });
  256
  257        // Simulate an edit in another editor
  258        buffer.update(cx, |buffer, cx| {
  259            buffer.start_transaction_at(now, cx);
  260            buffer.edit(
  261                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  262                None,
  263                cx,
  264            );
  265            buffer.edit(
  266                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  267                None,
  268                cx,
  269            );
  270            buffer.end_transaction_at(now, cx);
  271        });
  272
  273        assert_eq!(editor.text(cx), "ab2cde6");
  274        assert_eq!(
  275            editor.selections.ranges(&editor.display_snapshot(cx)),
  276            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  277        );
  278
  279        // Last transaction happened past the group interval in a different editor.
  280        // Undo it individually and don't restore selections.
  281        editor.undo(&Undo, window, cx);
  282        assert_eq!(editor.text(cx), "12cde6");
  283        assert_eq!(
  284            editor.selections.ranges(&editor.display_snapshot(cx)),
  285            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  286        );
  287
  288        // First two transactions happened within the group interval in this editor.
  289        // Undo them together and restore selections.
  290        editor.undo(&Undo, window, cx);
  291        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  292        assert_eq!(editor.text(cx), "123456");
  293        assert_eq!(
  294            editor.selections.ranges(&editor.display_snapshot(cx)),
  295            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  296        );
  297
  298        // Redo the first two transactions together.
  299        editor.redo(&Redo, window, cx);
  300        assert_eq!(editor.text(cx), "12cde6");
  301        assert_eq!(
  302            editor.selections.ranges(&editor.display_snapshot(cx)),
  303            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  304        );
  305
  306        // Redo the last transaction on its own.
  307        editor.redo(&Redo, window, cx);
  308        assert_eq!(editor.text(cx), "ab2cde6");
  309        assert_eq!(
  310            editor.selections.ranges(&editor.display_snapshot(cx)),
  311            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  312        );
  313
  314        // Test empty transactions.
  315        editor.start_transaction_at(now, window, cx);
  316        editor.end_transaction_at(now, cx);
  317        editor.undo(&Undo, window, cx);
  318        assert_eq!(editor.text(cx), "12cde6");
  319    });
  320}
  321
  322#[gpui::test]
  323fn test_ime_composition(cx: &mut TestAppContext) {
  324    init_test(cx, |_| {});
  325
  326    let buffer = cx.new(|cx| {
  327        let mut buffer = language::Buffer::local("abcde", cx);
  328        // Ensure automatic grouping doesn't occur.
  329        buffer.set_group_interval(Duration::ZERO);
  330        buffer
  331    });
  332
  333    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  334    cx.add_window(|window, cx| {
  335        let mut editor = build_editor(buffer.clone(), window, cx);
  336
  337        // Start a new IME composition.
  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        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  341        assert_eq!(editor.text(cx), "äbcde");
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![
  345                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  346            ])
  347        );
  348
  349        // Finalize IME composition.
  350        editor.replace_text_in_range(None, "ā", window, cx);
  351        assert_eq!(editor.text(cx), "ābcde");
  352        assert_eq!(editor.marked_text_ranges(cx), None);
  353
  354        // IME composition edits are grouped and are undone/redone at once.
  355        editor.undo(&Default::default(), window, cx);
  356        assert_eq!(editor.text(cx), "abcde");
  357        assert_eq!(editor.marked_text_ranges(cx), None);
  358        editor.redo(&Default::default(), window, cx);
  359        assert_eq!(editor.text(cx), "ābcde");
  360        assert_eq!(editor.marked_text_ranges(cx), None);
  361
  362        // Start a new IME composition.
  363        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  364        assert_eq!(
  365            editor.marked_text_ranges(cx),
  366            Some(vec![
  367                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  368            ])
  369        );
  370
  371        // Undoing during an IME composition cancels it.
  372        editor.undo(&Default::default(), window, cx);
  373        assert_eq!(editor.text(cx), "ābcde");
  374        assert_eq!(editor.marked_text_ranges(cx), None);
  375
  376        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  377        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  378        assert_eq!(editor.text(cx), "ābcdè");
  379        assert_eq!(
  380            editor.marked_text_ranges(cx),
  381            Some(vec![
  382                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  383            ])
  384        );
  385
  386        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  387        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  388        assert_eq!(editor.text(cx), "ābcdę");
  389        assert_eq!(editor.marked_text_ranges(cx), None);
  390
  391        // Start a new IME composition with multiple cursors.
  392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  393            s.select_ranges([
  394                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  396                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  397            ])
  398        });
  399        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  400        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  401        assert_eq!(
  402            editor.marked_text_ranges(cx),
  403            Some(vec![
  404                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  406                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  407            ])
  408        );
  409
  410        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  411        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  412        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  413        assert_eq!(
  414            editor.marked_text_ranges(cx),
  415            Some(vec![
  416                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  418                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  419            ])
  420        );
  421
  422        // Finalize IME composition with multiple cursors.
  423        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  424        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  425        assert_eq!(editor.marked_text_ranges(cx), None);
  426
  427        editor
  428    });
  429}
  430
  431#[gpui::test]
  432fn test_selection_with_mouse(cx: &mut TestAppContext) {
  433    init_test(cx, |_| {});
  434
  435    let editor = cx.add_window(|window, cx| {
  436        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  437        build_editor(buffer, window, cx)
  438    });
  439
  440    _ = editor.update(cx, |editor, window, cx| {
  441        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  442    });
  443    assert_eq!(
  444        editor
  445            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  446            .unwrap(),
  447        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  448    );
  449
  450    _ = editor.update(cx, |editor, window, cx| {
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(3), 3),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  463            .unwrap(),
  464        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  465    );
  466
  467    _ = editor.update(cx, |editor, window, cx| {
  468        editor.update_selection(
  469            DisplayPoint::new(DisplayRow(1), 1),
  470            0,
  471            gpui::Point::<f32>::default(),
  472            window,
  473            cx,
  474        );
  475    });
  476
  477    assert_eq!(
  478        editor
  479            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  480            .unwrap(),
  481        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  482    );
  483
  484    _ = editor.update(cx, |editor, window, cx| {
  485        editor.end_selection(window, cx);
  486        editor.update_selection(
  487            DisplayPoint::new(DisplayRow(3), 3),
  488            0,
  489            gpui::Point::<f32>::default(),
  490            window,
  491            cx,
  492        );
  493    });
  494
  495    assert_eq!(
  496        editor
  497            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  498            .unwrap(),
  499        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  500    );
  501
  502    _ = editor.update(cx, |editor, window, cx| {
  503        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  504        editor.update_selection(
  505            DisplayPoint::new(DisplayRow(0), 0),
  506            0,
  507            gpui::Point::<f32>::default(),
  508            window,
  509            cx,
  510        );
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  519            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.end_selection(window, cx);
  525    });
  526
  527    assert_eq!(
  528        editor
  529            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  530            .unwrap(),
  531        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  532    );
  533}
  534
  535#[gpui::test]
  536fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  537    init_test(cx, |_| {});
  538
  539    let editor = cx.add_window(|window, cx| {
  540        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  541        build_editor(buffer, window, cx)
  542    });
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    _ = editor.update(cx, |editor, window, cx| {
  553        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.end_selection(window, cx);
  558    });
  559
  560    assert_eq!(
  561        editor
  562            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  563            .unwrap(),
  564        [
  565            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  566            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  567        ]
  568    );
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  572    });
  573
  574    _ = editor.update(cx, |editor, window, cx| {
  575        editor.end_selection(window, cx);
  576    });
  577
  578    assert_eq!(
  579        editor
  580            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  581            .unwrap(),
  582        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  583    );
  584}
  585
  586#[gpui::test]
  587fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            display_ranges(editor, cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601    });
  602
  603    _ = editor.update(cx, |editor, window, cx| {
  604        editor.update_selection(
  605            DisplayPoint::new(DisplayRow(3), 3),
  606            0,
  607            gpui::Point::<f32>::default(),
  608            window,
  609            cx,
  610        );
  611        assert_eq!(
  612            display_ranges(editor, cx),
  613            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  614        );
  615    });
  616
  617    _ = editor.update(cx, |editor, window, cx| {
  618        editor.cancel(&Cancel, window, cx);
  619        editor.update_selection(
  620            DisplayPoint::new(DisplayRow(1), 1),
  621            0,
  622            gpui::Point::<f32>::default(),
  623            window,
  624            cx,
  625        );
  626        assert_eq!(
  627            display_ranges(editor, cx),
  628            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  629        );
  630    });
  631}
  632
  633#[gpui::test]
  634fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  635    init_test(cx, |_| {});
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  644        assert_eq!(
  645            display_ranges(editor, cx),
  646            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  647        );
  648
  649        editor.move_down(&Default::default(), window, cx);
  650        assert_eq!(
  651            display_ranges(editor, cx),
  652            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  653        );
  654
  655        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  656        assert_eq!(
  657            display_ranges(editor, cx),
  658            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  659        );
  660
  661        editor.move_up(&Default::default(), window, cx);
  662        assert_eq!(
  663            display_ranges(editor, cx),
  664            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  665        );
  666    });
  667}
  668
  669#[gpui::test]
  670fn test_extending_selection(cx: &mut TestAppContext) {
  671    init_test(cx, |_| {});
  672
  673    let editor = cx.add_window(|window, cx| {
  674        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  675        build_editor(buffer, window, cx)
  676    });
  677
  678    _ = editor.update(cx, |editor, window, cx| {
  679        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  680        editor.end_selection(window, cx);
  681        assert_eq!(
  682            display_ranges(editor, cx),
  683            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  684        );
  685
  686        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  687        editor.end_selection(window, cx);
  688        assert_eq!(
  689            display_ranges(editor, cx),
  690            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  691        );
  692
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  694        editor.end_selection(window, cx);
  695        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  696        assert_eq!(
  697            display_ranges(editor, cx),
  698            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  699        );
  700
  701        editor.update_selection(
  702            DisplayPoint::new(DisplayRow(0), 1),
  703            0,
  704            gpui::Point::<f32>::default(),
  705            window,
  706            cx,
  707        );
  708        editor.end_selection(window, cx);
  709        assert_eq!(
  710            display_ranges(editor, cx),
  711            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  712        );
  713
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  715        editor.end_selection(window, cx);
  716        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  717        editor.end_selection(window, cx);
  718        assert_eq!(
  719            display_ranges(editor, cx),
  720            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  721        );
  722
  723        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  724        assert_eq!(
  725            display_ranges(editor, cx),
  726            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  727        );
  728
  729        editor.update_selection(
  730            DisplayPoint::new(DisplayRow(0), 6),
  731            0,
  732            gpui::Point::<f32>::default(),
  733            window,
  734            cx,
  735        );
  736        assert_eq!(
  737            display_ranges(editor, cx),
  738            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  739        );
  740
  741        editor.update_selection(
  742            DisplayPoint::new(DisplayRow(0), 1),
  743            0,
  744            gpui::Point::<f32>::default(),
  745            window,
  746            cx,
  747        );
  748        editor.end_selection(window, cx);
  749        assert_eq!(
  750            display_ranges(editor, cx),
  751            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  752        );
  753    });
  754}
  755
  756#[gpui::test]
  757fn test_clone(cx: &mut TestAppContext) {
  758    init_test(cx, |_| {});
  759
  760    let (text, selection_ranges) = marked_text_ranges(
  761        indoc! {"
  762            one
  763            two
  764            threeˇ
  765            four
  766            fiveˇ
  767        "},
  768        true,
  769    );
  770
  771    let editor = cx.add_window(|window, cx| {
  772        let buffer = MultiBuffer::build_simple(&text, cx);
  773        build_editor(buffer, window, cx)
  774    });
  775
  776    _ = editor.update(cx, |editor, window, cx| {
  777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  778            s.select_ranges(
  779                selection_ranges
  780                    .iter()
  781                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  782            )
  783        });
  784        editor.fold_creases(
  785            vec![
  786                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  787                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  788            ],
  789            true,
  790            window,
  791            cx,
  792        );
  793    });
  794
  795    let cloned_editor = editor
  796        .update(cx, |editor, _, cx| {
  797            cx.open_window(Default::default(), |window, cx| {
  798                cx.new(|cx| editor.clone(window, cx))
  799            })
  800        })
  801        .unwrap()
  802        .unwrap();
  803
  804    let snapshot = editor
  805        .update(cx, |e, window, cx| e.snapshot(window, cx))
  806        .unwrap();
  807    let cloned_snapshot = cloned_editor
  808        .update(cx, |e, window, cx| e.snapshot(window, cx))
  809        .unwrap();
  810
  811    assert_eq!(
  812        cloned_editor
  813            .update(cx, |e, _, cx| e.display_text(cx))
  814            .unwrap(),
  815        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  816    );
  817    assert_eq!(
  818        cloned_snapshot
  819            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  820            .collect::<Vec<_>>(),
  821        snapshot
  822            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  823            .collect::<Vec<_>>(),
  824    );
  825    assert_set_eq!(
  826        cloned_editor
  827            .update(cx, |editor, _, cx| editor
  828                .selections
  829                .ranges::<Point>(&editor.display_snapshot(cx)))
  830            .unwrap(),
  831        editor
  832            .update(cx, |editor, _, cx| editor
  833                .selections
  834                .ranges(&editor.display_snapshot(cx)))
  835            .unwrap()
  836    );
  837    assert_set_eq!(
  838        cloned_editor
  839            .update(cx, |e, _window, cx| e
  840                .selections
  841                .display_ranges(&e.display_snapshot(cx)))
  842            .unwrap(),
  843        editor
  844            .update(cx, |e, _, cx| e
  845                .selections
  846                .display_ranges(&e.display_snapshot(cx)))
  847            .unwrap()
  848    );
  849}
  850
  851#[gpui::test]
  852async fn test_navigation_history(cx: &mut TestAppContext) {
  853    init_test(cx, |_| {});
  854
  855    use workspace::item::Item;
  856
  857    let fs = FakeFs::new(cx.executor());
  858    let project = Project::test(fs, [], cx).await;
  859    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  860    let pane = workspace
  861        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  862        .unwrap();
  863
  864    _ = workspace.update(cx, |_v, 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.scroll_manager.anchor();
  943
  944            // Jump to the end of the document and adjust scroll
  945            editor.move_to_end(&MoveToEnd, window, cx);
  946            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  947            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  948
  949            let nav_entry = pop_history(&mut editor, cx).unwrap();
  950            editor.navigate(nav_entry.data.unwrap(), window, cx);
  951            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  952
  953            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  954            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  955            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  956            let invalid_point = Point::new(9999, 0);
  957            editor.navigate(
  958                Box::new(NavigationData {
  959                    cursor_anchor: invalid_anchor,
  960                    cursor_position: invalid_point,
  961                    scroll_anchor: ScrollAnchor {
  962                        anchor: invalid_anchor,
  963                        offset: Default::default(),
  964                    },
  965                    scroll_top_row: invalid_point.row,
  966                }),
  967                window,
  968                cx,
  969            );
  970            assert_eq!(
  971                editor
  972                    .selections
  973                    .display_ranges(&editor.display_snapshot(cx)),
  974                &[editor.max_point(cx)..editor.max_point(cx)]
  975            );
  976            assert_eq!(
  977                editor.scroll_position(cx),
  978                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  979            );
  980
  981            editor
  982        })
  983    });
  984}
  985
  986#[gpui::test]
  987fn test_cancel(cx: &mut TestAppContext) {
  988    init_test(cx, |_| {});
  989
  990    let editor = cx.add_window(|window, cx| {
  991        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  992        build_editor(buffer, window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  997        editor.update_selection(
  998            DisplayPoint::new(DisplayRow(1), 1),
  999            0,
 1000            gpui::Point::<f32>::default(),
 1001            window,
 1002            cx,
 1003        );
 1004        editor.end_selection(window, cx);
 1005
 1006        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1007        editor.update_selection(
 1008            DisplayPoint::new(DisplayRow(0), 3),
 1009            0,
 1010            gpui::Point::<f32>::default(),
 1011            window,
 1012            cx,
 1013        );
 1014        editor.end_selection(window, cx);
 1015        assert_eq!(
 1016            display_ranges(editor, cx),
 1017            [
 1018                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1019                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1020            ]
 1021        );
 1022    });
 1023
 1024    _ = editor.update(cx, |editor, window, cx| {
 1025        editor.cancel(&Cancel, window, cx);
 1026        assert_eq!(
 1027            display_ranges(editor, cx),
 1028            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1029        );
 1030    });
 1031
 1032    _ = editor.update(cx, |editor, window, cx| {
 1033        editor.cancel(&Cancel, window, cx);
 1034        assert_eq!(
 1035            display_ranges(editor, cx),
 1036            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1037        );
 1038    });
 1039}
 1040
 1041#[gpui::test]
 1042fn test_fold_action(cx: &mut TestAppContext) {
 1043    init_test(cx, |_| {});
 1044
 1045    let editor = cx.add_window(|window, cx| {
 1046        let buffer = MultiBuffer::build_simple(
 1047            &"
 1048                impl Foo {
 1049                    // Hello!
 1050
 1051                    fn a() {
 1052                        1
 1053                    }
 1054
 1055                    fn b() {
 1056                        2
 1057                    }
 1058
 1059                    fn c() {
 1060                        3
 1061                    }
 1062                }
 1063            "
 1064            .unindent(),
 1065            cx,
 1066        );
 1067        build_editor(buffer, window, cx)
 1068    });
 1069
 1070    _ = editor.update(cx, |editor, window, cx| {
 1071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1072            s.select_display_ranges([
 1073                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1074            ]);
 1075        });
 1076        editor.fold(&Fold, window, cx);
 1077        assert_eq!(
 1078            editor.display_text(cx),
 1079            "
 1080                impl Foo {
 1081                    // Hello!
 1082
 1083                    fn a() {
 1084                        1
 1085                    }
 1086
 1087                    fn b() {⋯
 1088                    }
 1089
 1090                    fn c() {⋯
 1091                    }
 1092                }
 1093            "
 1094            .unindent(),
 1095        );
 1096
 1097        editor.fold(&Fold, window, cx);
 1098        assert_eq!(
 1099            editor.display_text(cx),
 1100            "
 1101                impl Foo {⋯
 1102                }
 1103            "
 1104            .unindent(),
 1105        );
 1106
 1107        editor.unfold_lines(&UnfoldLines, window, cx);
 1108        assert_eq!(
 1109            editor.display_text(cx),
 1110            "
 1111                impl Foo {
 1112                    // Hello!
 1113
 1114                    fn a() {
 1115                        1
 1116                    }
 1117
 1118                    fn b() {⋯
 1119                    }
 1120
 1121                    fn c() {⋯
 1122                    }
 1123                }
 1124            "
 1125            .unindent(),
 1126        );
 1127
 1128        editor.unfold_lines(&UnfoldLines, window, cx);
 1129        assert_eq!(
 1130            editor.display_text(cx),
 1131            editor.buffer.read(cx).read(cx).text()
 1132        );
 1133    });
 1134}
 1135
 1136#[gpui::test]
 1137fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1138    init_test(cx, |_| {});
 1139
 1140    let editor = cx.add_window(|window, cx| {
 1141        let buffer = MultiBuffer::build_simple(
 1142            &"
 1143                class Foo:
 1144                    # Hello!
 1145
 1146                    def a():
 1147                        print(1)
 1148
 1149                    def b():
 1150                        print(2)
 1151
 1152                    def c():
 1153                        print(3)
 1154            "
 1155            .unindent(),
 1156            cx,
 1157        );
 1158        build_editor(buffer, window, cx)
 1159    });
 1160
 1161    _ = editor.update(cx, |editor, window, cx| {
 1162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1163            s.select_display_ranges([
 1164                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1165            ]);
 1166        });
 1167        editor.fold(&Fold, window, cx);
 1168        assert_eq!(
 1169            editor.display_text(cx),
 1170            "
 1171                class Foo:
 1172                    # Hello!
 1173
 1174                    def a():
 1175                        print(1)
 1176
 1177                    def b():⋯
 1178
 1179                    def c():⋯
 1180            "
 1181            .unindent(),
 1182        );
 1183
 1184        editor.fold(&Fold, window, cx);
 1185        assert_eq!(
 1186            editor.display_text(cx),
 1187            "
 1188                class Foo:⋯
 1189            "
 1190            .unindent(),
 1191        );
 1192
 1193        editor.unfold_lines(&UnfoldLines, window, cx);
 1194        assert_eq!(
 1195            editor.display_text(cx),
 1196            "
 1197                class Foo:
 1198                    # Hello!
 1199
 1200                    def a():
 1201                        print(1)
 1202
 1203                    def b():⋯
 1204
 1205                    def c():⋯
 1206            "
 1207            .unindent(),
 1208        );
 1209
 1210        editor.unfold_lines(&UnfoldLines, window, cx);
 1211        assert_eq!(
 1212            editor.display_text(cx),
 1213            editor.buffer.read(cx).read(cx).text()
 1214        );
 1215    });
 1216}
 1217
 1218#[gpui::test]
 1219fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1220    init_test(cx, |_| {});
 1221
 1222    let editor = cx.add_window(|window, cx| {
 1223        let buffer = MultiBuffer::build_simple(
 1224            &"
 1225                class Foo:
 1226                    # Hello!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235                    def c():
 1236                        print(3)
 1237
 1238
 1239            "
 1240            .unindent(),
 1241            cx,
 1242        );
 1243        build_editor(buffer, window, cx)
 1244    });
 1245
 1246    _ = editor.update(cx, |editor, window, cx| {
 1247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1248            s.select_display_ranges([
 1249                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1250            ]);
 1251        });
 1252        editor.fold(&Fold, window, cx);
 1253        assert_eq!(
 1254            editor.display_text(cx),
 1255            "
 1256                class Foo:
 1257                    # Hello!
 1258
 1259                    def a():
 1260                        print(1)
 1261
 1262                    def b():⋯
 1263
 1264
 1265                    def c():⋯
 1266
 1267
 1268            "
 1269            .unindent(),
 1270        );
 1271
 1272        editor.fold(&Fold, window, cx);
 1273        assert_eq!(
 1274            editor.display_text(cx),
 1275            "
 1276                class Foo:⋯
 1277
 1278
 1279            "
 1280            .unindent(),
 1281        );
 1282
 1283        editor.unfold_lines(&UnfoldLines, window, cx);
 1284        assert_eq!(
 1285            editor.display_text(cx),
 1286            "
 1287                class Foo:
 1288                    # Hello!
 1289
 1290                    def a():
 1291                        print(1)
 1292
 1293                    def b():⋯
 1294
 1295
 1296                    def c():⋯
 1297
 1298
 1299            "
 1300            .unindent(),
 1301        );
 1302
 1303        editor.unfold_lines(&UnfoldLines, window, cx);
 1304        assert_eq!(
 1305            editor.display_text(cx),
 1306            editor.buffer.read(cx).read(cx).text()
 1307        );
 1308    });
 1309}
 1310
 1311#[gpui::test]
 1312fn test_fold_at_level(cx: &mut TestAppContext) {
 1313    init_test(cx, |_| {});
 1314
 1315    let editor = cx.add_window(|window, cx| {
 1316        let buffer = MultiBuffer::build_simple(
 1317            &"
 1318                class Foo:
 1319                    # Hello!
 1320
 1321                    def a():
 1322                        print(1)
 1323
 1324                    def b():
 1325                        print(2)
 1326
 1327
 1328                class Bar:
 1329                    # World!
 1330
 1331                    def a():
 1332                        print(1)
 1333
 1334                    def b():
 1335                        print(2)
 1336
 1337
 1338            "
 1339            .unindent(),
 1340            cx,
 1341        );
 1342        build_editor(buffer, window, cx)
 1343    });
 1344
 1345    _ = editor.update(cx, |editor, window, cx| {
 1346        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1347        assert_eq!(
 1348            editor.display_text(cx),
 1349            "
 1350                class Foo:
 1351                    # Hello!
 1352
 1353                    def a():⋯
 1354
 1355                    def b():⋯
 1356
 1357
 1358                class Bar:
 1359                    # World!
 1360
 1361                    def a():⋯
 1362
 1363                    def b():⋯
 1364
 1365
 1366            "
 1367            .unindent(),
 1368        );
 1369
 1370        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1371        assert_eq!(
 1372            editor.display_text(cx),
 1373            "
 1374                class Foo:⋯
 1375
 1376
 1377                class Bar:⋯
 1378
 1379
 1380            "
 1381            .unindent(),
 1382        );
 1383
 1384        editor.unfold_all(&UnfoldAll, window, cx);
 1385        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1386        assert_eq!(
 1387            editor.display_text(cx),
 1388            "
 1389                class Foo:
 1390                    # Hello!
 1391
 1392                    def a():
 1393                        print(1)
 1394
 1395                    def b():
 1396                        print(2)
 1397
 1398
 1399                class Bar:
 1400                    # World!
 1401
 1402                    def a():
 1403                        print(1)
 1404
 1405                    def b():
 1406                        print(2)
 1407
 1408
 1409            "
 1410            .unindent(),
 1411        );
 1412
 1413        assert_eq!(
 1414            editor.display_text(cx),
 1415            editor.buffer.read(cx).read(cx).text()
 1416        );
 1417        let (_, positions) = marked_text_ranges(
 1418            &"
 1419                       class Foo:
 1420                           # Hello!
 1421
 1422                           def a():
 1423                              print(1)
 1424
 1425                           def b():
 1426                               p«riˇ»nt(2)
 1427
 1428
 1429                       class Bar:
 1430                           # World!
 1431
 1432                           def a():
 1433                               «ˇprint(1)
 1434
 1435                           def b():
 1436                               print(2)»
 1437
 1438
 1439                   "
 1440            .unindent(),
 1441            true,
 1442        );
 1443
 1444        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1445            s.select_ranges(
 1446                positions
 1447                    .iter()
 1448                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1449            )
 1450        });
 1451
 1452        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1453        assert_eq!(
 1454            editor.display_text(cx),
 1455            "
 1456                class Foo:
 1457                    # Hello!
 1458
 1459                    def a():⋯
 1460
 1461                    def b():
 1462                        print(2)
 1463
 1464
 1465                class Bar:
 1466                    # World!
 1467
 1468                    def a():
 1469                        print(1)
 1470
 1471                    def b():
 1472                        print(2)
 1473
 1474
 1475            "
 1476            .unindent(),
 1477        );
 1478    });
 1479}
 1480
 1481#[gpui::test]
 1482fn test_move_cursor(cx: &mut TestAppContext) {
 1483    init_test(cx, |_| {});
 1484
 1485    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1486    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1487
 1488    buffer.update(cx, |buffer, cx| {
 1489        buffer.edit(
 1490            vec![
 1491                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1492                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1493            ],
 1494            None,
 1495            cx,
 1496        );
 1497    });
 1498    _ = editor.update(cx, |editor, window, cx| {
 1499        assert_eq!(
 1500            display_ranges(editor, cx),
 1501            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1502        );
 1503
 1504        editor.move_down(&MoveDown, window, cx);
 1505        assert_eq!(
 1506            display_ranges(editor, cx),
 1507            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1508        );
 1509
 1510        editor.move_right(&MoveRight, window, cx);
 1511        assert_eq!(
 1512            display_ranges(editor, cx),
 1513            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1514        );
 1515
 1516        editor.move_left(&MoveLeft, window, cx);
 1517        assert_eq!(
 1518            display_ranges(editor, cx),
 1519            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1520        );
 1521
 1522        editor.move_up(&MoveUp, window, cx);
 1523        assert_eq!(
 1524            display_ranges(editor, cx),
 1525            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1526        );
 1527
 1528        editor.move_to_end(&MoveToEnd, window, cx);
 1529        assert_eq!(
 1530            display_ranges(editor, cx),
 1531            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1532        );
 1533
 1534        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1535        assert_eq!(
 1536            display_ranges(editor, cx),
 1537            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1538        );
 1539
 1540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1541            s.select_display_ranges([
 1542                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1543            ]);
 1544        });
 1545        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1546        assert_eq!(
 1547            display_ranges(editor, cx),
 1548            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1549        );
 1550
 1551        editor.select_to_end(&SelectToEnd, window, cx);
 1552        assert_eq!(
 1553            display_ranges(editor, cx),
 1554            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1555        );
 1556    });
 1557}
 1558
 1559#[gpui::test]
 1560fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1561    init_test(cx, |_| {});
 1562
 1563    let editor = cx.add_window(|window, cx| {
 1564        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1565        build_editor(buffer, window, cx)
 1566    });
 1567
 1568    assert_eq!('🟥'.len_utf8(), 4);
 1569    assert_eq!('α'.len_utf8(), 2);
 1570
 1571    _ = editor.update(cx, |editor, window, cx| {
 1572        editor.fold_creases(
 1573            vec![
 1574                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1576                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1577            ],
 1578            true,
 1579            window,
 1580            cx,
 1581        );
 1582        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1583
 1584        editor.move_right(&MoveRight, window, cx);
 1585        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1588        editor.move_right(&MoveRight, window, cx);
 1589        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1590
 1591        editor.move_down(&MoveDown, window, cx);
 1592        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1593        editor.move_left(&MoveLeft, window, cx);
 1594        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1595        editor.move_left(&MoveLeft, window, cx);
 1596        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1597        editor.move_left(&MoveLeft, window, cx);
 1598        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1599
 1600        editor.move_down(&MoveDown, window, cx);
 1601        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1602        editor.move_right(&MoveRight, window, cx);
 1603        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1604        editor.move_right(&MoveRight, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1606        editor.move_right(&MoveRight, window, cx);
 1607        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1608
 1609        editor.move_up(&MoveUp, window, cx);
 1610        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1611        editor.move_down(&MoveDown, window, cx);
 1612        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1613        editor.move_up(&MoveUp, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1615
 1616        editor.move_up(&MoveUp, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1620        editor.move_left(&MoveLeft, window, cx);
 1621        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1622    });
 1623}
 1624
 1625#[gpui::test]
 1626fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1627    init_test(cx, |_| {});
 1628
 1629    let editor = cx.add_window(|window, cx| {
 1630        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1631        build_editor(buffer, window, cx)
 1632    });
 1633    _ = editor.update(cx, |editor, window, cx| {
 1634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1635            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1636        });
 1637
 1638        // moving above start of document should move selection to start of document,
 1639        // but the next move down should still be at the original goal_x
 1640        editor.move_up(&MoveUp, window, cx);
 1641        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1642
 1643        editor.move_down(&MoveDown, window, cx);
 1644        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1645
 1646        editor.move_down(&MoveDown, window, cx);
 1647        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1648
 1649        editor.move_down(&MoveDown, window, cx);
 1650        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1651
 1652        editor.move_down(&MoveDown, window, cx);
 1653        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1654
 1655        // moving past end of document should not change goal_x
 1656        editor.move_down(&MoveDown, window, cx);
 1657        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1661
 1662        editor.move_up(&MoveUp, window, cx);
 1663        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1664
 1665        editor.move_up(&MoveUp, window, cx);
 1666        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1667
 1668        editor.move_up(&MoveUp, window, cx);
 1669        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1670    });
 1671}
 1672
 1673#[gpui::test]
 1674fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1675    init_test(cx, |_| {});
 1676    let move_to_beg = MoveToBeginningOfLine {
 1677        stop_at_soft_wraps: true,
 1678        stop_at_indent: true,
 1679    };
 1680
 1681    let delete_to_beg = DeleteToBeginningOfLine {
 1682        stop_at_indent: false,
 1683    };
 1684
 1685    let move_to_end = MoveToEndOfLine {
 1686        stop_at_soft_wraps: true,
 1687    };
 1688
 1689    let editor = cx.add_window(|window, cx| {
 1690        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1691        build_editor(buffer, window, cx)
 1692    });
 1693    _ = editor.update(cx, |editor, window, cx| {
 1694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1695            s.select_display_ranges([
 1696                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1697                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1698            ]);
 1699        });
 1700    });
 1701
 1702    _ = editor.update(cx, |editor, window, cx| {
 1703        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1704        assert_eq!(
 1705            display_ranges(editor, cx),
 1706            &[
 1707                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1708                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1709            ]
 1710        );
 1711    });
 1712
 1713    _ = editor.update(cx, |editor, window, cx| {
 1714        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1715        assert_eq!(
 1716            display_ranges(editor, cx),
 1717            &[
 1718                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1719                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1720            ]
 1721        );
 1722    });
 1723
 1724    _ = editor.update(cx, |editor, window, cx| {
 1725        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1726        assert_eq!(
 1727            display_ranges(editor, cx),
 1728            &[
 1729                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1730                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1731            ]
 1732        );
 1733    });
 1734
 1735    _ = editor.update(cx, |editor, window, cx| {
 1736        editor.move_to_end_of_line(&move_to_end, window, cx);
 1737        assert_eq!(
 1738            display_ranges(editor, cx),
 1739            &[
 1740                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1741                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1742            ]
 1743        );
 1744    });
 1745
 1746    // Moving to the end of line again is a no-op.
 1747    _ = editor.update(cx, |editor, window, cx| {
 1748        editor.move_to_end_of_line(&move_to_end, window, cx);
 1749        assert_eq!(
 1750            display_ranges(editor, cx),
 1751            &[
 1752                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1753                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1754            ]
 1755        );
 1756    });
 1757
 1758    _ = editor.update(cx, |editor, window, cx| {
 1759        editor.move_left(&MoveLeft, window, cx);
 1760        editor.select_to_beginning_of_line(
 1761            &SelectToBeginningOfLine {
 1762                stop_at_soft_wraps: true,
 1763                stop_at_indent: true,
 1764            },
 1765            window,
 1766            cx,
 1767        );
 1768        assert_eq!(
 1769            display_ranges(editor, cx),
 1770            &[
 1771                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1772                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1773            ]
 1774        );
 1775    });
 1776
 1777    _ = editor.update(cx, |editor, window, cx| {
 1778        editor.select_to_beginning_of_line(
 1779            &SelectToBeginningOfLine {
 1780                stop_at_soft_wraps: true,
 1781                stop_at_indent: true,
 1782            },
 1783            window,
 1784            cx,
 1785        );
 1786        assert_eq!(
 1787            display_ranges(editor, cx),
 1788            &[
 1789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1790                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1791            ]
 1792        );
 1793    });
 1794
 1795    _ = editor.update(cx, |editor, window, cx| {
 1796        editor.select_to_beginning_of_line(
 1797            &SelectToBeginningOfLine {
 1798                stop_at_soft_wraps: true,
 1799                stop_at_indent: true,
 1800            },
 1801            window,
 1802            cx,
 1803        );
 1804        assert_eq!(
 1805            display_ranges(editor, cx),
 1806            &[
 1807                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1808                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1809            ]
 1810        );
 1811    });
 1812
 1813    _ = editor.update(cx, |editor, window, cx| {
 1814        editor.select_to_end_of_line(
 1815            &SelectToEndOfLine {
 1816                stop_at_soft_wraps: true,
 1817            },
 1818            window,
 1819            cx,
 1820        );
 1821        assert_eq!(
 1822            display_ranges(editor, cx),
 1823            &[
 1824                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1825                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1826            ]
 1827        );
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1832        assert_eq!(editor.display_text(cx), "ab\n  de");
 1833        assert_eq!(
 1834            display_ranges(editor, cx),
 1835            &[
 1836                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1837                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1838            ]
 1839        );
 1840    });
 1841
 1842    _ = editor.update(cx, |editor, window, cx| {
 1843        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1844        assert_eq!(editor.display_text(cx), "\n");
 1845        assert_eq!(
 1846            display_ranges(editor, cx),
 1847            &[
 1848                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1849                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1850            ]
 1851        );
 1852    });
 1853}
 1854
 1855#[gpui::test]
 1856fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1857    init_test(cx, |_| {});
 1858    let move_to_beg = MoveToBeginningOfLine {
 1859        stop_at_soft_wraps: false,
 1860        stop_at_indent: false,
 1861    };
 1862
 1863    let move_to_end = MoveToEndOfLine {
 1864        stop_at_soft_wraps: false,
 1865    };
 1866
 1867    let editor = cx.add_window(|window, cx| {
 1868        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1869        build_editor(buffer, window, cx)
 1870    });
 1871
 1872    _ = editor.update(cx, |editor, window, cx| {
 1873        editor.set_wrap_width(Some(140.0.into()), cx);
 1874
 1875        // We expect the following lines after wrapping
 1876        // ```
 1877        // thequickbrownfox
 1878        // jumpedoverthelazydo
 1879        // gs
 1880        // ```
 1881        // The final `gs` was soft-wrapped onto a new line.
 1882        assert_eq!(
 1883            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1884            editor.display_text(cx),
 1885        );
 1886
 1887        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1888        // Start the cursor at the `k` on the first line
 1889        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1890            s.select_display_ranges([
 1891                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1892            ]);
 1893        });
 1894
 1895        // Moving to the beginning of the line should put us at the beginning of the line.
 1896        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1897        assert_eq!(
 1898            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1899            display_ranges(editor, cx)
 1900        );
 1901
 1902        // Moving to the end of the line should put us at the end of the line.
 1903        editor.move_to_end_of_line(&move_to_end, window, cx);
 1904        assert_eq!(
 1905            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1906            display_ranges(editor, cx)
 1907        );
 1908
 1909        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1910        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1912            s.select_display_ranges([
 1913                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1914            ]);
 1915        });
 1916
 1917        // Moving to the beginning of the line should put us at the start of the second line of
 1918        // display text, i.e., the `j`.
 1919        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1920        assert_eq!(
 1921            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1922            display_ranges(editor, cx)
 1923        );
 1924
 1925        // Moving to the beginning of the line again should be a no-op.
 1926        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1927        assert_eq!(
 1928            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1929            display_ranges(editor, cx)
 1930        );
 1931
 1932        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1933        // next display line.
 1934        editor.move_to_end_of_line(&move_to_end, window, cx);
 1935        assert_eq!(
 1936            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1937            display_ranges(editor, cx)
 1938        );
 1939
 1940        // Moving to the end of the line again should be a no-op.
 1941        editor.move_to_end_of_line(&move_to_end, window, cx);
 1942        assert_eq!(
 1943            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1944            display_ranges(editor, cx)
 1945        );
 1946    });
 1947}
 1948
 1949#[gpui::test]
 1950fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1951    init_test(cx, |_| {});
 1952
 1953    let move_to_beg = MoveToBeginningOfLine {
 1954        stop_at_soft_wraps: true,
 1955        stop_at_indent: true,
 1956    };
 1957
 1958    let select_to_beg = SelectToBeginningOfLine {
 1959        stop_at_soft_wraps: true,
 1960        stop_at_indent: true,
 1961    };
 1962
 1963    let delete_to_beg = DeleteToBeginningOfLine {
 1964        stop_at_indent: true,
 1965    };
 1966
 1967    let move_to_end = MoveToEndOfLine {
 1968        stop_at_soft_wraps: false,
 1969    };
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1978            s.select_display_ranges([
 1979                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1980                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1981            ]);
 1982        });
 1983
 1984        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1985        // and the second cursor at the first non-whitespace character in the line.
 1986        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1987        assert_eq!(
 1988            display_ranges(editor, cx),
 1989            &[
 1990                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1991                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1992            ]
 1993        );
 1994
 1995        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1996        // and should move the second cursor to the beginning of the line.
 1997        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1998        assert_eq!(
 1999            display_ranges(editor, cx),
 2000            &[
 2001                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2002                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2003            ]
 2004        );
 2005
 2006        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2007        // and should move the second cursor back to the first non-whitespace character in the line.
 2008        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2009        assert_eq!(
 2010            display_ranges(editor, cx),
 2011            &[
 2012                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2013                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2014            ]
 2015        );
 2016
 2017        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2018        // and to the first non-whitespace character in the line for the second cursor.
 2019        editor.move_to_end_of_line(&move_to_end, window, cx);
 2020        editor.move_left(&MoveLeft, window, cx);
 2021        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2022        assert_eq!(
 2023            display_ranges(editor, cx),
 2024            &[
 2025                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2026                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2027            ]
 2028        );
 2029
 2030        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2031        // and should select to the beginning of the line for the second cursor.
 2032        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2033        assert_eq!(
 2034            display_ranges(editor, cx),
 2035            &[
 2036                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2037                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2038            ]
 2039        );
 2040
 2041        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2042        // and should delete to the first non-whitespace character in the line for the second cursor.
 2043        editor.move_to_end_of_line(&move_to_end, window, cx);
 2044        editor.move_left(&MoveLeft, window, cx);
 2045        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2046        assert_eq!(editor.text(cx), "c\n  f");
 2047    });
 2048}
 2049
 2050#[gpui::test]
 2051fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2052    init_test(cx, |_| {});
 2053
 2054    let move_to_beg = MoveToBeginningOfLine {
 2055        stop_at_soft_wraps: true,
 2056        stop_at_indent: true,
 2057    };
 2058
 2059    let editor = cx.add_window(|window, cx| {
 2060        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2061        build_editor(buffer, window, cx)
 2062    });
 2063
 2064    _ = editor.update(cx, |editor, window, cx| {
 2065        // test cursor between line_start and indent_start
 2066        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2067            s.select_display_ranges([
 2068                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2069            ]);
 2070        });
 2071
 2072        // cursor should move to line_start
 2073        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2074        assert_eq!(
 2075            display_ranges(editor, cx),
 2076            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2077        );
 2078
 2079        // cursor should move to indent_start
 2080        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2081        assert_eq!(
 2082            display_ranges(editor, cx),
 2083            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2084        );
 2085
 2086        // cursor should move to back to line_start
 2087        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2088        assert_eq!(
 2089            display_ranges(editor, cx),
 2090            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2091        );
 2092    });
 2093}
 2094
 2095#[gpui::test]
 2096fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2097    init_test(cx, |_| {});
 2098
 2099    let editor = cx.add_window(|window, cx| {
 2100        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2101        build_editor(buffer, window, cx)
 2102    });
 2103    _ = editor.update(cx, |editor, window, cx| {
 2104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2105            s.select_display_ranges([
 2106                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2107                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2108            ])
 2109        });
 2110        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2111        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2112
 2113        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2114        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2115
 2116        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2117        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2118
 2119        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2120        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2121
 2122        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2123        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2124
 2125        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2126        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2127
 2128        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2129        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2130
 2131        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2132        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2133
 2134        editor.move_right(&MoveRight, window, cx);
 2135        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2136        assert_selection_ranges(
 2137            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2138            editor,
 2139            cx,
 2140        );
 2141
 2142        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2143        assert_selection_ranges(
 2144            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2145            editor,
 2146            cx,
 2147        );
 2148
 2149        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2150        assert_selection_ranges(
 2151            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2152            editor,
 2153            cx,
 2154        );
 2155    });
 2156}
 2157
 2158#[gpui::test]
 2159fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2160    init_test(cx, |_| {});
 2161
 2162    let editor = cx.add_window(|window, cx| {
 2163        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2164        build_editor(buffer, window, cx)
 2165    });
 2166
 2167    _ = editor.update(cx, |editor, window, cx| {
 2168        editor.set_wrap_width(Some(140.0.into()), cx);
 2169        assert_eq!(
 2170            editor.display_text(cx),
 2171            "use one::{\n    two::three::\n    four::five\n};"
 2172        );
 2173
 2174        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2175            s.select_display_ranges([
 2176                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2177            ]);
 2178        });
 2179
 2180        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2181        assert_eq!(
 2182            display_ranges(editor, cx),
 2183            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2184        );
 2185
 2186        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2187        assert_eq!(
 2188            display_ranges(editor, cx),
 2189            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2190        );
 2191
 2192        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2193        assert_eq!(
 2194            display_ranges(editor, cx),
 2195            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2196        );
 2197
 2198        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2199        assert_eq!(
 2200            display_ranges(editor, cx),
 2201            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2202        );
 2203
 2204        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2205        assert_eq!(
 2206            display_ranges(editor, cx),
 2207            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2208        );
 2209
 2210        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2211        assert_eq!(
 2212            display_ranges(editor, cx),
 2213            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2214        );
 2215    });
 2216}
 2217
 2218#[gpui::test]
 2219async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2220    init_test(cx, |_| {});
 2221    let mut cx = EditorTestContext::new(cx).await;
 2222
 2223    let line_height = cx.update_editor(|editor, window, cx| {
 2224        editor
 2225            .style(cx)
 2226            .text
 2227            .line_height_in_pixels(window.rem_size())
 2228    });
 2229    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2230
 2231    cx.set_state(
 2232        &r#"ˇone
 2233        two
 2234
 2235        three
 2236        fourˇ
 2237        five
 2238
 2239        six"#
 2240            .unindent(),
 2241    );
 2242
 2243    cx.update_editor(|editor, window, cx| {
 2244        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2245    });
 2246    cx.assert_editor_state(
 2247        &r#"one
 2248        two
 2249        ˇ
 2250        three
 2251        four
 2252        five
 2253        ˇ
 2254        six"#
 2255            .unindent(),
 2256    );
 2257
 2258    cx.update_editor(|editor, window, cx| {
 2259        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2260    });
 2261    cx.assert_editor_state(
 2262        &r#"one
 2263        two
 2264
 2265        three
 2266        four
 2267        five
 2268        ˇ
 2269        sixˇ"#
 2270            .unindent(),
 2271    );
 2272
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2275    });
 2276    cx.assert_editor_state(
 2277        &r#"one
 2278        two
 2279
 2280        three
 2281        four
 2282        five
 2283
 2284        sixˇ"#
 2285            .unindent(),
 2286    );
 2287
 2288    cx.update_editor(|editor, window, cx| {
 2289        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2290    });
 2291    cx.assert_editor_state(
 2292        &r#"one
 2293        two
 2294
 2295        three
 2296        four
 2297        five
 2298        ˇ
 2299        six"#
 2300            .unindent(),
 2301    );
 2302
 2303    cx.update_editor(|editor, window, cx| {
 2304        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2305    });
 2306    cx.assert_editor_state(
 2307        &r#"one
 2308        two
 2309        ˇ
 2310        three
 2311        four
 2312        five
 2313
 2314        six"#
 2315            .unindent(),
 2316    );
 2317
 2318    cx.update_editor(|editor, window, cx| {
 2319        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2320    });
 2321    cx.assert_editor_state(
 2322        &r#"ˇone
 2323        two
 2324
 2325        three
 2326        four
 2327        five
 2328
 2329        six"#
 2330            .unindent(),
 2331    );
 2332}
 2333
 2334#[gpui::test]
 2335async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2336    init_test(cx, |_| {});
 2337    let mut cx = EditorTestContext::new(cx).await;
 2338    let line_height = cx.update_editor(|editor, window, cx| {
 2339        editor
 2340            .style(cx)
 2341            .text
 2342            .line_height_in_pixels(window.rem_size())
 2343    });
 2344    let window = cx.window;
 2345    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2346
 2347    cx.set_state(
 2348        r#"ˇone
 2349        two
 2350        three
 2351        four
 2352        five
 2353        six
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#,
 2359    );
 2360
 2361    cx.update_editor(|editor, window, cx| {
 2362        assert_eq!(
 2363            editor.snapshot(window, cx).scroll_position(),
 2364            gpui::Point::new(0., 0.)
 2365        );
 2366        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2367        assert_eq!(
 2368            editor.snapshot(window, cx).scroll_position(),
 2369            gpui::Point::new(0., 3.)
 2370        );
 2371        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2372        assert_eq!(
 2373            editor.snapshot(window, cx).scroll_position(),
 2374            gpui::Point::new(0., 6.)
 2375        );
 2376        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2377        assert_eq!(
 2378            editor.snapshot(window, cx).scroll_position(),
 2379            gpui::Point::new(0., 3.)
 2380        );
 2381
 2382        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2383        assert_eq!(
 2384            editor.snapshot(window, cx).scroll_position(),
 2385            gpui::Point::new(0., 1.)
 2386        );
 2387        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2388        assert_eq!(
 2389            editor.snapshot(window, cx).scroll_position(),
 2390            gpui::Point::new(0., 3.)
 2391        );
 2392    });
 2393}
 2394
 2395#[gpui::test]
 2396async fn test_autoscroll(cx: &mut TestAppContext) {
 2397    init_test(cx, |_| {});
 2398    let mut cx = EditorTestContext::new(cx).await;
 2399
 2400    let line_height = cx.update_editor(|editor, window, cx| {
 2401        editor.set_vertical_scroll_margin(2, cx);
 2402        editor
 2403            .style(cx)
 2404            .text
 2405            .line_height_in_pixels(window.rem_size())
 2406    });
 2407    let window = cx.window;
 2408    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2409
 2410    cx.set_state(
 2411        r#"ˇone
 2412            two
 2413            three
 2414            four
 2415            five
 2416            six
 2417            seven
 2418            eight
 2419            nine
 2420            ten
 2421        "#,
 2422    );
 2423    cx.update_editor(|editor, window, cx| {
 2424        assert_eq!(
 2425            editor.snapshot(window, cx).scroll_position(),
 2426            gpui::Point::new(0., 0.0)
 2427        );
 2428    });
 2429
 2430    // Add a cursor below the visible area. Since both cursors cannot fit
 2431    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2432    // allows the vertical scroll margin below that cursor.
 2433    cx.update_editor(|editor, window, cx| {
 2434        editor.change_selections(Default::default(), window, cx, |selections| {
 2435            selections.select_ranges([
 2436                Point::new(0, 0)..Point::new(0, 0),
 2437                Point::new(6, 0)..Point::new(6, 0),
 2438            ]);
 2439        })
 2440    });
 2441    cx.update_editor(|editor, window, cx| {
 2442        assert_eq!(
 2443            editor.snapshot(window, cx).scroll_position(),
 2444            gpui::Point::new(0., 3.0)
 2445        );
 2446    });
 2447
 2448    // Move down. The editor cursor scrolls down to track the newest cursor.
 2449    cx.update_editor(|editor, window, cx| {
 2450        editor.move_down(&Default::default(), window, cx);
 2451    });
 2452    cx.update_editor(|editor, window, cx| {
 2453        assert_eq!(
 2454            editor.snapshot(window, cx).scroll_position(),
 2455            gpui::Point::new(0., 4.0)
 2456        );
 2457    });
 2458
 2459    // Add a cursor above the visible area. Since both cursors fit on screen,
 2460    // the editor scrolls to show both.
 2461    cx.update_editor(|editor, window, cx| {
 2462        editor.change_selections(Default::default(), window, cx, |selections| {
 2463            selections.select_ranges([
 2464                Point::new(1, 0)..Point::new(1, 0),
 2465                Point::new(6, 0)..Point::new(6, 0),
 2466            ]);
 2467        })
 2468    });
 2469    cx.update_editor(|editor, window, cx| {
 2470        assert_eq!(
 2471            editor.snapshot(window, cx).scroll_position(),
 2472            gpui::Point::new(0., 1.0)
 2473        );
 2474    });
 2475}
 2476
 2477#[gpui::test]
 2478async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2479    init_test(cx, |_| {});
 2480    let mut cx = EditorTestContext::new(cx).await;
 2481
 2482    let line_height = cx.update_editor(|editor, window, cx| {
 2483        editor
 2484            .style(cx)
 2485            .text
 2486            .line_height_in_pixels(window.rem_size())
 2487    });
 2488    let window = cx.window;
 2489    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2490    cx.set_state(
 2491        &r#"
 2492        ˇone
 2493        two
 2494        threeˇ
 2495        four
 2496        five
 2497        six
 2498        seven
 2499        eight
 2500        nine
 2501        ten
 2502        "#
 2503        .unindent(),
 2504    );
 2505
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.move_page_down(&MovePageDown::default(), window, cx)
 2508    });
 2509    cx.assert_editor_state(
 2510        &r#"
 2511        one
 2512        two
 2513        three
 2514        ˇfour
 2515        five
 2516        sixˇ
 2517        seven
 2518        eight
 2519        nine
 2520        ten
 2521        "#
 2522        .unindent(),
 2523    );
 2524
 2525    cx.update_editor(|editor, window, cx| {
 2526        editor.move_page_down(&MovePageDown::default(), window, cx)
 2527    });
 2528    cx.assert_editor_state(
 2529        &r#"
 2530        one
 2531        two
 2532        three
 2533        four
 2534        five
 2535        six
 2536        ˇseven
 2537        eight
 2538        nineˇ
 2539        ten
 2540        "#
 2541        .unindent(),
 2542    );
 2543
 2544    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2545    cx.assert_editor_state(
 2546        &r#"
 2547        one
 2548        two
 2549        three
 2550        ˇfour
 2551        five
 2552        sixˇ
 2553        seven
 2554        eight
 2555        nine
 2556        ten
 2557        "#
 2558        .unindent(),
 2559    );
 2560
 2561    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2562    cx.assert_editor_state(
 2563        &r#"
 2564        ˇone
 2565        two
 2566        threeˇ
 2567        four
 2568        five
 2569        six
 2570        seven
 2571        eight
 2572        nine
 2573        ten
 2574        "#
 2575        .unindent(),
 2576    );
 2577
 2578    // Test select collapsing
 2579    cx.update_editor(|editor, window, cx| {
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583    });
 2584    cx.assert_editor_state(
 2585        &r#"
 2586        one
 2587        two
 2588        three
 2589        four
 2590        five
 2591        six
 2592        seven
 2593        eight
 2594        nine
 2595        ˇten
 2596        ˇ"#
 2597        .unindent(),
 2598    );
 2599}
 2600
 2601#[gpui::test]
 2602async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2603    init_test(cx, |_| {});
 2604    let mut cx = EditorTestContext::new(cx).await;
 2605    cx.set_state("one «two threeˇ» four");
 2606    cx.update_editor(|editor, window, cx| {
 2607        editor.delete_to_beginning_of_line(
 2608            &DeleteToBeginningOfLine {
 2609                stop_at_indent: false,
 2610            },
 2611            window,
 2612            cx,
 2613        );
 2614        assert_eq!(editor.text(cx), " four");
 2615    });
 2616}
 2617
 2618#[gpui::test]
 2619async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2620    init_test(cx, |_| {});
 2621
 2622    let mut cx = EditorTestContext::new(cx).await;
 2623
 2624    // For an empty selection, the preceding word fragment is deleted.
 2625    // For non-empty selections, only selected characters are deleted.
 2626    cx.set_state("onˇe two t«hreˇ»e four");
 2627    cx.update_editor(|editor, window, cx| {
 2628        editor.delete_to_previous_word_start(
 2629            &DeleteToPreviousWordStart {
 2630                ignore_newlines: false,
 2631                ignore_brackets: false,
 2632            },
 2633            window,
 2634            cx,
 2635        );
 2636    });
 2637    cx.assert_editor_state("ˇe two tˇe four");
 2638
 2639    cx.set_state("e tˇwo te «fˇ»our");
 2640    cx.update_editor(|editor, window, cx| {
 2641        editor.delete_to_next_word_end(
 2642            &DeleteToNextWordEnd {
 2643                ignore_newlines: false,
 2644                ignore_brackets: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649    });
 2650    cx.assert_editor_state("e tˇ te ˇour");
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    cx.set_state("here is some text    ˇwith a space");
 2660    cx.update_editor(|editor, window, cx| {
 2661        editor.delete_to_previous_word_start(
 2662            &DeleteToPreviousWordStart {
 2663                ignore_newlines: false,
 2664                ignore_brackets: true,
 2665            },
 2666            window,
 2667            cx,
 2668        );
 2669    });
 2670    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2671    cx.assert_editor_state("here is some textˇwith a space");
 2672
 2673    cx.set_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: false,
 2678                ignore_brackets: false,
 2679            },
 2680            window,
 2681            cx,
 2682        );
 2683    });
 2684    cx.assert_editor_state("here is some textˇwith a space");
 2685
 2686    cx.set_state("here is some textˇ    with a space");
 2687    cx.update_editor(|editor, window, cx| {
 2688        editor.delete_to_next_word_end(
 2689            &DeleteToNextWordEnd {
 2690                ignore_newlines: false,
 2691                ignore_brackets: true,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    // Same happens in the other direction.
 2698    cx.assert_editor_state("here is some textˇwith a space");
 2699
 2700    cx.set_state("here is some textˇ    with a space");
 2701    cx.update_editor(|editor, window, cx| {
 2702        editor.delete_to_next_word_end(
 2703            &DeleteToNextWordEnd {
 2704                ignore_newlines: false,
 2705                ignore_brackets: false,
 2706            },
 2707            window,
 2708            cx,
 2709        );
 2710    });
 2711    cx.assert_editor_state("here is some textˇwith a space");
 2712
 2713    cx.set_state("here is some textˇ    with a space");
 2714    cx.update_editor(|editor, window, cx| {
 2715        editor.delete_to_next_word_end(
 2716            &DeleteToNextWordEnd {
 2717                ignore_newlines: true,
 2718                ignore_brackets: false,
 2719            },
 2720            window,
 2721            cx,
 2722        );
 2723    });
 2724    cx.assert_editor_state("here is some textˇwith a space");
 2725    cx.update_editor(|editor, window, cx| {
 2726        editor.delete_to_previous_word_start(
 2727            &DeleteToPreviousWordStart {
 2728                ignore_newlines: true,
 2729                ignore_brackets: false,
 2730            },
 2731            window,
 2732            cx,
 2733        );
 2734    });
 2735    cx.assert_editor_state("here is some ˇwith a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_previous_word_start(
 2738            &DeleteToPreviousWordStart {
 2739                ignore_newlines: true,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    // Single whitespaces are removed with the word behind them.
 2747    cx.assert_editor_state("here is ˇwith a space");
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    cx.assert_editor_state("here ˇwith a space");
 2759    cx.update_editor(|editor, window, cx| {
 2760        editor.delete_to_previous_word_start(
 2761            &DeleteToPreviousWordStart {
 2762                ignore_newlines: true,
 2763                ignore_brackets: false,
 2764            },
 2765            window,
 2766            cx,
 2767        );
 2768    });
 2769    cx.assert_editor_state("ˇwith a space");
 2770    cx.update_editor(|editor, window, cx| {
 2771        editor.delete_to_previous_word_start(
 2772            &DeleteToPreviousWordStart {
 2773                ignore_newlines: true,
 2774                ignore_brackets: false,
 2775            },
 2776            window,
 2777            cx,
 2778        );
 2779    });
 2780    cx.assert_editor_state("ˇwith a space");
 2781    cx.update_editor(|editor, window, cx| {
 2782        editor.delete_to_next_word_end(
 2783            &DeleteToNextWordEnd {
 2784                ignore_newlines: true,
 2785                ignore_brackets: false,
 2786            },
 2787            window,
 2788            cx,
 2789        );
 2790    });
 2791    // Same happens in the other direction.
 2792    cx.assert_editor_state("ˇ a space");
 2793    cx.update_editor(|editor, window, cx| {
 2794        editor.delete_to_next_word_end(
 2795            &DeleteToNextWordEnd {
 2796                ignore_newlines: true,
 2797                ignore_brackets: false,
 2798            },
 2799            window,
 2800            cx,
 2801        );
 2802    });
 2803    cx.assert_editor_state("ˇ space");
 2804    cx.update_editor(|editor, window, cx| {
 2805        editor.delete_to_next_word_end(
 2806            &DeleteToNextWordEnd {
 2807                ignore_newlines: true,
 2808                ignore_brackets: false,
 2809            },
 2810            window,
 2811            cx,
 2812        );
 2813    });
 2814    cx.assert_editor_state("ˇ");
 2815    cx.update_editor(|editor, window, cx| {
 2816        editor.delete_to_next_word_end(
 2817            &DeleteToNextWordEnd {
 2818                ignore_newlines: true,
 2819                ignore_brackets: false,
 2820            },
 2821            window,
 2822            cx,
 2823        );
 2824    });
 2825    cx.assert_editor_state("ˇ");
 2826    cx.update_editor(|editor, window, cx| {
 2827        editor.delete_to_previous_word_start(
 2828            &DeleteToPreviousWordStart {
 2829                ignore_newlines: true,
 2830                ignore_brackets: false,
 2831            },
 2832            window,
 2833            cx,
 2834        );
 2835    });
 2836    cx.assert_editor_state("ˇ");
 2837}
 2838
 2839#[gpui::test]
 2840async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2841    init_test(cx, |_| {});
 2842
 2843    let language = Arc::new(
 2844        Language::new(
 2845            LanguageConfig {
 2846                brackets: BracketPairConfig {
 2847                    pairs: vec![
 2848                        BracketPair {
 2849                            start: "\"".to_string(),
 2850                            end: "\"".to_string(),
 2851                            close: true,
 2852                            surround: true,
 2853                            newline: false,
 2854                        },
 2855                        BracketPair {
 2856                            start: "(".to_string(),
 2857                            end: ")".to_string(),
 2858                            close: true,
 2859                            surround: true,
 2860                            newline: true,
 2861                        },
 2862                    ],
 2863                    ..BracketPairConfig::default()
 2864                },
 2865                ..LanguageConfig::default()
 2866            },
 2867            Some(tree_sitter_rust::LANGUAGE.into()),
 2868        )
 2869        .with_brackets_query(
 2870            r#"
 2871                ("(" @open ")" @close)
 2872                ("\"" @open "\"" @close)
 2873            "#,
 2874        )
 2875        .unwrap(),
 2876    );
 2877
 2878    let mut cx = EditorTestContext::new(cx).await;
 2879    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2880
 2881    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2882    cx.update_editor(|editor, window, cx| {
 2883        editor.delete_to_previous_word_start(
 2884            &DeleteToPreviousWordStart {
 2885                ignore_newlines: true,
 2886                ignore_brackets: false,
 2887            },
 2888            window,
 2889            cx,
 2890        );
 2891    });
 2892    // Deletion stops before brackets if asked to not ignore them.
 2893    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2894    cx.update_editor(|editor, window, cx| {
 2895        editor.delete_to_previous_word_start(
 2896            &DeleteToPreviousWordStart {
 2897                ignore_newlines: true,
 2898                ignore_brackets: false,
 2899            },
 2900            window,
 2901            cx,
 2902        );
 2903    });
 2904    // Deletion has to remove a single bracket and then stop again.
 2905    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2906
 2907    cx.update_editor(|editor, window, cx| {
 2908        editor.delete_to_previous_word_start(
 2909            &DeleteToPreviousWordStart {
 2910                ignore_newlines: true,
 2911                ignore_brackets: false,
 2912            },
 2913            window,
 2914            cx,
 2915        );
 2916    });
 2917    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2918
 2919    cx.update_editor(|editor, window, cx| {
 2920        editor.delete_to_previous_word_start(
 2921            &DeleteToPreviousWordStart {
 2922                ignore_newlines: true,
 2923                ignore_brackets: false,
 2924            },
 2925            window,
 2926            cx,
 2927        );
 2928    });
 2929    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2930
 2931    cx.update_editor(|editor, window, cx| {
 2932        editor.delete_to_previous_word_start(
 2933            &DeleteToPreviousWordStart {
 2934                ignore_newlines: true,
 2935                ignore_brackets: false,
 2936            },
 2937            window,
 2938            cx,
 2939        );
 2940    });
 2941    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2942
 2943    cx.update_editor(|editor, window, cx| {
 2944        editor.delete_to_next_word_end(
 2945            &DeleteToNextWordEnd {
 2946                ignore_newlines: true,
 2947                ignore_brackets: false,
 2948            },
 2949            window,
 2950            cx,
 2951        );
 2952    });
 2953    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2954    cx.assert_editor_state(r#"ˇ");"#);
 2955
 2956    cx.update_editor(|editor, window, cx| {
 2957        editor.delete_to_next_word_end(
 2958            &DeleteToNextWordEnd {
 2959                ignore_newlines: true,
 2960                ignore_brackets: false,
 2961            },
 2962            window,
 2963            cx,
 2964        );
 2965    });
 2966    cx.assert_editor_state(r#"ˇ"#);
 2967
 2968    cx.update_editor(|editor, window, cx| {
 2969        editor.delete_to_next_word_end(
 2970            &DeleteToNextWordEnd {
 2971                ignore_newlines: true,
 2972                ignore_brackets: false,
 2973            },
 2974            window,
 2975            cx,
 2976        );
 2977    });
 2978    cx.assert_editor_state(r#"ˇ"#);
 2979
 2980    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2981    cx.update_editor(|editor, window, cx| {
 2982        editor.delete_to_previous_word_start(
 2983            &DeleteToPreviousWordStart {
 2984                ignore_newlines: true,
 2985                ignore_brackets: true,
 2986            },
 2987            window,
 2988            cx,
 2989        );
 2990    });
 2991    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2992}
 2993
 2994#[gpui::test]
 2995fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2996    init_test(cx, |_| {});
 2997
 2998    let editor = cx.add_window(|window, cx| {
 2999        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3000        build_editor(buffer, window, cx)
 3001    });
 3002    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3003        ignore_newlines: false,
 3004        ignore_brackets: false,
 3005    };
 3006    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3007        ignore_newlines: true,
 3008        ignore_brackets: false,
 3009    };
 3010
 3011    _ = editor.update(cx, |editor, window, cx| {
 3012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3013            s.select_display_ranges([
 3014                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3015            ])
 3016        });
 3017        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3018        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3019        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3020        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3021        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3022        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3029    });
 3030}
 3031
 3032#[gpui::test]
 3033fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3034    init_test(cx, |_| {});
 3035
 3036    let editor = cx.add_window(|window, cx| {
 3037        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3038        build_editor(buffer, window, cx)
 3039    });
 3040    let del_to_next_word_end = DeleteToNextWordEnd {
 3041        ignore_newlines: false,
 3042        ignore_brackets: false,
 3043    };
 3044    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3045        ignore_newlines: true,
 3046        ignore_brackets: false,
 3047    };
 3048
 3049    _ = editor.update(cx, |editor, window, cx| {
 3050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3051            s.select_display_ranges([
 3052                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3053            ])
 3054        });
 3055        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3056        assert_eq!(
 3057            editor.buffer.read(cx).read(cx).text(),
 3058            "one\n   two\nthree\n   four"
 3059        );
 3060        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3061        assert_eq!(
 3062            editor.buffer.read(cx).read(cx).text(),
 3063            "\n   two\nthree\n   four"
 3064        );
 3065        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3066        assert_eq!(
 3067            editor.buffer.read(cx).read(cx).text(),
 3068            "two\nthree\n   four"
 3069        );
 3070        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3071        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3072        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3074        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3076        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3078    });
 3079}
 3080
 3081#[gpui::test]
 3082fn test_newline(cx: &mut TestAppContext) {
 3083    init_test(cx, |_| {});
 3084
 3085    let editor = cx.add_window(|window, cx| {
 3086        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3087        build_editor(buffer, window, cx)
 3088    });
 3089
 3090    _ = editor.update(cx, |editor, window, cx| {
 3091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3092            s.select_display_ranges([
 3093                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3094                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3095                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3096            ])
 3097        });
 3098
 3099        editor.newline(&Newline, window, cx);
 3100        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3101    });
 3102}
 3103
 3104#[gpui::test]
 3105async fn test_newline_yaml(cx: &mut TestAppContext) {
 3106    init_test(cx, |_| {});
 3107
 3108    let mut cx = EditorTestContext::new(cx).await;
 3109    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3111
 3112    // Object (between 2 fields)
 3113    cx.set_state(indoc! {"
 3114    test:ˇ
 3115    hello: bye"});
 3116    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3117    cx.assert_editor_state(indoc! {"
 3118    test:
 3119        ˇ
 3120    hello: bye"});
 3121
 3122    // Object (first and single line)
 3123    cx.set_state(indoc! {"
 3124    test:ˇ"});
 3125    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3126    cx.assert_editor_state(indoc! {"
 3127    test:
 3128        ˇ"});
 3129
 3130    // Array with objects (after first element)
 3131    cx.set_state(indoc! {"
 3132    test:
 3133        - foo: barˇ"});
 3134    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3135    cx.assert_editor_state(indoc! {"
 3136    test:
 3137        - foo: bar
 3138        ˇ"});
 3139
 3140    // Array with objects and comment
 3141    cx.set_state(indoc! {"
 3142    test:
 3143        - foo: bar
 3144        - bar: # testˇ"});
 3145    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3146    cx.assert_editor_state(indoc! {"
 3147    test:
 3148        - foo: bar
 3149        - bar: # test
 3150            ˇ"});
 3151
 3152    // Array with objects (after second element)
 3153    cx.set_state(indoc! {"
 3154    test:
 3155        - foo: bar
 3156        - bar: fooˇ"});
 3157    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159    test:
 3160        - foo: bar
 3161        - bar: foo
 3162        ˇ"});
 3163
 3164    // Array with strings (after first element)
 3165    cx.set_state(indoc! {"
 3166    test:
 3167        - fooˇ"});
 3168    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170    test:
 3171        - foo
 3172        ˇ"});
 3173}
 3174
 3175#[gpui::test]
 3176fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3177    init_test(cx, |_| {});
 3178
 3179    let editor = cx.add_window(|window, cx| {
 3180        let buffer = MultiBuffer::build_simple(
 3181            "
 3182                a
 3183                b(
 3184                    X
 3185                )
 3186                c(
 3187                    X
 3188                )
 3189            "
 3190            .unindent()
 3191            .as_str(),
 3192            cx,
 3193        );
 3194        let mut editor = build_editor(buffer, window, cx);
 3195        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3196            s.select_ranges([
 3197                Point::new(2, 4)..Point::new(2, 5),
 3198                Point::new(5, 4)..Point::new(5, 5),
 3199            ])
 3200        });
 3201        editor
 3202    });
 3203
 3204    _ = editor.update(cx, |editor, window, cx| {
 3205        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3206        editor.buffer.update(cx, |buffer, cx| {
 3207            buffer.edit(
 3208                [
 3209                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3210                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3211                ],
 3212                None,
 3213                cx,
 3214            );
 3215            assert_eq!(
 3216                buffer.read(cx).text(),
 3217                "
 3218                    a
 3219                    b()
 3220                    c()
 3221                "
 3222                .unindent()
 3223            );
 3224        });
 3225        assert_eq!(
 3226            editor.selections.ranges(&editor.display_snapshot(cx)),
 3227            &[
 3228                Point::new(1, 2)..Point::new(1, 2),
 3229                Point::new(2, 2)..Point::new(2, 2),
 3230            ],
 3231        );
 3232
 3233        editor.newline(&Newline, window, cx);
 3234        assert_eq!(
 3235            editor.text(cx),
 3236            "
 3237                a
 3238                b(
 3239                )
 3240                c(
 3241                )
 3242            "
 3243            .unindent()
 3244        );
 3245
 3246        // The selections are moved after the inserted newlines
 3247        assert_eq!(
 3248            editor.selections.ranges(&editor.display_snapshot(cx)),
 3249            &[
 3250                Point::new(2, 0)..Point::new(2, 0),
 3251                Point::new(4, 0)..Point::new(4, 0),
 3252            ],
 3253        );
 3254    });
 3255}
 3256
 3257#[gpui::test]
 3258async fn test_newline_above(cx: &mut TestAppContext) {
 3259    init_test(cx, |settings| {
 3260        settings.defaults.tab_size = NonZeroU32::new(4)
 3261    });
 3262
 3263    let language = Arc::new(
 3264        Language::new(
 3265            LanguageConfig::default(),
 3266            Some(tree_sitter_rust::LANGUAGE.into()),
 3267        )
 3268        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3269        .unwrap(),
 3270    );
 3271
 3272    let mut cx = EditorTestContext::new(cx).await;
 3273    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3274    cx.set_state(indoc! {"
 3275        const a: ˇA = (
 3276 3277                «const_functionˇ»(ˇ),
 3278                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3279 3280        ˇ);ˇ
 3281    "});
 3282
 3283    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3284    cx.assert_editor_state(indoc! {"
 3285        ˇ
 3286        const a: A = (
 3287            ˇ
 3288            (
 3289                ˇ
 3290                ˇ
 3291                const_function(),
 3292                ˇ
 3293                ˇ
 3294                ˇ
 3295                ˇ
 3296                something_else,
 3297                ˇ
 3298            )
 3299            ˇ
 3300            ˇ
 3301        );
 3302    "});
 3303}
 3304
 3305#[gpui::test]
 3306async fn test_newline_below(cx: &mut TestAppContext) {
 3307    init_test(cx, |settings| {
 3308        settings.defaults.tab_size = NonZeroU32::new(4)
 3309    });
 3310
 3311    let language = Arc::new(
 3312        Language::new(
 3313            LanguageConfig::default(),
 3314            Some(tree_sitter_rust::LANGUAGE.into()),
 3315        )
 3316        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3317        .unwrap(),
 3318    );
 3319
 3320    let mut cx = EditorTestContext::new(cx).await;
 3321    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3322    cx.set_state(indoc! {"
 3323        const a: ˇA = (
 3324 3325                «const_functionˇ»(ˇ),
 3326                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3327 3328        ˇ);ˇ
 3329    "});
 3330
 3331    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3332    cx.assert_editor_state(indoc! {"
 3333        const a: A = (
 3334            ˇ
 3335            (
 3336                ˇ
 3337                const_function(),
 3338                ˇ
 3339                ˇ
 3340                something_else,
 3341                ˇ
 3342                ˇ
 3343                ˇ
 3344                ˇ
 3345            )
 3346            ˇ
 3347        );
 3348        ˇ
 3349        ˇ
 3350    "});
 3351}
 3352
 3353#[gpui::test]
 3354async fn test_newline_comments(cx: &mut TestAppContext) {
 3355    init_test(cx, |settings| {
 3356        settings.defaults.tab_size = NonZeroU32::new(4)
 3357    });
 3358
 3359    let language = Arc::new(Language::new(
 3360        LanguageConfig {
 3361            line_comments: vec!["// ".into()],
 3362            ..LanguageConfig::default()
 3363        },
 3364        None,
 3365    ));
 3366    {
 3367        let mut cx = EditorTestContext::new(cx).await;
 3368        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3369        cx.set_state(indoc! {"
 3370        // Fooˇ
 3371    "});
 3372
 3373        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3374        cx.assert_editor_state(indoc! {"
 3375        // Foo
 3376        // ˇ
 3377    "});
 3378        // Ensure that we add comment prefix when existing line contains space
 3379        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3380        cx.assert_editor_state(
 3381            indoc! {"
 3382        // Foo
 3383        //s
 3384        // ˇ
 3385    "}
 3386            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3387            .as_str(),
 3388        );
 3389        // Ensure that we add comment prefix when existing line does not contain space
 3390        cx.set_state(indoc! {"
 3391        // Foo
 3392        //ˇ
 3393    "});
 3394        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3395        cx.assert_editor_state(indoc! {"
 3396        // Foo
 3397        //
 3398        // ˇ
 3399    "});
 3400        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3401        cx.set_state(indoc! {"
 3402        ˇ// Foo
 3403    "});
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(indoc! {"
 3406
 3407        ˇ// Foo
 3408    "});
 3409    }
 3410    // Ensure that comment continuations can be disabled.
 3411    update_test_language_settings(cx, |settings| {
 3412        settings.defaults.extend_comment_on_newline = Some(false);
 3413    });
 3414    let mut cx = EditorTestContext::new(cx).await;
 3415    cx.set_state(indoc! {"
 3416        // Fooˇ
 3417    "});
 3418    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        // Foo
 3421        ˇ
 3422    "});
 3423}
 3424
 3425#[gpui::test]
 3426async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3427    init_test(cx, |settings| {
 3428        settings.defaults.tab_size = NonZeroU32::new(4)
 3429    });
 3430
 3431    let language = Arc::new(Language::new(
 3432        LanguageConfig {
 3433            line_comments: vec!["// ".into(), "/// ".into()],
 3434            ..LanguageConfig::default()
 3435        },
 3436        None,
 3437    ));
 3438    {
 3439        let mut cx = EditorTestContext::new(cx).await;
 3440        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3441        cx.set_state(indoc! {"
 3442        //ˇ
 3443    "});
 3444        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3445        cx.assert_editor_state(indoc! {"
 3446        //
 3447        // ˇ
 3448    "});
 3449
 3450        cx.set_state(indoc! {"
 3451        ///ˇ
 3452    "});
 3453        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3454        cx.assert_editor_state(indoc! {"
 3455        ///
 3456        /// ˇ
 3457    "});
 3458    }
 3459}
 3460
 3461#[gpui::test]
 3462async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3463    init_test(cx, |settings| {
 3464        settings.defaults.tab_size = NonZeroU32::new(4)
 3465    });
 3466
 3467    let language = Arc::new(
 3468        Language::new(
 3469            LanguageConfig {
 3470                documentation_comment: Some(language::BlockCommentConfig {
 3471                    start: "/**".into(),
 3472                    end: "*/".into(),
 3473                    prefix: "* ".into(),
 3474                    tab_size: 1,
 3475                }),
 3476
 3477                ..LanguageConfig::default()
 3478            },
 3479            Some(tree_sitter_rust::LANGUAGE.into()),
 3480        )
 3481        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3482        .unwrap(),
 3483    );
 3484
 3485    {
 3486        let mut cx = EditorTestContext::new(cx).await;
 3487        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3488        cx.set_state(indoc! {"
 3489        /**ˇ
 3490    "});
 3491
 3492        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3493        cx.assert_editor_state(indoc! {"
 3494        /**
 3495         * ˇ
 3496    "});
 3497        // Ensure that if cursor is before the comment start,
 3498        // we do not actually insert a comment prefix.
 3499        cx.set_state(indoc! {"
 3500        ˇ/**
 3501    "});
 3502        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3503        cx.assert_editor_state(indoc! {"
 3504
 3505        ˇ/**
 3506    "});
 3507        // Ensure that if cursor is between it doesn't add comment prefix.
 3508        cx.set_state(indoc! {"
 3509        /*ˇ*
 3510    "});
 3511        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3512        cx.assert_editor_state(indoc! {"
 3513        /*
 3514        ˇ*
 3515    "});
 3516        // Ensure that if suffix exists on same line after cursor it adds new line.
 3517        cx.set_state(indoc! {"
 3518        /**ˇ*/
 3519    "});
 3520        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3521        cx.assert_editor_state(indoc! {"
 3522        /**
 3523         * ˇ
 3524         */
 3525    "});
 3526        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3527        cx.set_state(indoc! {"
 3528        /**ˇ */
 3529    "});
 3530        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3531        cx.assert_editor_state(indoc! {"
 3532        /**
 3533         * ˇ
 3534         */
 3535    "});
 3536        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3537        cx.set_state(indoc! {"
 3538        /** ˇ*/
 3539    "});
 3540        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3541        cx.assert_editor_state(
 3542            indoc! {"
 3543        /**s
 3544         * ˇ
 3545         */
 3546    "}
 3547            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3548            .as_str(),
 3549        );
 3550        // Ensure that delimiter space is preserved when newline on already
 3551        // spaced delimiter.
 3552        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3553        cx.assert_editor_state(
 3554            indoc! {"
 3555        /**s
 3556         *s
 3557         * ˇ
 3558         */
 3559    "}
 3560            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3561            .as_str(),
 3562        );
 3563        // Ensure that delimiter space is preserved when space is not
 3564        // on existing delimiter.
 3565        cx.set_state(indoc! {"
 3566        /**
 3567 3568         */
 3569    "});
 3570        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3571        cx.assert_editor_state(indoc! {"
 3572        /**
 3573         *
 3574         * ˇ
 3575         */
 3576    "});
 3577        // Ensure that if suffix exists on same line after cursor it
 3578        // doesn't add extra new line if prefix is not on same line.
 3579        cx.set_state(indoc! {"
 3580        /**
 3581        ˇ*/
 3582    "});
 3583        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3584        cx.assert_editor_state(indoc! {"
 3585        /**
 3586
 3587        ˇ*/
 3588    "});
 3589        // Ensure that it detects suffix after existing prefix.
 3590        cx.set_state(indoc! {"
 3591        /**ˇ/
 3592    "});
 3593        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3594        cx.assert_editor_state(indoc! {"
 3595        /**
 3596        ˇ/
 3597    "});
 3598        // Ensure that if suffix exists on same line before
 3599        // cursor it does not add comment prefix.
 3600        cx.set_state(indoc! {"
 3601        /** */ˇ
 3602    "});
 3603        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3604        cx.assert_editor_state(indoc! {"
 3605        /** */
 3606        ˇ
 3607    "});
 3608        // Ensure that if suffix exists on same line before
 3609        // cursor it does not add comment prefix.
 3610        cx.set_state(indoc! {"
 3611        /**
 3612         *
 3613         */ˇ
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         *
 3619         */
 3620         ˇ
 3621    "});
 3622
 3623        // Ensure that inline comment followed by code
 3624        // doesn't add comment prefix on newline
 3625        cx.set_state(indoc! {"
 3626        /** */ textˇ
 3627    "});
 3628        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3629        cx.assert_editor_state(indoc! {"
 3630        /** */ text
 3631        ˇ
 3632    "});
 3633
 3634        // Ensure that text after comment end tag
 3635        // doesn't add comment prefix on newline
 3636        cx.set_state(indoc! {"
 3637        /**
 3638         *
 3639         */ˇtext
 3640    "});
 3641        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3642        cx.assert_editor_state(indoc! {"
 3643        /**
 3644         *
 3645         */
 3646         ˇtext
 3647    "});
 3648
 3649        // Ensure if not comment block it doesn't
 3650        // add comment prefix on newline
 3651        cx.set_state(indoc! {"
 3652        * textˇ
 3653    "});
 3654        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3655        cx.assert_editor_state(indoc! {"
 3656        * text
 3657        ˇ
 3658    "});
 3659    }
 3660    // Ensure that comment continuations can be disabled.
 3661    update_test_language_settings(cx, |settings| {
 3662        settings.defaults.extend_comment_on_newline = Some(false);
 3663    });
 3664    let mut cx = EditorTestContext::new(cx).await;
 3665    cx.set_state(indoc! {"
 3666        /**ˇ
 3667    "});
 3668    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3669    cx.assert_editor_state(indoc! {"
 3670        /**
 3671        ˇ
 3672    "});
 3673}
 3674
 3675#[gpui::test]
 3676async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3677    init_test(cx, |settings| {
 3678        settings.defaults.tab_size = NonZeroU32::new(4)
 3679    });
 3680
 3681    let lua_language = Arc::new(Language::new(
 3682        LanguageConfig {
 3683            line_comments: vec!["--".into()],
 3684            block_comment: Some(language::BlockCommentConfig {
 3685                start: "--[[".into(),
 3686                prefix: "".into(),
 3687                end: "]]".into(),
 3688                tab_size: 0,
 3689            }),
 3690            ..LanguageConfig::default()
 3691        },
 3692        None,
 3693    ));
 3694
 3695    let mut cx = EditorTestContext::new(cx).await;
 3696    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3697
 3698    // Line with line comment should extend
 3699    cx.set_state(indoc! {"
 3700        --ˇ
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        --
 3705        --ˇ
 3706    "});
 3707
 3708    // Line with block comment that matches line comment should not extend
 3709    cx.set_state(indoc! {"
 3710        --[[ˇ
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        --[[
 3715        ˇ
 3716    "});
 3717}
 3718
 3719#[gpui::test]
 3720fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3721    init_test(cx, |_| {});
 3722
 3723    let editor = cx.add_window(|window, cx| {
 3724        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3725        let mut editor = build_editor(buffer, window, cx);
 3726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3727            s.select_ranges([
 3728                MultiBufferOffset(3)..MultiBufferOffset(4),
 3729                MultiBufferOffset(11)..MultiBufferOffset(12),
 3730                MultiBufferOffset(19)..MultiBufferOffset(20),
 3731            ])
 3732        });
 3733        editor
 3734    });
 3735
 3736    _ = editor.update(cx, |editor, window, cx| {
 3737        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3738        editor.buffer.update(cx, |buffer, cx| {
 3739            buffer.edit(
 3740                [
 3741                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3742                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3743                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3744                ],
 3745                None,
 3746                cx,
 3747            );
 3748            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3749        });
 3750        assert_eq!(
 3751            editor.selections.ranges(&editor.display_snapshot(cx)),
 3752            &[
 3753                MultiBufferOffset(2)..MultiBufferOffset(2),
 3754                MultiBufferOffset(7)..MultiBufferOffset(7),
 3755                MultiBufferOffset(12)..MultiBufferOffset(12)
 3756            ],
 3757        );
 3758
 3759        editor.insert("Z", window, cx);
 3760        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3761
 3762        // The selections are moved after the inserted characters
 3763        assert_eq!(
 3764            editor.selections.ranges(&editor.display_snapshot(cx)),
 3765            &[
 3766                MultiBufferOffset(3)..MultiBufferOffset(3),
 3767                MultiBufferOffset(9)..MultiBufferOffset(9),
 3768                MultiBufferOffset(15)..MultiBufferOffset(15)
 3769            ],
 3770        );
 3771    });
 3772}
 3773
 3774#[gpui::test]
 3775async fn test_tab(cx: &mut TestAppContext) {
 3776    init_test(cx, |settings| {
 3777        settings.defaults.tab_size = NonZeroU32::new(3)
 3778    });
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781    cx.set_state(indoc! {"
 3782        ˇabˇc
 3783        ˇ🏀ˇ🏀ˇefg
 3784 3785    "});
 3786    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3787    cx.assert_editor_state(indoc! {"
 3788           ˇab ˇc
 3789           ˇ🏀  ˇ🏀  ˇefg
 3790        d  ˇ
 3791    "});
 3792
 3793    cx.set_state(indoc! {"
 3794        a
 3795        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        a
 3800           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let mut cx = EditorTestContext::new(cx).await;
 3809    let language = Arc::new(
 3810        Language::new(
 3811            LanguageConfig::default(),
 3812            Some(tree_sitter_rust::LANGUAGE.into()),
 3813        )
 3814        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3815        .unwrap(),
 3816    );
 3817    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3818
 3819    // test when all cursors are not at suggested indent
 3820    // then simply move to their suggested indent location
 3821    cx.set_state(indoc! {"
 3822        const a: B = (
 3823            c(
 3824        ˇ
 3825        ˇ    )
 3826        );
 3827    "});
 3828    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3829    cx.assert_editor_state(indoc! {"
 3830        const a: B = (
 3831            c(
 3832                ˇ
 3833            ˇ)
 3834        );
 3835    "});
 3836
 3837    // test cursor already at suggested indent not moving when
 3838    // other cursors are yet to reach their suggested indents
 3839    cx.set_state(indoc! {"
 3840        ˇ
 3841        const a: B = (
 3842            c(
 3843                d(
 3844        ˇ
 3845                )
 3846        ˇ
 3847        ˇ    )
 3848        );
 3849    "});
 3850    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3851    cx.assert_editor_state(indoc! {"
 3852        ˇ
 3853        const a: B = (
 3854            c(
 3855                d(
 3856                    ˇ
 3857                )
 3858                ˇ
 3859            ˇ)
 3860        );
 3861    "});
 3862    // test when all cursors are at suggested indent then tab is inserted
 3863    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3864    cx.assert_editor_state(indoc! {"
 3865            ˇ
 3866        const a: B = (
 3867            c(
 3868                d(
 3869                        ˇ
 3870                )
 3871                    ˇ
 3872                ˇ)
 3873        );
 3874    "});
 3875
 3876    // test when current indent is less than suggested indent,
 3877    // we adjust line to match suggested indent and move cursor to it
 3878    //
 3879    // when no other cursor is at word boundary, all of them should move
 3880    cx.set_state(indoc! {"
 3881        const a: B = (
 3882            c(
 3883                d(
 3884        ˇ
 3885        ˇ   )
 3886        ˇ   )
 3887        );
 3888    "});
 3889    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3890    cx.assert_editor_state(indoc! {"
 3891        const a: B = (
 3892            c(
 3893                d(
 3894                    ˇ
 3895                ˇ)
 3896            ˇ)
 3897        );
 3898    "});
 3899
 3900    // test when current indent is less than suggested indent,
 3901    // we adjust line to match suggested indent and move cursor to it
 3902    //
 3903    // when some other cursor is at word boundary, it should not move
 3904    cx.set_state(indoc! {"
 3905        const a: B = (
 3906            c(
 3907                d(
 3908        ˇ
 3909        ˇ   )
 3910           ˇ)
 3911        );
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        const a: B = (
 3916            c(
 3917                d(
 3918                    ˇ
 3919                ˇ)
 3920            ˇ)
 3921        );
 3922    "});
 3923
 3924    // test when current indent is more than suggested indent,
 3925    // we just move cursor to current indent instead of suggested indent
 3926    //
 3927    // when no other cursor is at word boundary, all of them should move
 3928    cx.set_state(indoc! {"
 3929        const a: B = (
 3930            c(
 3931                d(
 3932        ˇ
 3933        ˇ                )
 3934        ˇ   )
 3935        );
 3936    "});
 3937    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3938    cx.assert_editor_state(indoc! {"
 3939        const a: B = (
 3940            c(
 3941                d(
 3942                    ˇ
 3943                        ˇ)
 3944            ˇ)
 3945        );
 3946    "});
 3947    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3948    cx.assert_editor_state(indoc! {"
 3949        const a: B = (
 3950            c(
 3951                d(
 3952                        ˇ
 3953                            ˇ)
 3954                ˇ)
 3955        );
 3956    "});
 3957
 3958    // test when current indent is more than suggested indent,
 3959    // we just move cursor to current indent instead of suggested indent
 3960    //
 3961    // when some other cursor is at word boundary, it doesn't move
 3962    cx.set_state(indoc! {"
 3963        const a: B = (
 3964            c(
 3965                d(
 3966        ˇ
 3967        ˇ                )
 3968            ˇ)
 3969        );
 3970    "});
 3971    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3972    cx.assert_editor_state(indoc! {"
 3973        const a: B = (
 3974            c(
 3975                d(
 3976                    ˇ
 3977                        ˇ)
 3978            ˇ)
 3979        );
 3980    "});
 3981
 3982    // handle auto-indent when there are multiple cursors on the same line
 3983    cx.set_state(indoc! {"
 3984        const a: B = (
 3985            c(
 3986        ˇ    ˇ
 3987        ˇ    )
 3988        );
 3989    "});
 3990    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3991    cx.assert_editor_state(indoc! {"
 3992        const a: B = (
 3993            c(
 3994                ˇ
 3995            ˇ)
 3996        );
 3997    "});
 3998}
 3999
 4000#[gpui::test]
 4001async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4002    init_test(cx, |settings| {
 4003        settings.defaults.tab_size = NonZeroU32::new(3)
 4004    });
 4005
 4006    let mut cx = EditorTestContext::new(cx).await;
 4007    cx.set_state(indoc! {"
 4008         ˇ
 4009        \t ˇ
 4010        \t  ˇ
 4011        \t   ˇ
 4012         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4013    "});
 4014
 4015    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4016    cx.assert_editor_state(indoc! {"
 4017           ˇ
 4018        \t   ˇ
 4019        \t   ˇ
 4020        \t      ˇ
 4021         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4022    "});
 4023}
 4024
 4025#[gpui::test]
 4026async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4027    init_test(cx, |settings| {
 4028        settings.defaults.tab_size = NonZeroU32::new(4)
 4029    });
 4030
 4031    let language = Arc::new(
 4032        Language::new(
 4033            LanguageConfig::default(),
 4034            Some(tree_sitter_rust::LANGUAGE.into()),
 4035        )
 4036        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4037        .unwrap(),
 4038    );
 4039
 4040    let mut cx = EditorTestContext::new(cx).await;
 4041    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4042    cx.set_state(indoc! {"
 4043        fn a() {
 4044            if b {
 4045        \t ˇc
 4046            }
 4047        }
 4048    "});
 4049
 4050    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4051    cx.assert_editor_state(indoc! {"
 4052        fn a() {
 4053            if b {
 4054                ˇc
 4055            }
 4056        }
 4057    "});
 4058}
 4059
 4060#[gpui::test]
 4061async fn test_indent_outdent(cx: &mut TestAppContext) {
 4062    init_test(cx, |settings| {
 4063        settings.defaults.tab_size = NonZeroU32::new(4);
 4064    });
 4065
 4066    let mut cx = EditorTestContext::new(cx).await;
 4067
 4068    cx.set_state(indoc! {"
 4069          «oneˇ» «twoˇ»
 4070        three
 4071         four
 4072    "});
 4073    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4074    cx.assert_editor_state(indoc! {"
 4075            «oneˇ» «twoˇ»
 4076        three
 4077         four
 4078    "});
 4079
 4080    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4081    cx.assert_editor_state(indoc! {"
 4082        «oneˇ» «twoˇ»
 4083        three
 4084         four
 4085    "});
 4086
 4087    // select across line ending
 4088    cx.set_state(indoc! {"
 4089        one two
 4090        t«hree
 4091        ˇ» four
 4092    "});
 4093    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4094    cx.assert_editor_state(indoc! {"
 4095        one two
 4096            t«hree
 4097        ˇ» four
 4098    "});
 4099
 4100    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4101    cx.assert_editor_state(indoc! {"
 4102        one two
 4103        t«hree
 4104        ˇ» four
 4105    "});
 4106
 4107    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4108    cx.set_state(indoc! {"
 4109        one two
 4110        ˇthree
 4111            four
 4112    "});
 4113    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4114    cx.assert_editor_state(indoc! {"
 4115        one two
 4116            ˇthree
 4117            four
 4118    "});
 4119
 4120    cx.set_state(indoc! {"
 4121        one two
 4122        ˇ    three
 4123            four
 4124    "});
 4125    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4126    cx.assert_editor_state(indoc! {"
 4127        one two
 4128        ˇthree
 4129            four
 4130    "});
 4131}
 4132
 4133#[gpui::test]
 4134async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4135    // This is a regression test for issue #33761
 4136    init_test(cx, |_| {});
 4137
 4138    let mut cx = EditorTestContext::new(cx).await;
 4139    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4141
 4142    cx.set_state(
 4143        r#"ˇ#     ingress:
 4144ˇ#         api:
 4145ˇ#             enabled: false
 4146ˇ#             pathType: Prefix
 4147ˇ#           console:
 4148ˇ#               enabled: false
 4149ˇ#               pathType: Prefix
 4150"#,
 4151    );
 4152
 4153    // Press tab to indent all lines
 4154    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4155
 4156    cx.assert_editor_state(
 4157        r#"    ˇ#     ingress:
 4158    ˇ#         api:
 4159    ˇ#             enabled: false
 4160    ˇ#             pathType: Prefix
 4161    ˇ#           console:
 4162    ˇ#               enabled: false
 4163    ˇ#               pathType: Prefix
 4164"#,
 4165    );
 4166}
 4167
 4168#[gpui::test]
 4169async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4170    // This is a test to make sure our fix for issue #33761 didn't break anything
 4171    init_test(cx, |_| {});
 4172
 4173    let mut cx = EditorTestContext::new(cx).await;
 4174    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4175    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4176
 4177    cx.set_state(
 4178        r#"ˇingress:
 4179ˇ  api:
 4180ˇ    enabled: false
 4181ˇ    pathType: Prefix
 4182"#,
 4183    );
 4184
 4185    // Press tab to indent all lines
 4186    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4187
 4188    cx.assert_editor_state(
 4189        r#"ˇingress:
 4190    ˇapi:
 4191        ˇenabled: false
 4192        ˇpathType: Prefix
 4193"#,
 4194    );
 4195}
 4196
 4197#[gpui::test]
 4198async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4199    init_test(cx, |settings| {
 4200        settings.defaults.hard_tabs = Some(true);
 4201    });
 4202
 4203    let mut cx = EditorTestContext::new(cx).await;
 4204
 4205    // select two ranges on one line
 4206    cx.set_state(indoc! {"
 4207        «oneˇ» «twoˇ»
 4208        three
 4209        four
 4210    "});
 4211    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4212    cx.assert_editor_state(indoc! {"
 4213        \t«oneˇ» «twoˇ»
 4214        three
 4215        four
 4216    "});
 4217    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4218    cx.assert_editor_state(indoc! {"
 4219        \t\t«oneˇ» «twoˇ»
 4220        three
 4221        four
 4222    "});
 4223    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4224    cx.assert_editor_state(indoc! {"
 4225        \t«oneˇ» «twoˇ»
 4226        three
 4227        four
 4228    "});
 4229    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4230    cx.assert_editor_state(indoc! {"
 4231        «oneˇ» «twoˇ»
 4232        three
 4233        four
 4234    "});
 4235
 4236    // select across a line ending
 4237    cx.set_state(indoc! {"
 4238        one two
 4239        t«hree
 4240        ˇ»four
 4241    "});
 4242    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4243    cx.assert_editor_state(indoc! {"
 4244        one two
 4245        \tt«hree
 4246        ˇ»four
 4247    "});
 4248    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4249    cx.assert_editor_state(indoc! {"
 4250        one two
 4251        \t\tt«hree
 4252        ˇ»four
 4253    "});
 4254    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4255    cx.assert_editor_state(indoc! {"
 4256        one two
 4257        \tt«hree
 4258        ˇ»four
 4259    "});
 4260    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4261    cx.assert_editor_state(indoc! {"
 4262        one two
 4263        t«hree
 4264        ˇ»four
 4265    "});
 4266
 4267    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4268    cx.set_state(indoc! {"
 4269        one two
 4270        ˇthree
 4271        four
 4272    "});
 4273    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4274    cx.assert_editor_state(indoc! {"
 4275        one two
 4276        ˇthree
 4277        four
 4278    "});
 4279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4280    cx.assert_editor_state(indoc! {"
 4281        one two
 4282        \tˇthree
 4283        four
 4284    "});
 4285    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4286    cx.assert_editor_state(indoc! {"
 4287        one two
 4288        ˇthree
 4289        four
 4290    "});
 4291}
 4292
 4293#[gpui::test]
 4294fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4295    init_test(cx, |settings| {
 4296        settings.languages.0.extend([
 4297            (
 4298                "TOML".into(),
 4299                LanguageSettingsContent {
 4300                    tab_size: NonZeroU32::new(2),
 4301                    ..Default::default()
 4302                },
 4303            ),
 4304            (
 4305                "Rust".into(),
 4306                LanguageSettingsContent {
 4307                    tab_size: NonZeroU32::new(4),
 4308                    ..Default::default()
 4309                },
 4310            ),
 4311        ]);
 4312    });
 4313
 4314    let toml_language = Arc::new(Language::new(
 4315        LanguageConfig {
 4316            name: "TOML".into(),
 4317            ..Default::default()
 4318        },
 4319        None,
 4320    ));
 4321    let rust_language = Arc::new(Language::new(
 4322        LanguageConfig {
 4323            name: "Rust".into(),
 4324            ..Default::default()
 4325        },
 4326        None,
 4327    ));
 4328
 4329    let toml_buffer =
 4330        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4331    let rust_buffer =
 4332        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4333    let multibuffer = cx.new(|cx| {
 4334        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4335        multibuffer.push_excerpts(
 4336            toml_buffer.clone(),
 4337            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4338            cx,
 4339        );
 4340        multibuffer.push_excerpts(
 4341            rust_buffer.clone(),
 4342            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4343            cx,
 4344        );
 4345        multibuffer
 4346    });
 4347
 4348    cx.add_window(|window, cx| {
 4349        let mut editor = build_editor(multibuffer, window, cx);
 4350
 4351        assert_eq!(
 4352            editor.text(cx),
 4353            indoc! {"
 4354                a = 1
 4355                b = 2
 4356
 4357                const c: usize = 3;
 4358            "}
 4359        );
 4360
 4361        select_ranges(
 4362            &mut editor,
 4363            indoc! {"
 4364                «aˇ» = 1
 4365                b = 2
 4366
 4367                «const c:ˇ» usize = 3;
 4368            "},
 4369            window,
 4370            cx,
 4371        );
 4372
 4373        editor.tab(&Tab, window, cx);
 4374        assert_text_with_selections(
 4375            &mut editor,
 4376            indoc! {"
 4377                  «aˇ» = 1
 4378                b = 2
 4379
 4380                    «const c:ˇ» usize = 3;
 4381            "},
 4382            cx,
 4383        );
 4384        editor.backtab(&Backtab, window, cx);
 4385        assert_text_with_selections(
 4386            &mut editor,
 4387            indoc! {"
 4388                «aˇ» = 1
 4389                b = 2
 4390
 4391                «const c:ˇ» usize = 3;
 4392            "},
 4393            cx,
 4394        );
 4395
 4396        editor
 4397    });
 4398}
 4399
 4400#[gpui::test]
 4401async fn test_backspace(cx: &mut TestAppContext) {
 4402    init_test(cx, |_| {});
 4403
 4404    let mut cx = EditorTestContext::new(cx).await;
 4405
 4406    // Basic backspace
 4407    cx.set_state(indoc! {"
 4408        onˇe two three
 4409        fou«rˇ» five six
 4410        seven «ˇeight nine
 4411        »ten
 4412    "});
 4413    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4414    cx.assert_editor_state(indoc! {"
 4415        oˇe two three
 4416        fouˇ five six
 4417        seven ˇten
 4418    "});
 4419
 4420    // Test backspace inside and around indents
 4421    cx.set_state(indoc! {"
 4422        zero
 4423            ˇone
 4424                ˇtwo
 4425            ˇ ˇ ˇ  three
 4426        ˇ  ˇ  four
 4427    "});
 4428    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4429    cx.assert_editor_state(indoc! {"
 4430        zero
 4431        ˇone
 4432            ˇtwo
 4433        ˇ  threeˇ  four
 4434    "});
 4435}
 4436
 4437#[gpui::test]
 4438async fn test_delete(cx: &mut TestAppContext) {
 4439    init_test(cx, |_| {});
 4440
 4441    let mut cx = EditorTestContext::new(cx).await;
 4442    cx.set_state(indoc! {"
 4443        onˇe two three
 4444        fou«rˇ» five six
 4445        seven «ˇeight nine
 4446        »ten
 4447    "});
 4448    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4449    cx.assert_editor_state(indoc! {"
 4450        onˇ two three
 4451        fouˇ five six
 4452        seven ˇten
 4453    "});
 4454}
 4455
 4456#[gpui::test]
 4457fn test_delete_line(cx: &mut TestAppContext) {
 4458    init_test(cx, |_| {});
 4459
 4460    let editor = cx.add_window(|window, cx| {
 4461        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4462        build_editor(buffer, window, cx)
 4463    });
 4464    _ = editor.update(cx, |editor, window, cx| {
 4465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4466            s.select_display_ranges([
 4467                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4468                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4469                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4470            ])
 4471        });
 4472        editor.delete_line(&DeleteLine, window, cx);
 4473        assert_eq!(editor.display_text(cx), "ghi");
 4474        assert_eq!(
 4475            display_ranges(editor, cx),
 4476            vec![
 4477                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4478                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4479            ]
 4480        );
 4481    });
 4482
 4483    let editor = cx.add_window(|window, cx| {
 4484        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4485        build_editor(buffer, window, cx)
 4486    });
 4487    _ = editor.update(cx, |editor, window, cx| {
 4488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4489            s.select_display_ranges([
 4490                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4491            ])
 4492        });
 4493        editor.delete_line(&DeleteLine, window, cx);
 4494        assert_eq!(editor.display_text(cx), "ghi\n");
 4495        assert_eq!(
 4496            display_ranges(editor, cx),
 4497            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4498        );
 4499    });
 4500
 4501    let editor = cx.add_window(|window, cx| {
 4502        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4503        build_editor(buffer, window, cx)
 4504    });
 4505    _ = editor.update(cx, |editor, window, cx| {
 4506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4507            s.select_display_ranges([
 4508                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4509            ])
 4510        });
 4511        editor.delete_line(&DeleteLine, window, cx);
 4512        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4513        assert_eq!(
 4514            display_ranges(editor, cx),
 4515            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4516        );
 4517    });
 4518}
 4519
 4520#[gpui::test]
 4521fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4522    init_test(cx, |_| {});
 4523
 4524    cx.add_window(|window, cx| {
 4525        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4526        let mut editor = build_editor(buffer.clone(), window, cx);
 4527        let buffer = buffer.read(cx).as_singleton().unwrap();
 4528
 4529        assert_eq!(
 4530            editor
 4531                .selections
 4532                .ranges::<Point>(&editor.display_snapshot(cx)),
 4533            &[Point::new(0, 0)..Point::new(0, 0)]
 4534        );
 4535
 4536        // When on single line, replace newline at end by space
 4537        editor.join_lines(&JoinLines, window, cx);
 4538        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4539        assert_eq!(
 4540            editor
 4541                .selections
 4542                .ranges::<Point>(&editor.display_snapshot(cx)),
 4543            &[Point::new(0, 3)..Point::new(0, 3)]
 4544        );
 4545
 4546        // When multiple lines are selected, remove newlines that are spanned by the selection
 4547        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4548            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4549        });
 4550        editor.join_lines(&JoinLines, window, cx);
 4551        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4552        assert_eq!(
 4553            editor
 4554                .selections
 4555                .ranges::<Point>(&editor.display_snapshot(cx)),
 4556            &[Point::new(0, 11)..Point::new(0, 11)]
 4557        );
 4558
 4559        // Undo should be transactional
 4560        editor.undo(&Undo, window, cx);
 4561        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4562        assert_eq!(
 4563            editor
 4564                .selections
 4565                .ranges::<Point>(&editor.display_snapshot(cx)),
 4566            &[Point::new(0, 5)..Point::new(2, 2)]
 4567        );
 4568
 4569        // When joining an empty line don't insert a space
 4570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4571            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4572        });
 4573        editor.join_lines(&JoinLines, window, cx);
 4574        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4575        assert_eq!(
 4576            editor
 4577                .selections
 4578                .ranges::<Point>(&editor.display_snapshot(cx)),
 4579            [Point::new(2, 3)..Point::new(2, 3)]
 4580        );
 4581
 4582        // We can remove trailing newlines
 4583        editor.join_lines(&JoinLines, window, cx);
 4584        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4585        assert_eq!(
 4586            editor
 4587                .selections
 4588                .ranges::<Point>(&editor.display_snapshot(cx)),
 4589            [Point::new(2, 3)..Point::new(2, 3)]
 4590        );
 4591
 4592        // We don't blow up on the last line
 4593        editor.join_lines(&JoinLines, window, cx);
 4594        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4595        assert_eq!(
 4596            editor
 4597                .selections
 4598                .ranges::<Point>(&editor.display_snapshot(cx)),
 4599            [Point::new(2, 3)..Point::new(2, 3)]
 4600        );
 4601
 4602        // reset to test indentation
 4603        editor.buffer.update(cx, |buffer, cx| {
 4604            buffer.edit(
 4605                [
 4606                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4607                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4608                ],
 4609                None,
 4610                cx,
 4611            )
 4612        });
 4613
 4614        // We remove any leading spaces
 4615        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4617            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4618        });
 4619        editor.join_lines(&JoinLines, window, cx);
 4620        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4621
 4622        // We don't insert a space for a line containing only spaces
 4623        editor.join_lines(&JoinLines, window, cx);
 4624        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4625
 4626        // We ignore any leading tabs
 4627        editor.join_lines(&JoinLines, window, cx);
 4628        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4629
 4630        editor
 4631    });
 4632}
 4633
 4634#[gpui::test]
 4635fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4636    init_test(cx, |_| {});
 4637
 4638    cx.add_window(|window, cx| {
 4639        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4640        let mut editor = build_editor(buffer.clone(), window, cx);
 4641        let buffer = buffer.read(cx).as_singleton().unwrap();
 4642
 4643        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4644            s.select_ranges([
 4645                Point::new(0, 2)..Point::new(1, 1),
 4646                Point::new(1, 2)..Point::new(1, 2),
 4647                Point::new(3, 1)..Point::new(3, 2),
 4648            ])
 4649        });
 4650
 4651        editor.join_lines(&JoinLines, window, cx);
 4652        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4653
 4654        assert_eq!(
 4655            editor
 4656                .selections
 4657                .ranges::<Point>(&editor.display_snapshot(cx)),
 4658            [
 4659                Point::new(0, 7)..Point::new(0, 7),
 4660                Point::new(1, 3)..Point::new(1, 3)
 4661            ]
 4662        );
 4663        editor
 4664    });
 4665}
 4666
 4667#[gpui::test]
 4668async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4669    init_test(cx, |_| {});
 4670
 4671    let mut cx = EditorTestContext::new(cx).await;
 4672
 4673    let diff_base = r#"
 4674        Line 0
 4675        Line 1
 4676        Line 2
 4677        Line 3
 4678        "#
 4679    .unindent();
 4680
 4681    cx.set_state(
 4682        &r#"
 4683        ˇLine 0
 4684        Line 1
 4685        Line 2
 4686        Line 3
 4687        "#
 4688        .unindent(),
 4689    );
 4690
 4691    cx.set_head_text(&diff_base);
 4692    executor.run_until_parked();
 4693
 4694    // Join lines
 4695    cx.update_editor(|editor, window, cx| {
 4696        editor.join_lines(&JoinLines, window, cx);
 4697    });
 4698    executor.run_until_parked();
 4699
 4700    cx.assert_editor_state(
 4701        &r#"
 4702        Line 0ˇ Line 1
 4703        Line 2
 4704        Line 3
 4705        "#
 4706        .unindent(),
 4707    );
 4708    // Join again
 4709    cx.update_editor(|editor, window, cx| {
 4710        editor.join_lines(&JoinLines, window, cx);
 4711    });
 4712    executor.run_until_parked();
 4713
 4714    cx.assert_editor_state(
 4715        &r#"
 4716        Line 0 Line 1ˇ Line 2
 4717        Line 3
 4718        "#
 4719        .unindent(),
 4720    );
 4721}
 4722
 4723#[gpui::test]
 4724async fn test_custom_newlines_cause_no_false_positive_diffs(
 4725    executor: BackgroundExecutor,
 4726    cx: &mut TestAppContext,
 4727) {
 4728    init_test(cx, |_| {});
 4729    let mut cx = EditorTestContext::new(cx).await;
 4730    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4731    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4732    executor.run_until_parked();
 4733
 4734    cx.update_editor(|editor, window, cx| {
 4735        let snapshot = editor.snapshot(window, cx);
 4736        assert_eq!(
 4737            snapshot
 4738                .buffer_snapshot()
 4739                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4740                .collect::<Vec<_>>(),
 4741            Vec::new(),
 4742            "Should not have any diffs for files with custom newlines"
 4743        );
 4744    });
 4745}
 4746
 4747#[gpui::test]
 4748async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4749    init_test(cx, |_| {});
 4750
 4751    let mut cx = EditorTestContext::new(cx).await;
 4752
 4753    // Test sort_lines_case_insensitive()
 4754    cx.set_state(indoc! {"
 4755        «z
 4756        y
 4757        x
 4758        Z
 4759        Y
 4760        Xˇ»
 4761    "});
 4762    cx.update_editor(|e, window, cx| {
 4763        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4764    });
 4765    cx.assert_editor_state(indoc! {"
 4766        «x
 4767        X
 4768        y
 4769        Y
 4770        z
 4771        Zˇ»
 4772    "});
 4773
 4774    // Test sort_lines_by_length()
 4775    //
 4776    // Demonstrates:
 4777    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4778    // - sort is stable
 4779    cx.set_state(indoc! {"
 4780        «123
 4781        æ
 4782        12
 4783 4784        1
 4785        æˇ»
 4786    "});
 4787    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4788    cx.assert_editor_state(indoc! {"
 4789        «æ
 4790 4791        1
 4792        æ
 4793        12
 4794        123ˇ»
 4795    "});
 4796
 4797    // Test reverse_lines()
 4798    cx.set_state(indoc! {"
 4799        «5
 4800        4
 4801        3
 4802        2
 4803        1ˇ»
 4804    "});
 4805    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4806    cx.assert_editor_state(indoc! {"
 4807        «1
 4808        2
 4809        3
 4810        4
 4811        5ˇ»
 4812    "});
 4813
 4814    // Skip testing shuffle_line()
 4815
 4816    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4817    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4818
 4819    // Don't manipulate when cursor is on single line, but expand the selection
 4820    cx.set_state(indoc! {"
 4821        ddˇdd
 4822        ccc
 4823        bb
 4824        a
 4825    "});
 4826    cx.update_editor(|e, window, cx| {
 4827        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4828    });
 4829    cx.assert_editor_state(indoc! {"
 4830        «ddddˇ»
 4831        ccc
 4832        bb
 4833        a
 4834    "});
 4835
 4836    // Basic manipulate case
 4837    // Start selection moves to column 0
 4838    // End of selection shrinks to fit shorter line
 4839    cx.set_state(indoc! {"
 4840        dd«d
 4841        ccc
 4842        bb
 4843        aaaaaˇ»
 4844    "});
 4845    cx.update_editor(|e, window, cx| {
 4846        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4847    });
 4848    cx.assert_editor_state(indoc! {"
 4849        «aaaaa
 4850        bb
 4851        ccc
 4852        dddˇ»
 4853    "});
 4854
 4855    // Manipulate case with newlines
 4856    cx.set_state(indoc! {"
 4857        dd«d
 4858        ccc
 4859
 4860        bb
 4861        aaaaa
 4862
 4863        ˇ»
 4864    "});
 4865    cx.update_editor(|e, window, cx| {
 4866        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4867    });
 4868    cx.assert_editor_state(indoc! {"
 4869        «
 4870
 4871        aaaaa
 4872        bb
 4873        ccc
 4874        dddˇ»
 4875
 4876    "});
 4877
 4878    // Adding new line
 4879    cx.set_state(indoc! {"
 4880        aa«a
 4881        bbˇ»b
 4882    "});
 4883    cx.update_editor(|e, window, cx| {
 4884        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4885    });
 4886    cx.assert_editor_state(indoc! {"
 4887        «aaa
 4888        bbb
 4889        added_lineˇ»
 4890    "});
 4891
 4892    // Removing line
 4893    cx.set_state(indoc! {"
 4894        aa«a
 4895        bbbˇ»
 4896    "});
 4897    cx.update_editor(|e, window, cx| {
 4898        e.manipulate_immutable_lines(window, cx, |lines| {
 4899            lines.pop();
 4900        })
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «aaaˇ»
 4904    "});
 4905
 4906    // Removing all lines
 4907    cx.set_state(indoc! {"
 4908        aa«a
 4909        bbbˇ»
 4910    "});
 4911    cx.update_editor(|e, window, cx| {
 4912        e.manipulate_immutable_lines(window, cx, |lines| {
 4913            lines.drain(..);
 4914        })
 4915    });
 4916    cx.assert_editor_state(indoc! {"
 4917        ˇ
 4918    "});
 4919}
 4920
 4921#[gpui::test]
 4922async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4923    init_test(cx, |_| {});
 4924
 4925    let mut cx = EditorTestContext::new(cx).await;
 4926
 4927    // Consider continuous selection as single selection
 4928    cx.set_state(indoc! {"
 4929        Aaa«aa
 4930        cˇ»c«c
 4931        bb
 4932        aaaˇ»aa
 4933    "});
 4934    cx.update_editor(|e, window, cx| {
 4935        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4936    });
 4937    cx.assert_editor_state(indoc! {"
 4938        «Aaaaa
 4939        ccc
 4940        bb
 4941        aaaaaˇ»
 4942    "});
 4943
 4944    cx.set_state(indoc! {"
 4945        Aaa«aa
 4946        cˇ»c«c
 4947        bb
 4948        aaaˇ»aa
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «Aaaaa
 4955        ccc
 4956        bbˇ»
 4957    "});
 4958
 4959    // Consider non continuous selection as distinct dedup operations
 4960    cx.set_state(indoc! {"
 4961        «aaaaa
 4962        bb
 4963        aaaaa
 4964        aaaaaˇ»
 4965
 4966        aaa«aaˇ»
 4967    "});
 4968    cx.update_editor(|e, window, cx| {
 4969        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4970    });
 4971    cx.assert_editor_state(indoc! {"
 4972        «aaaaa
 4973        bbˇ»
 4974
 4975        «aaaaaˇ»
 4976    "});
 4977}
 4978
 4979#[gpui::test]
 4980async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4981    init_test(cx, |_| {});
 4982
 4983    let mut cx = EditorTestContext::new(cx).await;
 4984
 4985    cx.set_state(indoc! {"
 4986        «Aaa
 4987        aAa
 4988        Aaaˇ»
 4989    "});
 4990    cx.update_editor(|e, window, cx| {
 4991        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4992    });
 4993    cx.assert_editor_state(indoc! {"
 4994        «Aaa
 4995        aAaˇ»
 4996    "});
 4997
 4998    cx.set_state(indoc! {"
 4999        «Aaa
 5000        aAa
 5001        aaAˇ»
 5002    "});
 5003    cx.update_editor(|e, window, cx| {
 5004        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5005    });
 5006    cx.assert_editor_state(indoc! {"
 5007        «Aaaˇ»
 5008    "});
 5009}
 5010
 5011#[gpui::test]
 5012async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5013    init_test(cx, |_| {});
 5014
 5015    let mut cx = EditorTestContext::new(cx).await;
 5016
 5017    let js_language = Arc::new(Language::new(
 5018        LanguageConfig {
 5019            name: "JavaScript".into(),
 5020            wrap_characters: Some(language::WrapCharactersConfig {
 5021                start_prefix: "<".into(),
 5022                start_suffix: ">".into(),
 5023                end_prefix: "</".into(),
 5024                end_suffix: ">".into(),
 5025            }),
 5026            ..LanguageConfig::default()
 5027        },
 5028        None,
 5029    ));
 5030
 5031    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5032
 5033    cx.set_state(indoc! {"
 5034        «testˇ»
 5035    "});
 5036    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5037    cx.assert_editor_state(indoc! {"
 5038        <«ˇ»>test</«ˇ»>
 5039    "});
 5040
 5041    cx.set_state(indoc! {"
 5042        «test
 5043         testˇ»
 5044    "});
 5045    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5046    cx.assert_editor_state(indoc! {"
 5047        <«ˇ»>test
 5048         test</«ˇ»>
 5049    "});
 5050
 5051    cx.set_state(indoc! {"
 5052        teˇst
 5053    "});
 5054    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5055    cx.assert_editor_state(indoc! {"
 5056        te<«ˇ»></«ˇ»>st
 5057    "});
 5058}
 5059
 5060#[gpui::test]
 5061async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5062    init_test(cx, |_| {});
 5063
 5064    let mut cx = EditorTestContext::new(cx).await;
 5065
 5066    let js_language = Arc::new(Language::new(
 5067        LanguageConfig {
 5068            name: "JavaScript".into(),
 5069            wrap_characters: Some(language::WrapCharactersConfig {
 5070                start_prefix: "<".into(),
 5071                start_suffix: ">".into(),
 5072                end_prefix: "</".into(),
 5073                end_suffix: ">".into(),
 5074            }),
 5075            ..LanguageConfig::default()
 5076        },
 5077        None,
 5078    ));
 5079
 5080    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5081
 5082    cx.set_state(indoc! {"
 5083        «testˇ»
 5084        «testˇ» «testˇ»
 5085        «testˇ»
 5086    "});
 5087    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5088    cx.assert_editor_state(indoc! {"
 5089        <«ˇ»>test</«ˇ»>
 5090        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5091        <«ˇ»>test</«ˇ»>
 5092    "});
 5093
 5094    cx.set_state(indoc! {"
 5095        «test
 5096         testˇ»
 5097        «test
 5098         testˇ»
 5099    "});
 5100    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5101    cx.assert_editor_state(indoc! {"
 5102        <«ˇ»>test
 5103         test</«ˇ»>
 5104        <«ˇ»>test
 5105         test</«ˇ»>
 5106    "});
 5107}
 5108
 5109#[gpui::test]
 5110async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5111    init_test(cx, |_| {});
 5112
 5113    let mut cx = EditorTestContext::new(cx).await;
 5114
 5115    let plaintext_language = Arc::new(Language::new(
 5116        LanguageConfig {
 5117            name: "Plain Text".into(),
 5118            ..LanguageConfig::default()
 5119        },
 5120        None,
 5121    ));
 5122
 5123    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5124
 5125    cx.set_state(indoc! {"
 5126        «testˇ»
 5127    "});
 5128    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5129    cx.assert_editor_state(indoc! {"
 5130      «testˇ»
 5131    "});
 5132}
 5133
 5134#[gpui::test]
 5135async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5136    init_test(cx, |_| {});
 5137
 5138    let mut cx = EditorTestContext::new(cx).await;
 5139
 5140    // Manipulate with multiple selections on a single line
 5141    cx.set_state(indoc! {"
 5142        dd«dd
 5143        cˇ»c«c
 5144        bb
 5145        aaaˇ»aa
 5146    "});
 5147    cx.update_editor(|e, window, cx| {
 5148        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5149    });
 5150    cx.assert_editor_state(indoc! {"
 5151        «aaaaa
 5152        bb
 5153        ccc
 5154        ddddˇ»
 5155    "});
 5156
 5157    // Manipulate with multiple disjoin selections
 5158    cx.set_state(indoc! {"
 5159 5160        4
 5161        3
 5162        2
 5163        1ˇ»
 5164
 5165        dd«dd
 5166        ccc
 5167        bb
 5168        aaaˇ»aa
 5169    "});
 5170    cx.update_editor(|e, window, cx| {
 5171        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5172    });
 5173    cx.assert_editor_state(indoc! {"
 5174        «1
 5175        2
 5176        3
 5177        4
 5178        5ˇ»
 5179
 5180        «aaaaa
 5181        bb
 5182        ccc
 5183        ddddˇ»
 5184    "});
 5185
 5186    // Adding lines on each selection
 5187    cx.set_state(indoc! {"
 5188 5189        1ˇ»
 5190
 5191        bb«bb
 5192        aaaˇ»aa
 5193    "});
 5194    cx.update_editor(|e, window, cx| {
 5195        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5196    });
 5197    cx.assert_editor_state(indoc! {"
 5198        «2
 5199        1
 5200        added lineˇ»
 5201
 5202        «bbbb
 5203        aaaaa
 5204        added lineˇ»
 5205    "});
 5206
 5207    // Removing lines on each selection
 5208    cx.set_state(indoc! {"
 5209 5210        1ˇ»
 5211
 5212        bb«bb
 5213        aaaˇ»aa
 5214    "});
 5215    cx.update_editor(|e, window, cx| {
 5216        e.manipulate_immutable_lines(window, cx, |lines| {
 5217            lines.pop();
 5218        })
 5219    });
 5220    cx.assert_editor_state(indoc! {"
 5221        «2ˇ»
 5222
 5223        «bbbbˇ»
 5224    "});
 5225}
 5226
 5227#[gpui::test]
 5228async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5229    init_test(cx, |settings| {
 5230        settings.defaults.tab_size = NonZeroU32::new(3)
 5231    });
 5232
 5233    let mut cx = EditorTestContext::new(cx).await;
 5234
 5235    // MULTI SELECTION
 5236    // Ln.1 "«" tests empty lines
 5237    // Ln.9 tests just leading whitespace
 5238    cx.set_state(indoc! {"
 5239        «
 5240        abc                 // No indentationˇ»
 5241        «\tabc              // 1 tabˇ»
 5242        \t\tabc «      ˇ»   // 2 tabs
 5243        \t ab«c             // Tab followed by space
 5244         \tabc              // Space followed by tab (3 spaces should be the result)
 5245        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5246           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5247        \t
 5248        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5249    "});
 5250    cx.update_editor(|e, window, cx| {
 5251        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5252    });
 5253    cx.assert_editor_state(
 5254        indoc! {"
 5255            «
 5256            abc                 // No indentation
 5257               abc              // 1 tab
 5258                  abc          // 2 tabs
 5259                abc             // Tab followed by space
 5260               abc              // Space followed by tab (3 spaces should be the result)
 5261                           abc   // Mixed indentation (tab conversion depends on the column)
 5262               abc         // Already space indented
 5263               ·
 5264               abc\tdef          // Only the leading tab is manipulatedˇ»
 5265        "}
 5266        .replace("·", "")
 5267        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5268    );
 5269
 5270    // Test on just a few lines, the others should remain unchanged
 5271    // Only lines (3, 5, 10, 11) should change
 5272    cx.set_state(
 5273        indoc! {"
 5274            ·
 5275            abc                 // No indentation
 5276            \tabcˇ               // 1 tab
 5277            \t\tabc             // 2 tabs
 5278            \t abcˇ              // Tab followed by space
 5279             \tabc              // Space followed by tab (3 spaces should be the result)
 5280            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5281               abc              // Already space indented
 5282            «\t
 5283            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5284        "}
 5285        .replace("·", "")
 5286        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5287    );
 5288    cx.update_editor(|e, window, cx| {
 5289        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5290    });
 5291    cx.assert_editor_state(
 5292        indoc! {"
 5293            ·
 5294            abc                 // No indentation
 5295            «   abc               // 1 tabˇ»
 5296            \t\tabc             // 2 tabs
 5297            «    abc              // Tab followed by spaceˇ»
 5298             \tabc              // Space followed by tab (3 spaces should be the result)
 5299            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5300               abc              // Already space indented
 5301            «   ·
 5302               abc\tdef          // Only the leading tab is manipulatedˇ»
 5303        "}
 5304        .replace("·", "")
 5305        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5306    );
 5307
 5308    // SINGLE SELECTION
 5309    // Ln.1 "«" tests empty lines
 5310    // Ln.9 tests just leading whitespace
 5311    cx.set_state(indoc! {"
 5312        «
 5313        abc                 // No indentation
 5314        \tabc               // 1 tab
 5315        \t\tabc             // 2 tabs
 5316        \t abc              // Tab followed by space
 5317         \tabc              // Space followed by tab (3 spaces should be the result)
 5318        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5319           abc              // Already space indented
 5320        \t
 5321        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5322    "});
 5323    cx.update_editor(|e, window, cx| {
 5324        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5325    });
 5326    cx.assert_editor_state(
 5327        indoc! {"
 5328            «
 5329            abc                 // No indentation
 5330               abc               // 1 tab
 5331                  abc             // 2 tabs
 5332                abc              // Tab followed by space
 5333               abc              // Space followed by tab (3 spaces should be the result)
 5334                           abc   // Mixed indentation (tab conversion depends on the column)
 5335               abc              // Already space indented
 5336               ·
 5337               abc\tdef          // Only the leading tab is manipulatedˇ»
 5338        "}
 5339        .replace("·", "")
 5340        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5341    );
 5342}
 5343
 5344#[gpui::test]
 5345async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5346    init_test(cx, |settings| {
 5347        settings.defaults.tab_size = NonZeroU32::new(3)
 5348    });
 5349
 5350    let mut cx = EditorTestContext::new(cx).await;
 5351
 5352    // MULTI SELECTION
 5353    // Ln.1 "«" tests empty lines
 5354    // Ln.11 tests just leading whitespace
 5355    cx.set_state(indoc! {"
 5356        «
 5357        abˇ»ˇc                 // No indentation
 5358         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5359          abc  «             // 2 spaces (< 3 so dont convert)
 5360           abc              // 3 spaces (convert)
 5361             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5362        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5363        «\t abc              // Tab followed by space
 5364         \tabc              // Space followed by tab (should be consumed due to tab)
 5365        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5366           \tˇ»  «\t
 5367           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5368    "});
 5369    cx.update_editor(|e, window, cx| {
 5370        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5371    });
 5372    cx.assert_editor_state(indoc! {"
 5373        «
 5374        abc                 // No indentation
 5375         abc                // 1 space (< 3 so dont convert)
 5376          abc               // 2 spaces (< 3 so dont convert)
 5377        \tabc              // 3 spaces (convert)
 5378        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5379        \t\t\tabc           // Already tab indented
 5380        \t abc              // Tab followed by space
 5381        \tabc              // Space followed by tab (should be consumed due to tab)
 5382        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5383        \t\t\t
 5384        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5385    "});
 5386
 5387    // Test on just a few lines, the other should remain unchanged
 5388    // Only lines (4, 8, 11, 12) should change
 5389    cx.set_state(
 5390        indoc! {"
 5391            ·
 5392            abc                 // No indentation
 5393             abc                // 1 space (< 3 so dont convert)
 5394              abc               // 2 spaces (< 3 so dont convert)
 5395            «   abc              // 3 spaces (convert)ˇ»
 5396                 abc            // 5 spaces (1 tab + 2 spaces)
 5397            \t\t\tabc           // Already tab indented
 5398            \t abc              // Tab followed by space
 5399             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5400               \t\t  \tabc      // Mixed indentation
 5401            \t \t  \t   \tabc   // Mixed indentation
 5402               \t  \tˇ
 5403            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5404        "}
 5405        .replace("·", "")
 5406        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5407    );
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5410    });
 5411    cx.assert_editor_state(
 5412        indoc! {"
 5413            ·
 5414            abc                 // No indentation
 5415             abc                // 1 space (< 3 so dont convert)
 5416              abc               // 2 spaces (< 3 so dont convert)
 5417            «\tabc              // 3 spaces (convert)ˇ»
 5418                 abc            // 5 spaces (1 tab + 2 spaces)
 5419            \t\t\tabc           // Already tab indented
 5420            \t abc              // Tab followed by space
 5421            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5422               \t\t  \tabc      // Mixed indentation
 5423            \t \t  \t   \tabc   // Mixed indentation
 5424            «\t\t\t
 5425            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5426        "}
 5427        .replace("·", "")
 5428        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5429    );
 5430
 5431    // SINGLE SELECTION
 5432    // Ln.1 "«" tests empty lines
 5433    // Ln.11 tests just leading whitespace
 5434    cx.set_state(indoc! {"
 5435        «
 5436        abc                 // No indentation
 5437         abc                // 1 space (< 3 so dont convert)
 5438          abc               // 2 spaces (< 3 so dont convert)
 5439           abc              // 3 spaces (convert)
 5440             abc            // 5 spaces (1 tab + 2 spaces)
 5441        \t\t\tabc           // Already tab indented
 5442        \t abc              // Tab followed by space
 5443         \tabc              // Space followed by tab (should be consumed due to tab)
 5444        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5445           \t  \t
 5446           abc   \t         // Only the leading spaces should be convertedˇ»
 5447    "});
 5448    cx.update_editor(|e, window, cx| {
 5449        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5450    });
 5451    cx.assert_editor_state(indoc! {"
 5452        «
 5453        abc                 // No indentation
 5454         abc                // 1 space (< 3 so dont convert)
 5455          abc               // 2 spaces (< 3 so dont convert)
 5456        \tabc              // 3 spaces (convert)
 5457        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5458        \t\t\tabc           // Already tab indented
 5459        \t abc              // Tab followed by space
 5460        \tabc              // Space followed by tab (should be consumed due to tab)
 5461        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5462        \t\t\t
 5463        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5464    "});
 5465}
 5466
 5467#[gpui::test]
 5468async fn test_toggle_case(cx: &mut TestAppContext) {
 5469    init_test(cx, |_| {});
 5470
 5471    let mut cx = EditorTestContext::new(cx).await;
 5472
 5473    // If all lower case -> upper case
 5474    cx.set_state(indoc! {"
 5475        «hello worldˇ»
 5476    "});
 5477    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5478    cx.assert_editor_state(indoc! {"
 5479        «HELLO WORLDˇ»
 5480    "});
 5481
 5482    // If all upper case -> lower case
 5483    cx.set_state(indoc! {"
 5484        «HELLO WORLDˇ»
 5485    "});
 5486    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5487    cx.assert_editor_state(indoc! {"
 5488        «hello worldˇ»
 5489    "});
 5490
 5491    // If any upper case characters are identified -> lower case
 5492    // This matches JetBrains IDEs
 5493    cx.set_state(indoc! {"
 5494        «hEllo worldˇ»
 5495    "});
 5496    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5497    cx.assert_editor_state(indoc! {"
 5498        «hello worldˇ»
 5499    "});
 5500}
 5501
 5502#[gpui::test]
 5503async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5504    init_test(cx, |_| {});
 5505
 5506    let mut cx = EditorTestContext::new(cx).await;
 5507
 5508    cx.set_state(indoc! {"
 5509        «implement-windows-supportˇ»
 5510    "});
 5511    cx.update_editor(|e, window, cx| {
 5512        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5513    });
 5514    cx.assert_editor_state(indoc! {"
 5515        «Implement windows supportˇ»
 5516    "});
 5517}
 5518
 5519#[gpui::test]
 5520async fn test_manipulate_text(cx: &mut TestAppContext) {
 5521    init_test(cx, |_| {});
 5522
 5523    let mut cx = EditorTestContext::new(cx).await;
 5524
 5525    // Test convert_to_upper_case()
 5526    cx.set_state(indoc! {"
 5527        «hello worldˇ»
 5528    "});
 5529    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5530    cx.assert_editor_state(indoc! {"
 5531        «HELLO WORLDˇ»
 5532    "});
 5533
 5534    // Test convert_to_lower_case()
 5535    cx.set_state(indoc! {"
 5536        «HELLO WORLDˇ»
 5537    "});
 5538    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5539    cx.assert_editor_state(indoc! {"
 5540        «hello worldˇ»
 5541    "});
 5542
 5543    // Test multiple line, single selection case
 5544    cx.set_state(indoc! {"
 5545        «The quick brown
 5546        fox jumps over
 5547        the lazy dogˇ»
 5548    "});
 5549    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5550    cx.assert_editor_state(indoc! {"
 5551        «The Quick Brown
 5552        Fox Jumps Over
 5553        The Lazy Dogˇ»
 5554    "});
 5555
 5556    // Test multiple line, single selection case
 5557    cx.set_state(indoc! {"
 5558        «The quick brown
 5559        fox jumps over
 5560        the lazy dogˇ»
 5561    "});
 5562    cx.update_editor(|e, window, cx| {
 5563        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5564    });
 5565    cx.assert_editor_state(indoc! {"
 5566        «TheQuickBrown
 5567        FoxJumpsOver
 5568        TheLazyDogˇ»
 5569    "});
 5570
 5571    // From here on out, test more complex cases of manipulate_text()
 5572
 5573    // Test no selection case - should affect words cursors are in
 5574    // Cursor at beginning, middle, and end of word
 5575    cx.set_state(indoc! {"
 5576        ˇhello big beauˇtiful worldˇ
 5577    "});
 5578    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5579    cx.assert_editor_state(indoc! {"
 5580        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5581    "});
 5582
 5583    // Test multiple selections on a single line and across multiple lines
 5584    cx.set_state(indoc! {"
 5585        «Theˇ» quick «brown
 5586        foxˇ» jumps «overˇ»
 5587        the «lazyˇ» dog
 5588    "});
 5589    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5590    cx.assert_editor_state(indoc! {"
 5591        «THEˇ» quick «BROWN
 5592        FOXˇ» jumps «OVERˇ»
 5593        the «LAZYˇ» dog
 5594    "});
 5595
 5596    // Test case where text length grows
 5597    cx.set_state(indoc! {"
 5598        «tschüߡ»
 5599    "});
 5600    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5601    cx.assert_editor_state(indoc! {"
 5602        «TSCHÜSSˇ»
 5603    "});
 5604
 5605    // Test to make sure we don't crash when text shrinks
 5606    cx.set_state(indoc! {"
 5607        aaa_bbbˇ
 5608    "});
 5609    cx.update_editor(|e, window, cx| {
 5610        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5611    });
 5612    cx.assert_editor_state(indoc! {"
 5613        «aaaBbbˇ»
 5614    "});
 5615
 5616    // Test to make sure we all aware of the fact that each word can grow and shrink
 5617    // Final selections should be aware of this fact
 5618    cx.set_state(indoc! {"
 5619        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5620    "});
 5621    cx.update_editor(|e, window, cx| {
 5622        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5623    });
 5624    cx.assert_editor_state(indoc! {"
 5625        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5626    "});
 5627
 5628    cx.set_state(indoc! {"
 5629        «hElLo, WoRld!ˇ»
 5630    "});
 5631    cx.update_editor(|e, window, cx| {
 5632        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5633    });
 5634    cx.assert_editor_state(indoc! {"
 5635        «HeLlO, wOrLD!ˇ»
 5636    "});
 5637
 5638    // Test selections with `line_mode() = true`.
 5639    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5640    cx.set_state(indoc! {"
 5641        «The quick brown
 5642        fox jumps over
 5643        tˇ»he lazy dog
 5644    "});
 5645    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5646    cx.assert_editor_state(indoc! {"
 5647        «THE QUICK BROWN
 5648        FOX JUMPS OVER
 5649        THE LAZY DOGˇ»
 5650    "});
 5651}
 5652
 5653#[gpui::test]
 5654fn test_duplicate_line(cx: &mut TestAppContext) {
 5655    init_test(cx, |_| {});
 5656
 5657    let editor = cx.add_window(|window, cx| {
 5658        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5659        build_editor(buffer, window, cx)
 5660    });
 5661    _ = editor.update(cx, |editor, window, cx| {
 5662        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5663            s.select_display_ranges([
 5664                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5665                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5666                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5667                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5668            ])
 5669        });
 5670        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5671        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5672        assert_eq!(
 5673            display_ranges(editor, cx),
 5674            vec![
 5675                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5676                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5677                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5678                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5679            ]
 5680        );
 5681    });
 5682
 5683    let editor = cx.add_window(|window, cx| {
 5684        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5685        build_editor(buffer, window, cx)
 5686    });
 5687    _ = editor.update(cx, |editor, window, cx| {
 5688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5689            s.select_display_ranges([
 5690                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5691                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5692            ])
 5693        });
 5694        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5695        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5696        assert_eq!(
 5697            display_ranges(editor, cx),
 5698            vec![
 5699                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5700                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5701            ]
 5702        );
 5703    });
 5704
 5705    // With `duplicate_line_up` the selections move to the duplicated lines,
 5706    // which are inserted above the original lines
 5707    let editor = cx.add_window(|window, cx| {
 5708        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5709        build_editor(buffer, window, cx)
 5710    });
 5711    _ = editor.update(cx, |editor, window, cx| {
 5712        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5713            s.select_display_ranges([
 5714                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5715                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5716                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5717                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5718            ])
 5719        });
 5720        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5721        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5722        assert_eq!(
 5723            display_ranges(editor, cx),
 5724            vec![
 5725                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5726                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5727                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5728                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5729            ]
 5730        );
 5731    });
 5732
 5733    let editor = cx.add_window(|window, cx| {
 5734        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5735        build_editor(buffer, window, cx)
 5736    });
 5737    _ = editor.update(cx, |editor, window, cx| {
 5738        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5739            s.select_display_ranges([
 5740                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5741                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5742            ])
 5743        });
 5744        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5745        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5746        assert_eq!(
 5747            display_ranges(editor, cx),
 5748            vec![
 5749                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5750                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5751            ]
 5752        );
 5753    });
 5754
 5755    let editor = cx.add_window(|window, cx| {
 5756        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5757        build_editor(buffer, window, cx)
 5758    });
 5759    _ = editor.update(cx, |editor, window, cx| {
 5760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5761            s.select_display_ranges([
 5762                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5763                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5764            ])
 5765        });
 5766        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5767        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5768        assert_eq!(
 5769            display_ranges(editor, cx),
 5770            vec![
 5771                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5772                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5773            ]
 5774        );
 5775    });
 5776}
 5777
 5778#[gpui::test]
 5779async fn test_rotate_selections(cx: &mut TestAppContext) {
 5780    init_test(cx, |_| {});
 5781
 5782    let mut cx = EditorTestContext::new(cx).await;
 5783
 5784    // Rotate text selections (horizontal)
 5785    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5786    cx.update_editor(|e, window, cx| {
 5787        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5788    });
 5789    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5790    cx.update_editor(|e, window, cx| {
 5791        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5792    });
 5793    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5794
 5795    // Rotate text selections (vertical)
 5796    cx.set_state(indoc! {"
 5797        x=«1ˇ»
 5798        y=«2ˇ»
 5799        z=«3ˇ»
 5800    "});
 5801    cx.update_editor(|e, window, cx| {
 5802        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5803    });
 5804    cx.assert_editor_state(indoc! {"
 5805        x=«3ˇ»
 5806        y=«1ˇ»
 5807        z=«2ˇ»
 5808    "});
 5809    cx.update_editor(|e, window, cx| {
 5810        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5811    });
 5812    cx.assert_editor_state(indoc! {"
 5813        x=«1ˇ»
 5814        y=«2ˇ»
 5815        z=«3ˇ»
 5816    "});
 5817
 5818    // Rotate text selections (vertical, different lengths)
 5819    cx.set_state(indoc! {"
 5820        x=\"«ˇ»\"
 5821        y=\"«aˇ»\"
 5822        z=\"«aaˇ»\"
 5823    "});
 5824    cx.update_editor(|e, window, cx| {
 5825        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5826    });
 5827    cx.assert_editor_state(indoc! {"
 5828        x=\"«aaˇ»\"
 5829        y=\"«ˇ»\"
 5830        z=\"«aˇ»\"
 5831    "});
 5832    cx.update_editor(|e, window, cx| {
 5833        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5834    });
 5835    cx.assert_editor_state(indoc! {"
 5836        x=\"«ˇ»\"
 5837        y=\"«aˇ»\"
 5838        z=\"«aaˇ»\"
 5839    "});
 5840
 5841    // Rotate whole lines (cursor positions preserved)
 5842    cx.set_state(indoc! {"
 5843        ˇline123
 5844        liˇne23
 5845        line3ˇ
 5846    "});
 5847    cx.update_editor(|e, window, cx| {
 5848        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5849    });
 5850    cx.assert_editor_state(indoc! {"
 5851        line3ˇ
 5852        ˇline123
 5853        liˇne23
 5854    "});
 5855    cx.update_editor(|e, window, cx| {
 5856        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5857    });
 5858    cx.assert_editor_state(indoc! {"
 5859        ˇline123
 5860        liˇne23
 5861        line3ˇ
 5862    "});
 5863
 5864    // Rotate whole lines, multiple cursors per line (positions preserved)
 5865    cx.set_state(indoc! {"
 5866        ˇliˇne123
 5867        ˇline23
 5868        ˇline3
 5869    "});
 5870    cx.update_editor(|e, window, cx| {
 5871        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5872    });
 5873    cx.assert_editor_state(indoc! {"
 5874        ˇline3
 5875        ˇliˇne123
 5876        ˇline23
 5877    "});
 5878    cx.update_editor(|e, window, cx| {
 5879        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5880    });
 5881    cx.assert_editor_state(indoc! {"
 5882        ˇliˇne123
 5883        ˇline23
 5884        ˇline3
 5885    "});
 5886}
 5887
 5888#[gpui::test]
 5889fn test_move_line_up_down(cx: &mut TestAppContext) {
 5890    init_test(cx, |_| {});
 5891
 5892    let editor = cx.add_window(|window, cx| {
 5893        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5894        build_editor(buffer, window, cx)
 5895    });
 5896    _ = editor.update(cx, |editor, window, cx| {
 5897        editor.fold_creases(
 5898            vec![
 5899                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5900                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5901                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5902            ],
 5903            true,
 5904            window,
 5905            cx,
 5906        );
 5907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5908            s.select_display_ranges([
 5909                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5910                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5911                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5912                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5913            ])
 5914        });
 5915        assert_eq!(
 5916            editor.display_text(cx),
 5917            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5918        );
 5919
 5920        editor.move_line_up(&MoveLineUp, window, cx);
 5921        assert_eq!(
 5922            editor.display_text(cx),
 5923            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5924        );
 5925        assert_eq!(
 5926            display_ranges(editor, cx),
 5927            vec![
 5928                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5929                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5930                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5931                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5932            ]
 5933        );
 5934    });
 5935
 5936    _ = editor.update(cx, |editor, window, cx| {
 5937        editor.move_line_down(&MoveLineDown, window, cx);
 5938        assert_eq!(
 5939            editor.display_text(cx),
 5940            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5941        );
 5942        assert_eq!(
 5943            display_ranges(editor, cx),
 5944            vec![
 5945                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5946                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5947                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5948                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5949            ]
 5950        );
 5951    });
 5952
 5953    _ = editor.update(cx, |editor, window, cx| {
 5954        editor.move_line_down(&MoveLineDown, window, cx);
 5955        assert_eq!(
 5956            editor.display_text(cx),
 5957            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5958        );
 5959        assert_eq!(
 5960            display_ranges(editor, cx),
 5961            vec![
 5962                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5963                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5964                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5965                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5966            ]
 5967        );
 5968    });
 5969
 5970    _ = editor.update(cx, |editor, window, cx| {
 5971        editor.move_line_up(&MoveLineUp, window, cx);
 5972        assert_eq!(
 5973            editor.display_text(cx),
 5974            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5975        );
 5976        assert_eq!(
 5977            display_ranges(editor, cx),
 5978            vec![
 5979                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5980                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5981                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5982                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5983            ]
 5984        );
 5985    });
 5986}
 5987
 5988#[gpui::test]
 5989fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5990    init_test(cx, |_| {});
 5991    let editor = cx.add_window(|window, cx| {
 5992        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5993        build_editor(buffer, window, cx)
 5994    });
 5995    _ = editor.update(cx, |editor, window, cx| {
 5996        editor.fold_creases(
 5997            vec![Crease::simple(
 5998                Point::new(6, 4)..Point::new(7, 4),
 5999                FoldPlaceholder::test(),
 6000            )],
 6001            true,
 6002            window,
 6003            cx,
 6004        );
 6005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6006            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6007        });
 6008        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6009        editor.move_line_up(&MoveLineUp, window, cx);
 6010        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6011        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6012    });
 6013}
 6014
 6015#[gpui::test]
 6016fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6017    init_test(cx, |_| {});
 6018
 6019    let editor = cx.add_window(|window, cx| {
 6020        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6021        build_editor(buffer, window, cx)
 6022    });
 6023    _ = editor.update(cx, |editor, window, cx| {
 6024        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6025        editor.insert_blocks(
 6026            [BlockProperties {
 6027                style: BlockStyle::Fixed,
 6028                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6029                height: Some(1),
 6030                render: Arc::new(|_| div().into_any()),
 6031                priority: 0,
 6032            }],
 6033            Some(Autoscroll::fit()),
 6034            cx,
 6035        );
 6036        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6037            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6038        });
 6039        editor.move_line_down(&MoveLineDown, window, cx);
 6040    });
 6041}
 6042
 6043#[gpui::test]
 6044async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6045    init_test(cx, |_| {});
 6046
 6047    let mut cx = EditorTestContext::new(cx).await;
 6048    cx.set_state(
 6049        &"
 6050            ˇzero
 6051            one
 6052            two
 6053            three
 6054            four
 6055            five
 6056        "
 6057        .unindent(),
 6058    );
 6059
 6060    // Create a four-line block that replaces three lines of text.
 6061    cx.update_editor(|editor, window, cx| {
 6062        let snapshot = editor.snapshot(window, cx);
 6063        let snapshot = &snapshot.buffer_snapshot();
 6064        let placement = BlockPlacement::Replace(
 6065            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6066        );
 6067        editor.insert_blocks(
 6068            [BlockProperties {
 6069                placement,
 6070                height: Some(4),
 6071                style: BlockStyle::Sticky,
 6072                render: Arc::new(|_| gpui::div().into_any_element()),
 6073                priority: 0,
 6074            }],
 6075            None,
 6076            cx,
 6077        );
 6078    });
 6079
 6080    // Move down so that the cursor touches the block.
 6081    cx.update_editor(|editor, window, cx| {
 6082        editor.move_down(&Default::default(), window, cx);
 6083    });
 6084    cx.assert_editor_state(
 6085        &"
 6086            zero
 6087            «one
 6088            two
 6089            threeˇ»
 6090            four
 6091            five
 6092        "
 6093        .unindent(),
 6094    );
 6095
 6096    // Move down past the block.
 6097    cx.update_editor(|editor, window, cx| {
 6098        editor.move_down(&Default::default(), window, cx);
 6099    });
 6100    cx.assert_editor_state(
 6101        &"
 6102            zero
 6103            one
 6104            two
 6105            three
 6106            ˇfour
 6107            five
 6108        "
 6109        .unindent(),
 6110    );
 6111}
 6112
 6113#[gpui::test]
 6114fn test_transpose(cx: &mut TestAppContext) {
 6115    init_test(cx, |_| {});
 6116
 6117    _ = cx.add_window(|window, cx| {
 6118        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6119        editor.set_style(EditorStyle::default(), window, cx);
 6120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6121            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6122        });
 6123        editor.transpose(&Default::default(), window, cx);
 6124        assert_eq!(editor.text(cx), "bac");
 6125        assert_eq!(
 6126            editor.selections.ranges(&editor.display_snapshot(cx)),
 6127            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6128        );
 6129
 6130        editor.transpose(&Default::default(), window, cx);
 6131        assert_eq!(editor.text(cx), "bca");
 6132        assert_eq!(
 6133            editor.selections.ranges(&editor.display_snapshot(cx)),
 6134            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6135        );
 6136
 6137        editor.transpose(&Default::default(), window, cx);
 6138        assert_eq!(editor.text(cx), "bac");
 6139        assert_eq!(
 6140            editor.selections.ranges(&editor.display_snapshot(cx)),
 6141            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6142        );
 6143
 6144        editor
 6145    });
 6146
 6147    _ = cx.add_window(|window, cx| {
 6148        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6149        editor.set_style(EditorStyle::default(), window, cx);
 6150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6151            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6152        });
 6153        editor.transpose(&Default::default(), window, cx);
 6154        assert_eq!(editor.text(cx), "acb\nde");
 6155        assert_eq!(
 6156            editor.selections.ranges(&editor.display_snapshot(cx)),
 6157            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6158        );
 6159
 6160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6161            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6162        });
 6163        editor.transpose(&Default::default(), window, cx);
 6164        assert_eq!(editor.text(cx), "acbd\ne");
 6165        assert_eq!(
 6166            editor.selections.ranges(&editor.display_snapshot(cx)),
 6167            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6168        );
 6169
 6170        editor.transpose(&Default::default(), window, cx);
 6171        assert_eq!(editor.text(cx), "acbde\n");
 6172        assert_eq!(
 6173            editor.selections.ranges(&editor.display_snapshot(cx)),
 6174            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6175        );
 6176
 6177        editor.transpose(&Default::default(), window, cx);
 6178        assert_eq!(editor.text(cx), "acbd\ne");
 6179        assert_eq!(
 6180            editor.selections.ranges(&editor.display_snapshot(cx)),
 6181            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6182        );
 6183
 6184        editor
 6185    });
 6186
 6187    _ = cx.add_window(|window, cx| {
 6188        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6189        editor.set_style(EditorStyle::default(), window, cx);
 6190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6191            s.select_ranges([
 6192                MultiBufferOffset(1)..MultiBufferOffset(1),
 6193                MultiBufferOffset(2)..MultiBufferOffset(2),
 6194                MultiBufferOffset(4)..MultiBufferOffset(4),
 6195            ])
 6196        });
 6197        editor.transpose(&Default::default(), window, cx);
 6198        assert_eq!(editor.text(cx), "bacd\ne");
 6199        assert_eq!(
 6200            editor.selections.ranges(&editor.display_snapshot(cx)),
 6201            [
 6202                MultiBufferOffset(2)..MultiBufferOffset(2),
 6203                MultiBufferOffset(3)..MultiBufferOffset(3),
 6204                MultiBufferOffset(5)..MultiBufferOffset(5)
 6205            ]
 6206        );
 6207
 6208        editor.transpose(&Default::default(), window, cx);
 6209        assert_eq!(editor.text(cx), "bcade\n");
 6210        assert_eq!(
 6211            editor.selections.ranges(&editor.display_snapshot(cx)),
 6212            [
 6213                MultiBufferOffset(3)..MultiBufferOffset(3),
 6214                MultiBufferOffset(4)..MultiBufferOffset(4),
 6215                MultiBufferOffset(6)..MultiBufferOffset(6)
 6216            ]
 6217        );
 6218
 6219        editor.transpose(&Default::default(), window, cx);
 6220        assert_eq!(editor.text(cx), "bcda\ne");
 6221        assert_eq!(
 6222            editor.selections.ranges(&editor.display_snapshot(cx)),
 6223            [
 6224                MultiBufferOffset(4)..MultiBufferOffset(4),
 6225                MultiBufferOffset(6)..MultiBufferOffset(6)
 6226            ]
 6227        );
 6228
 6229        editor.transpose(&Default::default(), window, cx);
 6230        assert_eq!(editor.text(cx), "bcade\n");
 6231        assert_eq!(
 6232            editor.selections.ranges(&editor.display_snapshot(cx)),
 6233            [
 6234                MultiBufferOffset(4)..MultiBufferOffset(4),
 6235                MultiBufferOffset(6)..MultiBufferOffset(6)
 6236            ]
 6237        );
 6238
 6239        editor.transpose(&Default::default(), window, cx);
 6240        assert_eq!(editor.text(cx), "bcaed\n");
 6241        assert_eq!(
 6242            editor.selections.ranges(&editor.display_snapshot(cx)),
 6243            [
 6244                MultiBufferOffset(5)..MultiBufferOffset(5),
 6245                MultiBufferOffset(6)..MultiBufferOffset(6)
 6246            ]
 6247        );
 6248
 6249        editor
 6250    });
 6251
 6252    _ = cx.add_window(|window, cx| {
 6253        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6254        editor.set_style(EditorStyle::default(), window, cx);
 6255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6256            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6257        });
 6258        editor.transpose(&Default::default(), window, cx);
 6259        assert_eq!(editor.text(cx), "🏀🍐✋");
 6260        assert_eq!(
 6261            editor.selections.ranges(&editor.display_snapshot(cx)),
 6262            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6263        );
 6264
 6265        editor.transpose(&Default::default(), window, cx);
 6266        assert_eq!(editor.text(cx), "🏀✋🍐");
 6267        assert_eq!(
 6268            editor.selections.ranges(&editor.display_snapshot(cx)),
 6269            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6270        );
 6271
 6272        editor.transpose(&Default::default(), window, cx);
 6273        assert_eq!(editor.text(cx), "🏀🍐✋");
 6274        assert_eq!(
 6275            editor.selections.ranges(&editor.display_snapshot(cx)),
 6276            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6277        );
 6278
 6279        editor
 6280    });
 6281}
 6282
 6283#[gpui::test]
 6284async fn test_rewrap(cx: &mut TestAppContext) {
 6285    init_test(cx, |settings| {
 6286        settings.languages.0.extend([
 6287            (
 6288                "Markdown".into(),
 6289                LanguageSettingsContent {
 6290                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6291                    preferred_line_length: Some(40),
 6292                    ..Default::default()
 6293                },
 6294            ),
 6295            (
 6296                "Plain Text".into(),
 6297                LanguageSettingsContent {
 6298                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6299                    preferred_line_length: Some(40),
 6300                    ..Default::default()
 6301                },
 6302            ),
 6303            (
 6304                "C++".into(),
 6305                LanguageSettingsContent {
 6306                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6307                    preferred_line_length: Some(40),
 6308                    ..Default::default()
 6309                },
 6310            ),
 6311            (
 6312                "Python".into(),
 6313                LanguageSettingsContent {
 6314                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6315                    preferred_line_length: Some(40),
 6316                    ..Default::default()
 6317                },
 6318            ),
 6319            (
 6320                "Rust".into(),
 6321                LanguageSettingsContent {
 6322                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6323                    preferred_line_length: Some(40),
 6324                    ..Default::default()
 6325                },
 6326            ),
 6327        ])
 6328    });
 6329
 6330    let mut cx = EditorTestContext::new(cx).await;
 6331
 6332    let cpp_language = Arc::new(Language::new(
 6333        LanguageConfig {
 6334            name: "C++".into(),
 6335            line_comments: vec!["// ".into()],
 6336            ..LanguageConfig::default()
 6337        },
 6338        None,
 6339    ));
 6340    let python_language = Arc::new(Language::new(
 6341        LanguageConfig {
 6342            name: "Python".into(),
 6343            line_comments: vec!["# ".into()],
 6344            ..LanguageConfig::default()
 6345        },
 6346        None,
 6347    ));
 6348    let markdown_language = Arc::new(Language::new(
 6349        LanguageConfig {
 6350            name: "Markdown".into(),
 6351            rewrap_prefixes: vec![
 6352                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6353                regex::Regex::new("[-*+]\\s+").unwrap(),
 6354            ],
 6355            ..LanguageConfig::default()
 6356        },
 6357        None,
 6358    ));
 6359    let rust_language = Arc::new(
 6360        Language::new(
 6361            LanguageConfig {
 6362                name: "Rust".into(),
 6363                line_comments: vec!["// ".into(), "/// ".into()],
 6364                ..LanguageConfig::default()
 6365            },
 6366            Some(tree_sitter_rust::LANGUAGE.into()),
 6367        )
 6368        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6369        .unwrap(),
 6370    );
 6371
 6372    let plaintext_language = Arc::new(Language::new(
 6373        LanguageConfig {
 6374            name: "Plain Text".into(),
 6375            ..LanguageConfig::default()
 6376        },
 6377        None,
 6378    ));
 6379
 6380    // Test basic rewrapping of a long line with a cursor
 6381    assert_rewrap(
 6382        indoc! {"
 6383            // ˇThis is a long comment that needs to be wrapped.
 6384        "},
 6385        indoc! {"
 6386            // ˇThis is a long comment that needs to
 6387            // be wrapped.
 6388        "},
 6389        cpp_language.clone(),
 6390        &mut cx,
 6391    );
 6392
 6393    // Test rewrapping a full selection
 6394    assert_rewrap(
 6395        indoc! {"
 6396            «// This selected long comment needs to be wrapped.ˇ»"
 6397        },
 6398        indoc! {"
 6399            «// This selected long comment needs to
 6400            // be wrapped.ˇ»"
 6401        },
 6402        cpp_language.clone(),
 6403        &mut cx,
 6404    );
 6405
 6406    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6407    assert_rewrap(
 6408        indoc! {"
 6409            // ˇThis is the first line.
 6410            // Thisˇ is the second line.
 6411            // This is the thirdˇ line, all part of one paragraph.
 6412         "},
 6413        indoc! {"
 6414            // ˇThis is the first line. Thisˇ is the
 6415            // second line. This is the thirdˇ line,
 6416            // all part of one paragraph.
 6417         "},
 6418        cpp_language.clone(),
 6419        &mut cx,
 6420    );
 6421
 6422    // Test multiple cursors in different paragraphs trigger separate rewraps
 6423    assert_rewrap(
 6424        indoc! {"
 6425            // ˇThis is the first paragraph, first line.
 6426            // ˇThis is the first paragraph, second line.
 6427
 6428            // ˇThis is the second paragraph, first line.
 6429            // ˇThis is the second paragraph, second line.
 6430        "},
 6431        indoc! {"
 6432            // ˇThis is the first paragraph, first
 6433            // line. ˇThis is the first paragraph,
 6434            // second line.
 6435
 6436            // ˇThis is the second paragraph, first
 6437            // line. ˇThis is the second paragraph,
 6438            // second line.
 6439        "},
 6440        cpp_language.clone(),
 6441        &mut cx,
 6442    );
 6443
 6444    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6445    assert_rewrap(
 6446        indoc! {"
 6447            «// A regular long long comment to be wrapped.
 6448            /// A documentation long comment to be wrapped.ˇ»
 6449          "},
 6450        indoc! {"
 6451            «// A regular long long comment to be
 6452            // wrapped.
 6453            /// A documentation long comment to be
 6454            /// wrapped.ˇ»
 6455          "},
 6456        rust_language.clone(),
 6457        &mut cx,
 6458    );
 6459
 6460    // Test that change in indentation level trigger seperate rewraps
 6461    assert_rewrap(
 6462        indoc! {"
 6463            fn foo() {
 6464                «// This is a long comment at the base indent.
 6465                    // This is a long comment at the next indent.ˇ»
 6466            }
 6467        "},
 6468        indoc! {"
 6469            fn foo() {
 6470                «// This is a long comment at the
 6471                // base indent.
 6472                    // This is a long comment at the
 6473                    // next indent.ˇ»
 6474            }
 6475        "},
 6476        rust_language.clone(),
 6477        &mut cx,
 6478    );
 6479
 6480    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6481    assert_rewrap(
 6482        indoc! {"
 6483            # ˇThis is a long comment using a pound sign.
 6484        "},
 6485        indoc! {"
 6486            # ˇThis is a long comment using a pound
 6487            # sign.
 6488        "},
 6489        python_language,
 6490        &mut cx,
 6491    );
 6492
 6493    // Test rewrapping only affects comments, not code even when selected
 6494    assert_rewrap(
 6495        indoc! {"
 6496            «/// This doc comment is long and should be wrapped.
 6497            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6498        "},
 6499        indoc! {"
 6500            «/// This doc comment is long and should
 6501            /// be wrapped.
 6502            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6503        "},
 6504        rust_language.clone(),
 6505        &mut cx,
 6506    );
 6507
 6508    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6509    assert_rewrap(
 6510        indoc! {"
 6511            # Header
 6512
 6513            A long long long line of markdown text to wrap.ˇ
 6514         "},
 6515        indoc! {"
 6516            # Header
 6517
 6518            A long long long line of markdown text
 6519            to wrap.ˇ
 6520         "},
 6521        markdown_language.clone(),
 6522        &mut cx,
 6523    );
 6524
 6525    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6526    assert_rewrap(
 6527        indoc! {"
 6528            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6529            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6530            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6531        "},
 6532        indoc! {"
 6533            «1. This is a numbered list item that is
 6534               very long and needs to be wrapped
 6535               properly.
 6536            2. This is a numbered list item that is
 6537               very long and needs to be wrapped
 6538               properly.
 6539            - This is an unordered list item that is
 6540              also very long and should not merge
 6541              with the numbered item.ˇ»
 6542        "},
 6543        markdown_language.clone(),
 6544        &mut cx,
 6545    );
 6546
 6547    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6548    assert_rewrap(
 6549        indoc! {"
 6550            «1. This is a numbered list item that is
 6551            very long and needs to be wrapped
 6552            properly.
 6553            2. This is a numbered list item that is
 6554            very long and needs to be wrapped
 6555            properly.
 6556            - This is an unordered list item that is
 6557            also very long and should not merge with
 6558            the numbered item.ˇ»
 6559        "},
 6560        indoc! {"
 6561            «1. This is a numbered list item that is
 6562               very long and needs to be wrapped
 6563               properly.
 6564            2. This is a numbered list item that is
 6565               very long and needs to be wrapped
 6566               properly.
 6567            - This is an unordered list item that is
 6568              also very long and should not merge
 6569              with the numbered item.ˇ»
 6570        "},
 6571        markdown_language.clone(),
 6572        &mut cx,
 6573    );
 6574
 6575    // Test that rewrapping maintain indents even when they already exists.
 6576    assert_rewrap(
 6577        indoc! {"
 6578            «1. This is a numbered list
 6579               item that is very long and needs to be wrapped properly.
 6580            2. This is a numbered list
 6581               item that is very long and needs to be wrapped properly.
 6582            - This is an unordered list item that is also very long and
 6583              should not merge with the numbered item.ˇ»
 6584        "},
 6585        indoc! {"
 6586            «1. This is a numbered list item that is
 6587               very long and needs to be wrapped
 6588               properly.
 6589            2. This is a numbered list item that is
 6590               very long and needs to be wrapped
 6591               properly.
 6592            - This is an unordered list item that is
 6593              also very long and should not merge
 6594              with the numbered item.ˇ»
 6595        "},
 6596        markdown_language,
 6597        &mut cx,
 6598    );
 6599
 6600    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6601    assert_rewrap(
 6602        indoc! {"
 6603            ˇThis is a very long line of plain text that will be wrapped.
 6604        "},
 6605        indoc! {"
 6606            ˇThis is a very long line of plain text
 6607            that will be wrapped.
 6608        "},
 6609        plaintext_language.clone(),
 6610        &mut cx,
 6611    );
 6612
 6613    // Test that non-commented code acts as a paragraph boundary within a selection
 6614    assert_rewrap(
 6615        indoc! {"
 6616               «// This is the first long comment block to be wrapped.
 6617               fn my_func(a: u32);
 6618               // This is the second long comment block to be wrapped.ˇ»
 6619           "},
 6620        indoc! {"
 6621               «// This is the first long comment block
 6622               // to be wrapped.
 6623               fn my_func(a: u32);
 6624               // This is the second long comment block
 6625               // to be wrapped.ˇ»
 6626           "},
 6627        rust_language,
 6628        &mut cx,
 6629    );
 6630
 6631    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6632    assert_rewrap(
 6633        indoc! {"
 6634            «ˇThis is a very long line that will be wrapped.
 6635
 6636            This is another paragraph in the same selection.»
 6637
 6638            «\tThis is a very long indented line that will be wrapped.ˇ»
 6639         "},
 6640        indoc! {"
 6641            «ˇThis is a very long line that will be
 6642            wrapped.
 6643
 6644            This is another paragraph in the same
 6645            selection.»
 6646
 6647            «\tThis is a very long indented line
 6648            \tthat will be wrapped.ˇ»
 6649         "},
 6650        plaintext_language,
 6651        &mut cx,
 6652    );
 6653
 6654    // Test that an empty comment line acts as a paragraph boundary
 6655    assert_rewrap(
 6656        indoc! {"
 6657            // ˇThis is a long comment that will be wrapped.
 6658            //
 6659            // And this is another long comment that will also be wrapped.ˇ
 6660         "},
 6661        indoc! {"
 6662            // ˇThis is a long comment that will be
 6663            // wrapped.
 6664            //
 6665            // And this is another long comment that
 6666            // will also be wrapped.ˇ
 6667         "},
 6668        cpp_language,
 6669        &mut cx,
 6670    );
 6671
 6672    #[track_caller]
 6673    fn assert_rewrap(
 6674        unwrapped_text: &str,
 6675        wrapped_text: &str,
 6676        language: Arc<Language>,
 6677        cx: &mut EditorTestContext,
 6678    ) {
 6679        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6680        cx.set_state(unwrapped_text);
 6681        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6682        cx.assert_editor_state(wrapped_text);
 6683    }
 6684}
 6685
 6686#[gpui::test]
 6687async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6688    init_test(cx, |settings| {
 6689        settings.languages.0.extend([(
 6690            "Rust".into(),
 6691            LanguageSettingsContent {
 6692                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6693                preferred_line_length: Some(40),
 6694                ..Default::default()
 6695            },
 6696        )])
 6697    });
 6698
 6699    let mut cx = EditorTestContext::new(cx).await;
 6700
 6701    let rust_lang = Arc::new(
 6702        Language::new(
 6703            LanguageConfig {
 6704                name: "Rust".into(),
 6705                line_comments: vec!["// ".into()],
 6706                block_comment: Some(BlockCommentConfig {
 6707                    start: "/*".into(),
 6708                    end: "*/".into(),
 6709                    prefix: "* ".into(),
 6710                    tab_size: 1,
 6711                }),
 6712                documentation_comment: Some(BlockCommentConfig {
 6713                    start: "/**".into(),
 6714                    end: "*/".into(),
 6715                    prefix: "* ".into(),
 6716                    tab_size: 1,
 6717                }),
 6718
 6719                ..LanguageConfig::default()
 6720            },
 6721            Some(tree_sitter_rust::LANGUAGE.into()),
 6722        )
 6723        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6724        .unwrap(),
 6725    );
 6726
 6727    // regular block comment
 6728    assert_rewrap(
 6729        indoc! {"
 6730            /*
 6731             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6732             */
 6733            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6734        "},
 6735        indoc! {"
 6736            /*
 6737             *ˇ Lorem ipsum dolor sit amet,
 6738             * consectetur adipiscing elit.
 6739             */
 6740            /*
 6741             *ˇ Lorem ipsum dolor sit amet,
 6742             * consectetur adipiscing elit.
 6743             */
 6744        "},
 6745        rust_lang.clone(),
 6746        &mut cx,
 6747    );
 6748
 6749    // indent is respected
 6750    assert_rewrap(
 6751        indoc! {"
 6752            {}
 6753                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6754        "},
 6755        indoc! {"
 6756            {}
 6757                /*
 6758                 *ˇ Lorem ipsum dolor sit amet,
 6759                 * consectetur adipiscing elit.
 6760                 */
 6761        "},
 6762        rust_lang.clone(),
 6763        &mut cx,
 6764    );
 6765
 6766    // short block comments with inline delimiters
 6767    assert_rewrap(
 6768        indoc! {"
 6769            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6770            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6771             */
 6772            /*
 6773             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6774        "},
 6775        indoc! {"
 6776            /*
 6777             *ˇ Lorem ipsum dolor sit amet,
 6778             * consectetur adipiscing elit.
 6779             */
 6780            /*
 6781             *ˇ Lorem ipsum dolor sit amet,
 6782             * consectetur adipiscing elit.
 6783             */
 6784            /*
 6785             *ˇ Lorem ipsum dolor sit amet,
 6786             * consectetur adipiscing elit.
 6787             */
 6788        "},
 6789        rust_lang.clone(),
 6790        &mut cx,
 6791    );
 6792
 6793    // multiline block comment with inline start/end delimiters
 6794    assert_rewrap(
 6795        indoc! {"
 6796            /*ˇ Lorem ipsum dolor sit amet,
 6797             * consectetur adipiscing elit. */
 6798        "},
 6799        indoc! {"
 6800            /*
 6801             *ˇ Lorem ipsum dolor sit amet,
 6802             * consectetur adipiscing elit.
 6803             */
 6804        "},
 6805        rust_lang.clone(),
 6806        &mut cx,
 6807    );
 6808
 6809    // block comment rewrap still respects paragraph bounds
 6810    assert_rewrap(
 6811        indoc! {"
 6812            /*
 6813             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6814             *
 6815             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6816             */
 6817        "},
 6818        indoc! {"
 6819            /*
 6820             *ˇ Lorem ipsum dolor sit amet,
 6821             * consectetur adipiscing elit.
 6822             *
 6823             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6824             */
 6825        "},
 6826        rust_lang.clone(),
 6827        &mut cx,
 6828    );
 6829
 6830    // documentation comments
 6831    assert_rewrap(
 6832        indoc! {"
 6833            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6834            /**
 6835             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6836             */
 6837        "},
 6838        indoc! {"
 6839            /**
 6840             *ˇ Lorem ipsum dolor sit amet,
 6841             * consectetur adipiscing elit.
 6842             */
 6843            /**
 6844             *ˇ Lorem ipsum dolor sit amet,
 6845             * consectetur adipiscing elit.
 6846             */
 6847        "},
 6848        rust_lang.clone(),
 6849        &mut cx,
 6850    );
 6851
 6852    // different, adjacent comments
 6853    assert_rewrap(
 6854        indoc! {"
 6855            /**
 6856             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6857             */
 6858            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6859            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6860        "},
 6861        indoc! {"
 6862            /**
 6863             *ˇ Lorem ipsum dolor sit amet,
 6864             * consectetur adipiscing elit.
 6865             */
 6866            /*
 6867             *ˇ Lorem ipsum dolor sit amet,
 6868             * consectetur adipiscing elit.
 6869             */
 6870            //ˇ Lorem ipsum dolor sit amet,
 6871            // consectetur adipiscing elit.
 6872        "},
 6873        rust_lang.clone(),
 6874        &mut cx,
 6875    );
 6876
 6877    // selection w/ single short block comment
 6878    assert_rewrap(
 6879        indoc! {"
 6880            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6881        "},
 6882        indoc! {"
 6883            «/*
 6884             * Lorem ipsum dolor sit amet,
 6885             * consectetur adipiscing elit.
 6886             */ˇ»
 6887        "},
 6888        rust_lang.clone(),
 6889        &mut cx,
 6890    );
 6891
 6892    // rewrapping a single comment w/ abutting comments
 6893    assert_rewrap(
 6894        indoc! {"
 6895            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6896            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6897        "},
 6898        indoc! {"
 6899            /*
 6900             * ˇLorem ipsum dolor sit amet,
 6901             * consectetur adipiscing elit.
 6902             */
 6903            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6904        "},
 6905        rust_lang.clone(),
 6906        &mut cx,
 6907    );
 6908
 6909    // selection w/ non-abutting short block comments
 6910    assert_rewrap(
 6911        indoc! {"
 6912            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6913
 6914            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6915        "},
 6916        indoc! {"
 6917            «/*
 6918             * Lorem ipsum dolor sit amet,
 6919             * consectetur adipiscing elit.
 6920             */
 6921
 6922            /*
 6923             * Lorem ipsum dolor sit amet,
 6924             * consectetur adipiscing elit.
 6925             */ˇ»
 6926        "},
 6927        rust_lang.clone(),
 6928        &mut cx,
 6929    );
 6930
 6931    // selection of multiline block comments
 6932    assert_rewrap(
 6933        indoc! {"
 6934            «/* Lorem ipsum dolor sit amet,
 6935             * consectetur adipiscing elit. */ˇ»
 6936        "},
 6937        indoc! {"
 6938            «/*
 6939             * Lorem ipsum dolor sit amet,
 6940             * consectetur adipiscing elit.
 6941             */ˇ»
 6942        "},
 6943        rust_lang.clone(),
 6944        &mut cx,
 6945    );
 6946
 6947    // partial selection of multiline block comments
 6948    assert_rewrap(
 6949        indoc! {"
 6950            «/* Lorem ipsum dolor sit amet,ˇ»
 6951             * consectetur adipiscing elit. */
 6952            /* Lorem ipsum dolor sit amet,
 6953             «* consectetur adipiscing elit. */ˇ»
 6954        "},
 6955        indoc! {"
 6956            «/*
 6957             * Lorem ipsum dolor sit amet,ˇ»
 6958             * consectetur adipiscing elit. */
 6959            /* Lorem ipsum dolor sit amet,
 6960             «* consectetur adipiscing elit.
 6961             */ˇ»
 6962        "},
 6963        rust_lang.clone(),
 6964        &mut cx,
 6965    );
 6966
 6967    // selection w/ abutting short block comments
 6968    // TODO: should not be combined; should rewrap as 2 comments
 6969    assert_rewrap(
 6970        indoc! {"
 6971            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6972            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6973        "},
 6974        // desired behavior:
 6975        // indoc! {"
 6976        //     «/*
 6977        //      * Lorem ipsum dolor sit amet,
 6978        //      * consectetur adipiscing elit.
 6979        //      */
 6980        //     /*
 6981        //      * Lorem ipsum dolor sit amet,
 6982        //      * consectetur adipiscing elit.
 6983        //      */ˇ»
 6984        // "},
 6985        // actual behaviour:
 6986        indoc! {"
 6987            «/*
 6988             * Lorem ipsum dolor sit amet,
 6989             * consectetur adipiscing elit. Lorem
 6990             * ipsum dolor sit amet, consectetur
 6991             * adipiscing elit.
 6992             */ˇ»
 6993        "},
 6994        rust_lang.clone(),
 6995        &mut cx,
 6996    );
 6997
 6998    // TODO: same as above, but with delimiters on separate line
 6999    // assert_rewrap(
 7000    //     indoc! {"
 7001    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7002    //          */
 7003    //         /*
 7004    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7005    //     "},
 7006    //     // desired:
 7007    //     // indoc! {"
 7008    //     //     «/*
 7009    //     //      * Lorem ipsum dolor sit amet,
 7010    //     //      * consectetur adipiscing elit.
 7011    //     //      */
 7012    //     //     /*
 7013    //     //      * Lorem ipsum dolor sit amet,
 7014    //     //      * consectetur adipiscing elit.
 7015    //     //      */ˇ»
 7016    //     // "},
 7017    //     // actual: (but with trailing w/s on the empty lines)
 7018    //     indoc! {"
 7019    //         «/*
 7020    //          * Lorem ipsum dolor sit amet,
 7021    //          * consectetur adipiscing elit.
 7022    //          *
 7023    //          */
 7024    //         /*
 7025    //          *
 7026    //          * Lorem ipsum dolor sit amet,
 7027    //          * consectetur adipiscing elit.
 7028    //          */ˇ»
 7029    //     "},
 7030    //     rust_lang.clone(),
 7031    //     &mut cx,
 7032    // );
 7033
 7034    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7035    assert_rewrap(
 7036        indoc! {"
 7037            /*
 7038             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7039             */
 7040            /*
 7041             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7042            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7043        "},
 7044        // desired:
 7045        // indoc! {"
 7046        //     /*
 7047        //      *ˇ Lorem ipsum dolor sit amet,
 7048        //      * consectetur adipiscing elit.
 7049        //      */
 7050        //     /*
 7051        //      *ˇ Lorem ipsum dolor sit amet,
 7052        //      * consectetur adipiscing elit.
 7053        //      */
 7054        //     /*
 7055        //      *ˇ Lorem ipsum dolor sit amet
 7056        //      */ /* consectetur adipiscing elit. */
 7057        // "},
 7058        // actual:
 7059        indoc! {"
 7060            /*
 7061             //ˇ Lorem ipsum dolor sit amet,
 7062             // consectetur adipiscing elit.
 7063             */
 7064            /*
 7065             * //ˇ Lorem ipsum dolor sit amet,
 7066             * consectetur adipiscing elit.
 7067             */
 7068            /*
 7069             *ˇ Lorem ipsum dolor sit amet */ /*
 7070             * consectetur adipiscing elit.
 7071             */
 7072        "},
 7073        rust_lang,
 7074        &mut cx,
 7075    );
 7076
 7077    #[track_caller]
 7078    fn assert_rewrap(
 7079        unwrapped_text: &str,
 7080        wrapped_text: &str,
 7081        language: Arc<Language>,
 7082        cx: &mut EditorTestContext,
 7083    ) {
 7084        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7085        cx.set_state(unwrapped_text);
 7086        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7087        cx.assert_editor_state(wrapped_text);
 7088    }
 7089}
 7090
 7091#[gpui::test]
 7092async fn test_hard_wrap(cx: &mut TestAppContext) {
 7093    init_test(cx, |_| {});
 7094    let mut cx = EditorTestContext::new(cx).await;
 7095
 7096    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7097    cx.update_editor(|editor, _, cx| {
 7098        editor.set_hard_wrap(Some(14), cx);
 7099    });
 7100
 7101    cx.set_state(indoc!(
 7102        "
 7103        one two three ˇ
 7104        "
 7105    ));
 7106    cx.simulate_input("four");
 7107    cx.run_until_parked();
 7108
 7109    cx.assert_editor_state(indoc!(
 7110        "
 7111        one two three
 7112        fourˇ
 7113        "
 7114    ));
 7115
 7116    cx.update_editor(|editor, window, cx| {
 7117        editor.newline(&Default::default(), window, cx);
 7118    });
 7119    cx.run_until_parked();
 7120    cx.assert_editor_state(indoc!(
 7121        "
 7122        one two three
 7123        four
 7124        ˇ
 7125        "
 7126    ));
 7127
 7128    cx.simulate_input("five");
 7129    cx.run_until_parked();
 7130    cx.assert_editor_state(indoc!(
 7131        "
 7132        one two three
 7133        four
 7134        fiveˇ
 7135        "
 7136    ));
 7137
 7138    cx.update_editor(|editor, window, cx| {
 7139        editor.newline(&Default::default(), window, cx);
 7140    });
 7141    cx.run_until_parked();
 7142    cx.simulate_input("# ");
 7143    cx.run_until_parked();
 7144    cx.assert_editor_state(indoc!(
 7145        "
 7146        one two three
 7147        four
 7148        five
 7149        # ˇ
 7150        "
 7151    ));
 7152
 7153    cx.update_editor(|editor, window, cx| {
 7154        editor.newline(&Default::default(), window, cx);
 7155    });
 7156    cx.run_until_parked();
 7157    cx.assert_editor_state(indoc!(
 7158        "
 7159        one two three
 7160        four
 7161        five
 7162        #\x20
 7163 7164        "
 7165    ));
 7166
 7167    cx.simulate_input(" 6");
 7168    cx.run_until_parked();
 7169    cx.assert_editor_state(indoc!(
 7170        "
 7171        one two three
 7172        four
 7173        five
 7174        #
 7175        # 6ˇ
 7176        "
 7177    ));
 7178}
 7179
 7180#[gpui::test]
 7181async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7182    init_test(cx, |_| {});
 7183
 7184    let mut cx = EditorTestContext::new(cx).await;
 7185
 7186    cx.set_state(indoc! {"The quick brownˇ"});
 7187    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7188    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7189
 7190    cx.set_state(indoc! {"The emacs foxˇ"});
 7191    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7192    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7193
 7194    cx.set_state(indoc! {"
 7195        The quick« brownˇ»
 7196        fox jumps overˇ
 7197        the lazy dog"});
 7198    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7199    cx.assert_editor_state(indoc! {"
 7200        The quickˇ
 7201        ˇthe lazy dog"});
 7202
 7203    cx.set_state(indoc! {"
 7204        The quick« brownˇ»
 7205        fox jumps overˇ
 7206        the lazy dog"});
 7207    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7208    cx.assert_editor_state(indoc! {"
 7209        The quickˇ
 7210        fox jumps overˇthe lazy dog"});
 7211
 7212    cx.set_state(indoc! {"
 7213        The quick« brownˇ»
 7214        fox jumps overˇ
 7215        the lazy dog"});
 7216    cx.update_editor(|e, window, cx| {
 7217        e.cut_to_end_of_line(
 7218            &CutToEndOfLine {
 7219                stop_at_newlines: true,
 7220            },
 7221            window,
 7222            cx,
 7223        )
 7224    });
 7225    cx.assert_editor_state(indoc! {"
 7226        The quickˇ
 7227        fox jumps overˇ
 7228        the lazy dog"});
 7229
 7230    cx.set_state(indoc! {"
 7231        The quick« brownˇ»
 7232        fox jumps overˇ
 7233        the lazy dog"});
 7234    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7235    cx.assert_editor_state(indoc! {"
 7236        The quickˇ
 7237        fox jumps overˇthe lazy dog"});
 7238}
 7239
 7240#[gpui::test]
 7241async fn test_clipboard(cx: &mut TestAppContext) {
 7242    init_test(cx, |_| {});
 7243
 7244    let mut cx = EditorTestContext::new(cx).await;
 7245
 7246    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7247    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7248    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7249
 7250    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7251    cx.set_state("two ˇfour ˇsix ˇ");
 7252    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7253    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7254
 7255    // Paste again but with only two cursors. Since the number of cursors doesn't
 7256    // match the number of slices in the clipboard, the entire clipboard text
 7257    // is pasted at each cursor.
 7258    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7259    cx.update_editor(|e, window, cx| {
 7260        e.handle_input("( ", window, cx);
 7261        e.paste(&Paste, window, cx);
 7262        e.handle_input(") ", window, cx);
 7263    });
 7264    cx.assert_editor_state(
 7265        &([
 7266            "( one✅ ",
 7267            "three ",
 7268            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7269            "three ",
 7270            "five ) ˇ",
 7271        ]
 7272        .join("\n")),
 7273    );
 7274
 7275    // Cut with three selections, one of which is full-line.
 7276    cx.set_state(indoc! {"
 7277        1«2ˇ»3
 7278        4ˇ567
 7279        «8ˇ»9"});
 7280    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7281    cx.assert_editor_state(indoc! {"
 7282        1ˇ3
 7283        ˇ9"});
 7284
 7285    // Paste with three selections, noticing how the copied selection that was full-line
 7286    // gets inserted before the second cursor.
 7287    cx.set_state(indoc! {"
 7288        1ˇ3
 7289 7290        «oˇ»ne"});
 7291    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7292    cx.assert_editor_state(indoc! {"
 7293        12ˇ3
 7294        4567
 7295 7296        8ˇne"});
 7297
 7298    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7299    cx.set_state(indoc! {"
 7300        The quick brown
 7301        fox juˇmps over
 7302        the lazy dog"});
 7303    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7304    assert_eq!(
 7305        cx.read_from_clipboard()
 7306            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7307        Some("fox jumps over\n".to_string())
 7308    );
 7309
 7310    // Paste with three selections, noticing how the copied full-line selection is inserted
 7311    // before the empty selections but replaces the selection that is non-empty.
 7312    cx.set_state(indoc! {"
 7313        Tˇhe quick brown
 7314        «foˇ»x jumps over
 7315        tˇhe lazy dog"});
 7316    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7317    cx.assert_editor_state(indoc! {"
 7318        fox jumps over
 7319        Tˇhe quick brown
 7320        fox jumps over
 7321        ˇx jumps over
 7322        fox jumps over
 7323        tˇhe lazy dog"});
 7324}
 7325
 7326#[gpui::test]
 7327async fn test_copy_trim(cx: &mut TestAppContext) {
 7328    init_test(cx, |_| {});
 7329
 7330    let mut cx = EditorTestContext::new(cx).await;
 7331    cx.set_state(
 7332        r#"            «for selection in selections.iter() {
 7333            let mut start = selection.start;
 7334            let mut end = selection.end;
 7335            let is_entire_line = selection.is_empty();
 7336            if is_entire_line {
 7337                start = Point::new(start.row, 0);ˇ»
 7338                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7339            }
 7340        "#,
 7341    );
 7342    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7343    assert_eq!(
 7344        cx.read_from_clipboard()
 7345            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7346        Some(
 7347            "for selection in selections.iter() {
 7348            let mut start = selection.start;
 7349            let mut end = selection.end;
 7350            let is_entire_line = selection.is_empty();
 7351            if is_entire_line {
 7352                start = Point::new(start.row, 0);"
 7353                .to_string()
 7354        ),
 7355        "Regular copying preserves all indentation selected",
 7356    );
 7357    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7358    assert_eq!(
 7359        cx.read_from_clipboard()
 7360            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7361        Some(
 7362            "for selection in selections.iter() {
 7363let mut start = selection.start;
 7364let mut end = selection.end;
 7365let is_entire_line = selection.is_empty();
 7366if is_entire_line {
 7367    start = Point::new(start.row, 0);"
 7368                .to_string()
 7369        ),
 7370        "Copying with stripping should strip all leading whitespaces"
 7371    );
 7372
 7373    cx.set_state(
 7374        r#"       «     for selection in selections.iter() {
 7375            let mut start = selection.start;
 7376            let mut end = selection.end;
 7377            let is_entire_line = selection.is_empty();
 7378            if is_entire_line {
 7379                start = Point::new(start.row, 0);ˇ»
 7380                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7381            }
 7382        "#,
 7383    );
 7384    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7385    assert_eq!(
 7386        cx.read_from_clipboard()
 7387            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7388        Some(
 7389            "     for selection in selections.iter() {
 7390            let mut start = selection.start;
 7391            let mut end = selection.end;
 7392            let is_entire_line = selection.is_empty();
 7393            if is_entire_line {
 7394                start = Point::new(start.row, 0);"
 7395                .to_string()
 7396        ),
 7397        "Regular copying preserves all indentation selected",
 7398    );
 7399    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7400    assert_eq!(
 7401        cx.read_from_clipboard()
 7402            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7403        Some(
 7404            "for selection in selections.iter() {
 7405let mut start = selection.start;
 7406let mut end = selection.end;
 7407let is_entire_line = selection.is_empty();
 7408if is_entire_line {
 7409    start = Point::new(start.row, 0);"
 7410                .to_string()
 7411        ),
 7412        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7413    );
 7414
 7415    cx.set_state(
 7416        r#"       «ˇ     for selection in selections.iter() {
 7417            let mut start = selection.start;
 7418            let mut end = selection.end;
 7419            let is_entire_line = selection.is_empty();
 7420            if is_entire_line {
 7421                start = Point::new(start.row, 0);»
 7422                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7423            }
 7424        "#,
 7425    );
 7426    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7427    assert_eq!(
 7428        cx.read_from_clipboard()
 7429            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7430        Some(
 7431            "     for selection in selections.iter() {
 7432            let mut start = selection.start;
 7433            let mut end = selection.end;
 7434            let is_entire_line = selection.is_empty();
 7435            if is_entire_line {
 7436                start = Point::new(start.row, 0);"
 7437                .to_string()
 7438        ),
 7439        "Regular copying for reverse selection works the same",
 7440    );
 7441    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7442    assert_eq!(
 7443        cx.read_from_clipboard()
 7444            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7445        Some(
 7446            "for selection in selections.iter() {
 7447let mut start = selection.start;
 7448let mut end = selection.end;
 7449let is_entire_line = selection.is_empty();
 7450if is_entire_line {
 7451    start = Point::new(start.row, 0);"
 7452                .to_string()
 7453        ),
 7454        "Copying with stripping for reverse selection works the same"
 7455    );
 7456
 7457    cx.set_state(
 7458        r#"            for selection «in selections.iter() {
 7459            let mut start = selection.start;
 7460            let mut end = selection.end;
 7461            let is_entire_line = selection.is_empty();
 7462            if is_entire_line {
 7463                start = Point::new(start.row, 0);ˇ»
 7464                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7465            }
 7466        "#,
 7467    );
 7468    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7469    assert_eq!(
 7470        cx.read_from_clipboard()
 7471            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7472        Some(
 7473            "in selections.iter() {
 7474            let mut start = selection.start;
 7475            let mut end = selection.end;
 7476            let is_entire_line = selection.is_empty();
 7477            if is_entire_line {
 7478                start = Point::new(start.row, 0);"
 7479                .to_string()
 7480        ),
 7481        "When selecting past the indent, the copying works as usual",
 7482    );
 7483    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7484    assert_eq!(
 7485        cx.read_from_clipboard()
 7486            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7487        Some(
 7488            "in selections.iter() {
 7489            let mut start = selection.start;
 7490            let mut end = selection.end;
 7491            let is_entire_line = selection.is_empty();
 7492            if is_entire_line {
 7493                start = Point::new(start.row, 0);"
 7494                .to_string()
 7495        ),
 7496        "When selecting past the indent, nothing is trimmed"
 7497    );
 7498
 7499    cx.set_state(
 7500        r#"            «for selection in selections.iter() {
 7501            let mut start = selection.start;
 7502
 7503            let mut end = selection.end;
 7504            let is_entire_line = selection.is_empty();
 7505            if is_entire_line {
 7506                start = Point::new(start.row, 0);
 7507ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7508            }
 7509        "#,
 7510    );
 7511    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7512    assert_eq!(
 7513        cx.read_from_clipboard()
 7514            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7515        Some(
 7516            "for selection in selections.iter() {
 7517let mut start = selection.start;
 7518
 7519let mut end = selection.end;
 7520let is_entire_line = selection.is_empty();
 7521if is_entire_line {
 7522    start = Point::new(start.row, 0);
 7523"
 7524            .to_string()
 7525        ),
 7526        "Copying with stripping should ignore empty lines"
 7527    );
 7528}
 7529
 7530#[gpui::test]
 7531async fn test_paste_multiline(cx: &mut TestAppContext) {
 7532    init_test(cx, |_| {});
 7533
 7534    let mut cx = EditorTestContext::new(cx).await;
 7535    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7536
 7537    // Cut an indented block, without the leading whitespace.
 7538    cx.set_state(indoc! {"
 7539        const a: B = (
 7540            c(),
 7541            «d(
 7542                e,
 7543                f
 7544            )ˇ»
 7545        );
 7546    "});
 7547    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7548    cx.assert_editor_state(indoc! {"
 7549        const a: B = (
 7550            c(),
 7551            ˇ
 7552        );
 7553    "});
 7554
 7555    // Paste it at the same position.
 7556    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7557    cx.assert_editor_state(indoc! {"
 7558        const a: B = (
 7559            c(),
 7560            d(
 7561                e,
 7562                f
 7563 7564        );
 7565    "});
 7566
 7567    // Paste it at a line with a lower indent level.
 7568    cx.set_state(indoc! {"
 7569        ˇ
 7570        const a: B = (
 7571            c(),
 7572        );
 7573    "});
 7574    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7575    cx.assert_editor_state(indoc! {"
 7576        d(
 7577            e,
 7578            f
 7579 7580        const a: B = (
 7581            c(),
 7582        );
 7583    "});
 7584
 7585    // Cut an indented block, with the leading whitespace.
 7586    cx.set_state(indoc! {"
 7587        const a: B = (
 7588            c(),
 7589        «    d(
 7590                e,
 7591                f
 7592            )
 7593        ˇ»);
 7594    "});
 7595    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7596    cx.assert_editor_state(indoc! {"
 7597        const a: B = (
 7598            c(),
 7599        ˇ);
 7600    "});
 7601
 7602    // Paste it at the same position.
 7603    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7604    cx.assert_editor_state(indoc! {"
 7605        const a: B = (
 7606            c(),
 7607            d(
 7608                e,
 7609                f
 7610            )
 7611        ˇ);
 7612    "});
 7613
 7614    // Paste it at a line with a higher indent level.
 7615    cx.set_state(indoc! {"
 7616        const a: B = (
 7617            c(),
 7618            d(
 7619                e,
 7620 7621            )
 7622        );
 7623    "});
 7624    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7625    cx.assert_editor_state(indoc! {"
 7626        const a: B = (
 7627            c(),
 7628            d(
 7629                e,
 7630                f    d(
 7631                    e,
 7632                    f
 7633                )
 7634        ˇ
 7635            )
 7636        );
 7637    "});
 7638
 7639    // Copy an indented block, starting mid-line
 7640    cx.set_state(indoc! {"
 7641        const a: B = (
 7642            c(),
 7643            somethin«g(
 7644                e,
 7645                f
 7646            )ˇ»
 7647        );
 7648    "});
 7649    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7650
 7651    // Paste it on a line with a lower indent level
 7652    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7653    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7654    cx.assert_editor_state(indoc! {"
 7655        const a: B = (
 7656            c(),
 7657            something(
 7658                e,
 7659                f
 7660            )
 7661        );
 7662        g(
 7663            e,
 7664            f
 7665"});
 7666}
 7667
 7668#[gpui::test]
 7669async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7670    init_test(cx, |_| {});
 7671
 7672    cx.write_to_clipboard(ClipboardItem::new_string(
 7673        "    d(\n        e\n    );\n".into(),
 7674    ));
 7675
 7676    let mut cx = EditorTestContext::new(cx).await;
 7677    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7678
 7679    cx.set_state(indoc! {"
 7680        fn a() {
 7681            b();
 7682            if c() {
 7683                ˇ
 7684            }
 7685        }
 7686    "});
 7687
 7688    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7689    cx.assert_editor_state(indoc! {"
 7690        fn a() {
 7691            b();
 7692            if c() {
 7693                d(
 7694                    e
 7695                );
 7696        ˇ
 7697            }
 7698        }
 7699    "});
 7700
 7701    cx.set_state(indoc! {"
 7702        fn a() {
 7703            b();
 7704            ˇ
 7705        }
 7706    "});
 7707
 7708    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7709    cx.assert_editor_state(indoc! {"
 7710        fn a() {
 7711            b();
 7712            d(
 7713                e
 7714            );
 7715        ˇ
 7716        }
 7717    "});
 7718}
 7719
 7720#[gpui::test]
 7721fn test_select_all(cx: &mut TestAppContext) {
 7722    init_test(cx, |_| {});
 7723
 7724    let editor = cx.add_window(|window, cx| {
 7725        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7726        build_editor(buffer, window, cx)
 7727    });
 7728    _ = editor.update(cx, |editor, window, cx| {
 7729        editor.select_all(&SelectAll, window, cx);
 7730        assert_eq!(
 7731            display_ranges(editor, cx),
 7732            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7733        );
 7734    });
 7735}
 7736
 7737#[gpui::test]
 7738fn test_select_line(cx: &mut TestAppContext) {
 7739    init_test(cx, |_| {});
 7740
 7741    let editor = cx.add_window(|window, cx| {
 7742        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7743        build_editor(buffer, window, cx)
 7744    });
 7745    _ = editor.update(cx, |editor, window, cx| {
 7746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7747            s.select_display_ranges([
 7748                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7749                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7750                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7751                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7752            ])
 7753        });
 7754        editor.select_line(&SelectLine, window, cx);
 7755        // Adjacent line selections should NOT merge (only overlapping ones do)
 7756        assert_eq!(
 7757            display_ranges(editor, cx),
 7758            vec![
 7759                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7760                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7761                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7762            ]
 7763        );
 7764    });
 7765
 7766    _ = editor.update(cx, |editor, window, cx| {
 7767        editor.select_line(&SelectLine, window, cx);
 7768        assert_eq!(
 7769            display_ranges(editor, cx),
 7770            vec![
 7771                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7772                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7773            ]
 7774        );
 7775    });
 7776
 7777    _ = editor.update(cx, |editor, window, cx| {
 7778        editor.select_line(&SelectLine, window, cx);
 7779        // Adjacent but not overlapping, so they stay separate
 7780        assert_eq!(
 7781            display_ranges(editor, cx),
 7782            vec![
 7783                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7784                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7785            ]
 7786        );
 7787    });
 7788}
 7789
 7790#[gpui::test]
 7791async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7792    init_test(cx, |_| {});
 7793    let mut cx = EditorTestContext::new(cx).await;
 7794
 7795    #[track_caller]
 7796    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7797        cx.set_state(initial_state);
 7798        cx.update_editor(|e, window, cx| {
 7799            e.split_selection_into_lines(&Default::default(), window, cx)
 7800        });
 7801        cx.assert_editor_state(expected_state);
 7802    }
 7803
 7804    // Selection starts and ends at the middle of lines, left-to-right
 7805    test(
 7806        &mut cx,
 7807        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7808        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7809    );
 7810    // Same thing, right-to-left
 7811    test(
 7812        &mut cx,
 7813        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7814        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7815    );
 7816
 7817    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7818    test(
 7819        &mut cx,
 7820        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7821        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7822    );
 7823    // Same thing, right-to-left
 7824    test(
 7825        &mut cx,
 7826        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7827        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7828    );
 7829
 7830    // Whole buffer, left-to-right, last line ends with newline
 7831    test(
 7832        &mut cx,
 7833        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7834        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7835    );
 7836    // Same thing, right-to-left
 7837    test(
 7838        &mut cx,
 7839        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7840        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7841    );
 7842
 7843    // Starts at the end of a line, ends at the start of another
 7844    test(
 7845        &mut cx,
 7846        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7847        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7848    );
 7849}
 7850
 7851#[gpui::test]
 7852async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7853    init_test(cx, |_| {});
 7854
 7855    let editor = cx.add_window(|window, cx| {
 7856        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7857        build_editor(buffer, window, cx)
 7858    });
 7859
 7860    // setup
 7861    _ = editor.update(cx, |editor, window, cx| {
 7862        editor.fold_creases(
 7863            vec![
 7864                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7865                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7866                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7867            ],
 7868            true,
 7869            window,
 7870            cx,
 7871        );
 7872        assert_eq!(
 7873            editor.display_text(cx),
 7874            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7875        );
 7876    });
 7877
 7878    _ = editor.update(cx, |editor, window, cx| {
 7879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7880            s.select_display_ranges([
 7881                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7882                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7883                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7884                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7885            ])
 7886        });
 7887        editor.split_selection_into_lines(&Default::default(), window, cx);
 7888        assert_eq!(
 7889            editor.display_text(cx),
 7890            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7891        );
 7892    });
 7893    EditorTestContext::for_editor(editor, cx)
 7894        .await
 7895        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7896
 7897    _ = editor.update(cx, |editor, window, cx| {
 7898        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7899            s.select_display_ranges([
 7900                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7901            ])
 7902        });
 7903        editor.split_selection_into_lines(&Default::default(), window, cx);
 7904        assert_eq!(
 7905            editor.display_text(cx),
 7906            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7907        );
 7908        assert_eq!(
 7909            display_ranges(editor, cx),
 7910            [
 7911                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7912                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7913                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7914                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7915                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7916                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7917                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7918            ]
 7919        );
 7920    });
 7921    EditorTestContext::for_editor(editor, cx)
 7922        .await
 7923        .assert_editor_state(
 7924            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7925        );
 7926}
 7927
 7928#[gpui::test]
 7929async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7930    init_test(cx, |_| {});
 7931
 7932    let mut cx = EditorTestContext::new(cx).await;
 7933
 7934    cx.set_state(indoc!(
 7935        r#"abc
 7936           defˇghi
 7937
 7938           jk
 7939           nlmo
 7940           "#
 7941    ));
 7942
 7943    cx.update_editor(|editor, window, cx| {
 7944        editor.add_selection_above(&Default::default(), window, cx);
 7945    });
 7946
 7947    cx.assert_editor_state(indoc!(
 7948        r#"abcˇ
 7949           defˇghi
 7950
 7951           jk
 7952           nlmo
 7953           "#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_above(&Default::default(), window, cx);
 7958    });
 7959
 7960    cx.assert_editor_state(indoc!(
 7961        r#"abcˇ
 7962            defˇghi
 7963
 7964            jk
 7965            nlmo
 7966            "#
 7967    ));
 7968
 7969    cx.update_editor(|editor, window, cx| {
 7970        editor.add_selection_below(&Default::default(), window, cx);
 7971    });
 7972
 7973    cx.assert_editor_state(indoc!(
 7974        r#"abc
 7975           defˇghi
 7976
 7977           jk
 7978           nlmo
 7979           "#
 7980    ));
 7981
 7982    cx.update_editor(|editor, window, cx| {
 7983        editor.undo_selection(&Default::default(), window, cx);
 7984    });
 7985
 7986    cx.assert_editor_state(indoc!(
 7987        r#"abcˇ
 7988           defˇghi
 7989
 7990           jk
 7991           nlmo
 7992           "#
 7993    ));
 7994
 7995    cx.update_editor(|editor, window, cx| {
 7996        editor.redo_selection(&Default::default(), window, cx);
 7997    });
 7998
 7999    cx.assert_editor_state(indoc!(
 8000        r#"abc
 8001           defˇghi
 8002
 8003           jk
 8004           nlmo
 8005           "#
 8006    ));
 8007
 8008    cx.update_editor(|editor, window, cx| {
 8009        editor.add_selection_below(&Default::default(), window, cx);
 8010    });
 8011
 8012    cx.assert_editor_state(indoc!(
 8013        r#"abc
 8014           defˇghi
 8015           ˇ
 8016           jk
 8017           nlmo
 8018           "#
 8019    ));
 8020
 8021    cx.update_editor(|editor, window, cx| {
 8022        editor.add_selection_below(&Default::default(), window, cx);
 8023    });
 8024
 8025    cx.assert_editor_state(indoc!(
 8026        r#"abc
 8027           defˇghi
 8028           ˇ
 8029           jkˇ
 8030           nlmo
 8031           "#
 8032    ));
 8033
 8034    cx.update_editor(|editor, window, cx| {
 8035        editor.add_selection_below(&Default::default(), window, cx);
 8036    });
 8037
 8038    cx.assert_editor_state(indoc!(
 8039        r#"abc
 8040           defˇghi
 8041           ˇ
 8042           jkˇ
 8043           nlmˇo
 8044           "#
 8045    ));
 8046
 8047    cx.update_editor(|editor, window, cx| {
 8048        editor.add_selection_below(&Default::default(), window, cx);
 8049    });
 8050
 8051    cx.assert_editor_state(indoc!(
 8052        r#"abc
 8053           defˇghi
 8054           ˇ
 8055           jkˇ
 8056           nlmˇo
 8057           ˇ"#
 8058    ));
 8059
 8060    // change selections
 8061    cx.set_state(indoc!(
 8062        r#"abc
 8063           def«ˇg»hi
 8064
 8065           jk
 8066           nlmo
 8067           "#
 8068    ));
 8069
 8070    cx.update_editor(|editor, window, cx| {
 8071        editor.add_selection_below(&Default::default(), window, cx);
 8072    });
 8073
 8074    cx.assert_editor_state(indoc!(
 8075        r#"abc
 8076           def«ˇg»hi
 8077
 8078           jk
 8079           nlm«ˇo»
 8080           "#
 8081    ));
 8082
 8083    cx.update_editor(|editor, window, cx| {
 8084        editor.add_selection_below(&Default::default(), window, cx);
 8085    });
 8086
 8087    cx.assert_editor_state(indoc!(
 8088        r#"abc
 8089           def«ˇg»hi
 8090
 8091           jk
 8092           nlm«ˇo»
 8093           "#
 8094    ));
 8095
 8096    cx.update_editor(|editor, window, cx| {
 8097        editor.add_selection_above(&Default::default(), window, cx);
 8098    });
 8099
 8100    cx.assert_editor_state(indoc!(
 8101        r#"abc
 8102           def«ˇg»hi
 8103
 8104           jk
 8105           nlmo
 8106           "#
 8107    ));
 8108
 8109    cx.update_editor(|editor, window, cx| {
 8110        editor.add_selection_above(&Default::default(), window, cx);
 8111    });
 8112
 8113    cx.assert_editor_state(indoc!(
 8114        r#"abc
 8115           def«ˇg»hi
 8116
 8117           jk
 8118           nlmo
 8119           "#
 8120    ));
 8121
 8122    // Change selections again
 8123    cx.set_state(indoc!(
 8124        r#"a«bc
 8125           defgˇ»hi
 8126
 8127           jk
 8128           nlmo
 8129           "#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_below(&Default::default(), window, cx);
 8134    });
 8135
 8136    cx.assert_editor_state(indoc!(
 8137        r#"a«bcˇ»
 8138           d«efgˇ»hi
 8139
 8140           j«kˇ»
 8141           nlmo
 8142           "#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.add_selection_below(&Default::default(), window, cx);
 8147    });
 8148    cx.assert_editor_state(indoc!(
 8149        r#"a«bcˇ»
 8150           d«efgˇ»hi
 8151
 8152           j«kˇ»
 8153           n«lmoˇ»
 8154           "#
 8155    ));
 8156    cx.update_editor(|editor, window, cx| {
 8157        editor.add_selection_above(&Default::default(), window, cx);
 8158    });
 8159
 8160    cx.assert_editor_state(indoc!(
 8161        r#"a«bcˇ»
 8162           d«efgˇ»hi
 8163
 8164           j«kˇ»
 8165           nlmo
 8166           "#
 8167    ));
 8168
 8169    // Change selections again
 8170    cx.set_state(indoc!(
 8171        r#"abc
 8172           d«ˇefghi
 8173
 8174           jk
 8175           nlm»o
 8176           "#
 8177    ));
 8178
 8179    cx.update_editor(|editor, window, cx| {
 8180        editor.add_selection_above(&Default::default(), window, cx);
 8181    });
 8182
 8183    cx.assert_editor_state(indoc!(
 8184        r#"a«ˇbc»
 8185           d«ˇef»ghi
 8186
 8187           j«ˇk»
 8188           n«ˇlm»o
 8189           "#
 8190    ));
 8191
 8192    cx.update_editor(|editor, window, cx| {
 8193        editor.add_selection_below(&Default::default(), window, cx);
 8194    });
 8195
 8196    cx.assert_editor_state(indoc!(
 8197        r#"abc
 8198           d«ˇef»ghi
 8199
 8200           j«ˇk»
 8201           n«ˇlm»o
 8202           "#
 8203    ));
 8204}
 8205
 8206#[gpui::test]
 8207async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8208    init_test(cx, |_| {});
 8209    let mut cx = EditorTestContext::new(cx).await;
 8210
 8211    cx.set_state(indoc!(
 8212        r#"line onˇe
 8213           liˇne two
 8214           line three
 8215           line four"#
 8216    ));
 8217
 8218    cx.update_editor(|editor, window, cx| {
 8219        editor.add_selection_below(&Default::default(), window, cx);
 8220    });
 8221
 8222    // test multiple cursors expand in the same direction
 8223    cx.assert_editor_state(indoc!(
 8224        r#"line onˇe
 8225           liˇne twˇo
 8226           liˇne three
 8227           line four"#
 8228    ));
 8229
 8230    cx.update_editor(|editor, window, cx| {
 8231        editor.add_selection_below(&Default::default(), window, cx);
 8232    });
 8233
 8234    cx.update_editor(|editor, window, cx| {
 8235        editor.add_selection_below(&Default::default(), window, cx);
 8236    });
 8237
 8238    // test multiple cursors expand below overflow
 8239    cx.assert_editor_state(indoc!(
 8240        r#"line onˇe
 8241           liˇne twˇo
 8242           liˇne thˇree
 8243           liˇne foˇur"#
 8244    ));
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.add_selection_above(&Default::default(), window, cx);
 8248    });
 8249
 8250    // test multiple cursors retrieves back correctly
 8251    cx.assert_editor_state(indoc!(
 8252        r#"line onˇe
 8253           liˇne twˇo
 8254           liˇne thˇree
 8255           line four"#
 8256    ));
 8257
 8258    cx.update_editor(|editor, window, cx| {
 8259        editor.add_selection_above(&Default::default(), window, cx);
 8260    });
 8261
 8262    cx.update_editor(|editor, window, cx| {
 8263        editor.add_selection_above(&Default::default(), window, cx);
 8264    });
 8265
 8266    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8267    cx.assert_editor_state(indoc!(
 8268        r#"liˇne onˇe
 8269           liˇne two
 8270           line three
 8271           line four"#
 8272    ));
 8273
 8274    cx.update_editor(|editor, window, cx| {
 8275        editor.undo_selection(&Default::default(), window, cx);
 8276    });
 8277
 8278    // test undo
 8279    cx.assert_editor_state(indoc!(
 8280        r#"line onˇe
 8281           liˇne twˇo
 8282           line three
 8283           line four"#
 8284    ));
 8285
 8286    cx.update_editor(|editor, window, cx| {
 8287        editor.redo_selection(&Default::default(), window, cx);
 8288    });
 8289
 8290    // test redo
 8291    cx.assert_editor_state(indoc!(
 8292        r#"liˇne onˇe
 8293           liˇne two
 8294           line three
 8295           line four"#
 8296    ));
 8297
 8298    cx.set_state(indoc!(
 8299        r#"abcd
 8300           ef«ghˇ»
 8301           ijkl
 8302           «mˇ»nop"#
 8303    ));
 8304
 8305    cx.update_editor(|editor, window, cx| {
 8306        editor.add_selection_above(&Default::default(), window, cx);
 8307    });
 8308
 8309    // test multiple selections expand in the same direction
 8310    cx.assert_editor_state(indoc!(
 8311        r#"ab«cdˇ»
 8312           ef«ghˇ»
 8313           «iˇ»jkl
 8314           «mˇ»nop"#
 8315    ));
 8316
 8317    cx.update_editor(|editor, window, cx| {
 8318        editor.add_selection_above(&Default::default(), window, cx);
 8319    });
 8320
 8321    // test multiple selection upward overflow
 8322    cx.assert_editor_state(indoc!(
 8323        r#"ab«cdˇ»
 8324           «eˇ»f«ghˇ»
 8325           «iˇ»jkl
 8326           «mˇ»nop"#
 8327    ));
 8328
 8329    cx.update_editor(|editor, window, cx| {
 8330        editor.add_selection_below(&Default::default(), window, cx);
 8331    });
 8332
 8333    // test multiple selection retrieves back correctly
 8334    cx.assert_editor_state(indoc!(
 8335        r#"abcd
 8336           ef«ghˇ»
 8337           «iˇ»jkl
 8338           «mˇ»nop"#
 8339    ));
 8340
 8341    cx.update_editor(|editor, window, cx| {
 8342        editor.add_selection_below(&Default::default(), window, cx);
 8343    });
 8344
 8345    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8346    cx.assert_editor_state(indoc!(
 8347        r#"abcd
 8348           ef«ghˇ»
 8349           ij«klˇ»
 8350           «mˇ»nop"#
 8351    ));
 8352
 8353    cx.update_editor(|editor, window, cx| {
 8354        editor.undo_selection(&Default::default(), window, cx);
 8355    });
 8356
 8357    // test undo
 8358    cx.assert_editor_state(indoc!(
 8359        r#"abcd
 8360           ef«ghˇ»
 8361           «iˇ»jkl
 8362           «mˇ»nop"#
 8363    ));
 8364
 8365    cx.update_editor(|editor, window, cx| {
 8366        editor.redo_selection(&Default::default(), window, cx);
 8367    });
 8368
 8369    // test redo
 8370    cx.assert_editor_state(indoc!(
 8371        r#"abcd
 8372           ef«ghˇ»
 8373           ij«klˇ»
 8374           «mˇ»nop"#
 8375    ));
 8376}
 8377
 8378#[gpui::test]
 8379async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8380    init_test(cx, |_| {});
 8381    let mut cx = EditorTestContext::new(cx).await;
 8382
 8383    cx.set_state(indoc!(
 8384        r#"line onˇe
 8385           liˇne two
 8386           line three
 8387           line four"#
 8388    ));
 8389
 8390    cx.update_editor(|editor, window, cx| {
 8391        editor.add_selection_below(&Default::default(), window, cx);
 8392        editor.add_selection_below(&Default::default(), window, cx);
 8393        editor.add_selection_below(&Default::default(), window, cx);
 8394    });
 8395
 8396    // initial state with two multi cursor groups
 8397    cx.assert_editor_state(indoc!(
 8398        r#"line onˇe
 8399           liˇne twˇo
 8400           liˇne thˇree
 8401           liˇne foˇur"#
 8402    ));
 8403
 8404    // add single cursor in middle - simulate opt click
 8405    cx.update_editor(|editor, window, cx| {
 8406        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8407        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8408        editor.end_selection(window, cx);
 8409    });
 8410
 8411    cx.assert_editor_state(indoc!(
 8412        r#"line onˇe
 8413           liˇne twˇo
 8414           liˇneˇ thˇree
 8415           liˇne foˇur"#
 8416    ));
 8417
 8418    cx.update_editor(|editor, window, cx| {
 8419        editor.add_selection_above(&Default::default(), window, cx);
 8420    });
 8421
 8422    // test new added selection expands above and existing selection shrinks
 8423    cx.assert_editor_state(indoc!(
 8424        r#"line onˇe
 8425           liˇneˇ twˇo
 8426           liˇneˇ thˇree
 8427           line four"#
 8428    ));
 8429
 8430    cx.update_editor(|editor, window, cx| {
 8431        editor.add_selection_above(&Default::default(), window, cx);
 8432    });
 8433
 8434    // test new added selection expands above and existing selection shrinks
 8435    cx.assert_editor_state(indoc!(
 8436        r#"lineˇ onˇe
 8437           liˇneˇ twˇo
 8438           lineˇ three
 8439           line four"#
 8440    ));
 8441
 8442    // intial state with two selection groups
 8443    cx.set_state(indoc!(
 8444        r#"abcd
 8445           ef«ghˇ»
 8446           ijkl
 8447           «mˇ»nop"#
 8448    ));
 8449
 8450    cx.update_editor(|editor, window, cx| {
 8451        editor.add_selection_above(&Default::default(), window, cx);
 8452        editor.add_selection_above(&Default::default(), window, cx);
 8453    });
 8454
 8455    cx.assert_editor_state(indoc!(
 8456        r#"ab«cdˇ»
 8457           «eˇ»f«ghˇ»
 8458           «iˇ»jkl
 8459           «mˇ»nop"#
 8460    ));
 8461
 8462    // add single selection in middle - simulate opt drag
 8463    cx.update_editor(|editor, window, cx| {
 8464        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8465        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8466        editor.update_selection(
 8467            DisplayPoint::new(DisplayRow(2), 4),
 8468            0,
 8469            gpui::Point::<f32>::default(),
 8470            window,
 8471            cx,
 8472        );
 8473        editor.end_selection(window, cx);
 8474    });
 8475
 8476    cx.assert_editor_state(indoc!(
 8477        r#"ab«cdˇ»
 8478           «eˇ»f«ghˇ»
 8479           «iˇ»jk«lˇ»
 8480           «mˇ»nop"#
 8481    ));
 8482
 8483    cx.update_editor(|editor, window, cx| {
 8484        editor.add_selection_below(&Default::default(), window, cx);
 8485    });
 8486
 8487    // test new added selection expands below, others shrinks from above
 8488    cx.assert_editor_state(indoc!(
 8489        r#"abcd
 8490           ef«ghˇ»
 8491           «iˇ»jk«lˇ»
 8492           «mˇ»no«pˇ»"#
 8493    ));
 8494}
 8495
 8496#[gpui::test]
 8497async fn test_select_next(cx: &mut TestAppContext) {
 8498    init_test(cx, |_| {});
 8499    let mut cx = EditorTestContext::new(cx).await;
 8500
 8501    // Enable case sensitive search.
 8502    update_test_editor_settings(&mut cx, |settings| {
 8503        let mut search_settings = SearchSettingsContent::default();
 8504        search_settings.case_sensitive = Some(true);
 8505        settings.search = Some(search_settings);
 8506    });
 8507
 8508    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8509
 8510    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8511        .unwrap();
 8512    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8513
 8514    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8515        .unwrap();
 8516    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8517
 8518    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8519    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8520
 8521    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8522    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8523
 8524    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8525        .unwrap();
 8526    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8527
 8528    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8529        .unwrap();
 8530    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8531
 8532    // Test selection direction should be preserved
 8533    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8534
 8535    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8536        .unwrap();
 8537    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8538
 8539    // Test case sensitivity
 8540    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8541    cx.update_editor(|e, window, cx| {
 8542        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8543    });
 8544    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8545
 8546    // Disable case sensitive search.
 8547    update_test_editor_settings(&mut cx, |settings| {
 8548        let mut search_settings = SearchSettingsContent::default();
 8549        search_settings.case_sensitive = Some(false);
 8550        settings.search = Some(search_settings);
 8551    });
 8552
 8553    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8554    cx.update_editor(|e, window, cx| {
 8555        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8556        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8557    });
 8558    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8559}
 8560
 8561#[gpui::test]
 8562async fn test_select_all_matches(cx: &mut TestAppContext) {
 8563    init_test(cx, |_| {});
 8564    let mut cx = EditorTestContext::new(cx).await;
 8565
 8566    // Enable case sensitive search.
 8567    update_test_editor_settings(&mut cx, |settings| {
 8568        let mut search_settings = SearchSettingsContent::default();
 8569        search_settings.case_sensitive = Some(true);
 8570        settings.search = Some(search_settings);
 8571    });
 8572
 8573    // Test caret-only selections
 8574    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8575    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8576        .unwrap();
 8577    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8578
 8579    // Test left-to-right selections
 8580    cx.set_state("abc\n«abcˇ»\nabc");
 8581    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8582        .unwrap();
 8583    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8584
 8585    // Test right-to-left selections
 8586    cx.set_state("abc\n«ˇabc»\nabc");
 8587    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8588        .unwrap();
 8589    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8590
 8591    // Test selecting whitespace with caret selection
 8592    cx.set_state("abc\nˇ   abc\nabc");
 8593    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8594        .unwrap();
 8595    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8596
 8597    // Test selecting whitespace with left-to-right selection
 8598    cx.set_state("abc\n«ˇ  »abc\nabc");
 8599    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8600        .unwrap();
 8601    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8602
 8603    // Test no matches with right-to-left selection
 8604    cx.set_state("abc\n«  ˇ»abc\nabc");
 8605    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8606        .unwrap();
 8607    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8608
 8609    // Test with a single word and clip_at_line_ends=true (#29823)
 8610    cx.set_state("aˇbc");
 8611    cx.update_editor(|e, window, cx| {
 8612        e.set_clip_at_line_ends(true, cx);
 8613        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8614        e.set_clip_at_line_ends(false, cx);
 8615    });
 8616    cx.assert_editor_state("«abcˇ»");
 8617
 8618    // Test case sensitivity
 8619    cx.set_state("fˇoo\nFOO\nFoo");
 8620    cx.update_editor(|e, window, cx| {
 8621        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8622    });
 8623    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8624
 8625    // Disable case sensitive search.
 8626    update_test_editor_settings(&mut cx, |settings| {
 8627        let mut search_settings = SearchSettingsContent::default();
 8628        search_settings.case_sensitive = Some(false);
 8629        settings.search = Some(search_settings);
 8630    });
 8631
 8632    cx.set_state("fˇoo\nFOO\nFoo");
 8633    cx.update_editor(|e, window, cx| {
 8634        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8635    });
 8636    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8637}
 8638
 8639#[gpui::test]
 8640async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8641    init_test(cx, |_| {});
 8642
 8643    let mut cx = EditorTestContext::new(cx).await;
 8644
 8645    let large_body_1 = "\nd".repeat(200);
 8646    let large_body_2 = "\ne".repeat(200);
 8647
 8648    cx.set_state(&format!(
 8649        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8650    ));
 8651    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8652        let scroll_position = editor.scroll_position(cx);
 8653        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8654        scroll_position
 8655    });
 8656
 8657    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8658        .unwrap();
 8659    cx.assert_editor_state(&format!(
 8660        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8661    ));
 8662    let scroll_position_after_selection =
 8663        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8664    assert_eq!(
 8665        initial_scroll_position, scroll_position_after_selection,
 8666        "Scroll position should not change after selecting all matches"
 8667    );
 8668}
 8669
 8670#[gpui::test]
 8671async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8672    init_test(cx, |_| {});
 8673
 8674    let mut cx = EditorLspTestContext::new_rust(
 8675        lsp::ServerCapabilities {
 8676            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8677            ..Default::default()
 8678        },
 8679        cx,
 8680    )
 8681    .await;
 8682
 8683    cx.set_state(indoc! {"
 8684        line 1
 8685        line 2
 8686        linˇe 3
 8687        line 4
 8688        line 5
 8689    "});
 8690
 8691    // Make an edit
 8692    cx.update_editor(|editor, window, cx| {
 8693        editor.handle_input("X", window, cx);
 8694    });
 8695
 8696    // Move cursor to a different position
 8697    cx.update_editor(|editor, window, cx| {
 8698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8699            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8700        });
 8701    });
 8702
 8703    cx.assert_editor_state(indoc! {"
 8704        line 1
 8705        line 2
 8706        linXe 3
 8707        line 4
 8708        liˇne 5
 8709    "});
 8710
 8711    cx.lsp
 8712        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8713            Ok(Some(vec![lsp::TextEdit::new(
 8714                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8715                "PREFIX ".to_string(),
 8716            )]))
 8717        });
 8718
 8719    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8720        .unwrap()
 8721        .await
 8722        .unwrap();
 8723
 8724    cx.assert_editor_state(indoc! {"
 8725        PREFIX line 1
 8726        line 2
 8727        linXe 3
 8728        line 4
 8729        liˇne 5
 8730    "});
 8731
 8732    // Undo formatting
 8733    cx.update_editor(|editor, window, cx| {
 8734        editor.undo(&Default::default(), window, cx);
 8735    });
 8736
 8737    // Verify cursor moved back to position after edit
 8738    cx.assert_editor_state(indoc! {"
 8739        line 1
 8740        line 2
 8741        linXˇe 3
 8742        line 4
 8743        line 5
 8744    "});
 8745}
 8746
 8747#[gpui::test]
 8748async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8749    init_test(cx, |_| {});
 8750
 8751    let mut cx = EditorTestContext::new(cx).await;
 8752
 8753    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8754    cx.update_editor(|editor, window, cx| {
 8755        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8756    });
 8757
 8758    cx.set_state(indoc! {"
 8759        line 1
 8760        line 2
 8761        linˇe 3
 8762        line 4
 8763        line 5
 8764        line 6
 8765        line 7
 8766        line 8
 8767        line 9
 8768        line 10
 8769    "});
 8770
 8771    let snapshot = cx.buffer_snapshot();
 8772    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8773
 8774    cx.update(|_, cx| {
 8775        provider.update(cx, |provider, _| {
 8776            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8777                id: None,
 8778                edits: vec![(edit_position..edit_position, "X".into())],
 8779                edit_preview: None,
 8780            }))
 8781        })
 8782    });
 8783
 8784    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8785    cx.update_editor(|editor, window, cx| {
 8786        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8787    });
 8788
 8789    cx.assert_editor_state(indoc! {"
 8790        line 1
 8791        line 2
 8792        lineXˇ 3
 8793        line 4
 8794        line 5
 8795        line 6
 8796        line 7
 8797        line 8
 8798        line 9
 8799        line 10
 8800    "});
 8801
 8802    cx.update_editor(|editor, window, cx| {
 8803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8804            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8805        });
 8806    });
 8807
 8808    cx.assert_editor_state(indoc! {"
 8809        line 1
 8810        line 2
 8811        lineX 3
 8812        line 4
 8813        line 5
 8814        line 6
 8815        line 7
 8816        line 8
 8817        line 9
 8818        liˇne 10
 8819    "});
 8820
 8821    cx.update_editor(|editor, window, cx| {
 8822        editor.undo(&Default::default(), window, cx);
 8823    });
 8824
 8825    cx.assert_editor_state(indoc! {"
 8826        line 1
 8827        line 2
 8828        lineˇ 3
 8829        line 4
 8830        line 5
 8831        line 6
 8832        line 7
 8833        line 8
 8834        line 9
 8835        line 10
 8836    "});
 8837}
 8838
 8839#[gpui::test]
 8840async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8841    init_test(cx, |_| {});
 8842
 8843    let mut cx = EditorTestContext::new(cx).await;
 8844    cx.set_state(
 8845        r#"let foo = 2;
 8846lˇet foo = 2;
 8847let fooˇ = 2;
 8848let foo = 2;
 8849let foo = ˇ2;"#,
 8850    );
 8851
 8852    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8853        .unwrap();
 8854    cx.assert_editor_state(
 8855        r#"let foo = 2;
 8856«letˇ» foo = 2;
 8857let «fooˇ» = 2;
 8858let foo = 2;
 8859let foo = «2ˇ»;"#,
 8860    );
 8861
 8862    // noop for multiple selections with different contents
 8863    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8864        .unwrap();
 8865    cx.assert_editor_state(
 8866        r#"let foo = 2;
 8867«letˇ» foo = 2;
 8868let «fooˇ» = 2;
 8869let foo = 2;
 8870let foo = «2ˇ»;"#,
 8871    );
 8872
 8873    // Test last selection direction should be preserved
 8874    cx.set_state(
 8875        r#"let foo = 2;
 8876let foo = 2;
 8877let «fooˇ» = 2;
 8878let «ˇfoo» = 2;
 8879let foo = 2;"#,
 8880    );
 8881
 8882    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8883        .unwrap();
 8884    cx.assert_editor_state(
 8885        r#"let foo = 2;
 8886let foo = 2;
 8887let «fooˇ» = 2;
 8888let «ˇfoo» = 2;
 8889let «ˇfoo» = 2;"#,
 8890    );
 8891}
 8892
 8893#[gpui::test]
 8894async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8895    init_test(cx, |_| {});
 8896
 8897    let mut cx =
 8898        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8899
 8900    cx.assert_editor_state(indoc! {"
 8901        ˇbbb
 8902        ccc
 8903
 8904        bbb
 8905        ccc
 8906        "});
 8907    cx.dispatch_action(SelectPrevious::default());
 8908    cx.assert_editor_state(indoc! {"
 8909                «bbbˇ»
 8910                ccc
 8911
 8912                bbb
 8913                ccc
 8914                "});
 8915    cx.dispatch_action(SelectPrevious::default());
 8916    cx.assert_editor_state(indoc! {"
 8917                «bbbˇ»
 8918                ccc
 8919
 8920                «bbbˇ»
 8921                ccc
 8922                "});
 8923}
 8924
 8925#[gpui::test]
 8926async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8927    init_test(cx, |_| {});
 8928
 8929    let mut cx = EditorTestContext::new(cx).await;
 8930    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8931
 8932    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8933        .unwrap();
 8934    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8935
 8936    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8937        .unwrap();
 8938    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8939
 8940    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8941    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8942
 8943    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8944    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8945
 8946    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8947        .unwrap();
 8948    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8949
 8950    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8951        .unwrap();
 8952    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8957    init_test(cx, |_| {});
 8958
 8959    let mut cx = EditorTestContext::new(cx).await;
 8960    cx.set_state("");
 8961
 8962    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8963        .unwrap();
 8964    cx.assert_editor_state("«aˇ»");
 8965    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8966        .unwrap();
 8967    cx.assert_editor_state("«aˇ»");
 8968}
 8969
 8970#[gpui::test]
 8971async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8972    init_test(cx, |_| {});
 8973
 8974    let mut cx = EditorTestContext::new(cx).await;
 8975    cx.set_state(
 8976        r#"let foo = 2;
 8977lˇet foo = 2;
 8978let fooˇ = 2;
 8979let foo = 2;
 8980let foo = ˇ2;"#,
 8981    );
 8982
 8983    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8984        .unwrap();
 8985    cx.assert_editor_state(
 8986        r#"let foo = 2;
 8987«letˇ» foo = 2;
 8988let «fooˇ» = 2;
 8989let foo = 2;
 8990let foo = «2ˇ»;"#,
 8991    );
 8992
 8993    // noop for multiple selections with different contents
 8994    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8995        .unwrap();
 8996    cx.assert_editor_state(
 8997        r#"let foo = 2;
 8998«letˇ» foo = 2;
 8999let «fooˇ» = 2;
 9000let foo = 2;
 9001let foo = «2ˇ»;"#,
 9002    );
 9003}
 9004
 9005#[gpui::test]
 9006async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9007    init_test(cx, |_| {});
 9008    let mut cx = EditorTestContext::new(cx).await;
 9009
 9010    // Enable case sensitive search.
 9011    update_test_editor_settings(&mut cx, |settings| {
 9012        let mut search_settings = SearchSettingsContent::default();
 9013        search_settings.case_sensitive = Some(true);
 9014        settings.search = Some(search_settings);
 9015    });
 9016
 9017    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9018
 9019    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9020        .unwrap();
 9021    // selection direction is preserved
 9022    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9023
 9024    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9025        .unwrap();
 9026    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9027
 9028    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9029    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9030
 9031    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9032    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9033
 9034    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9035        .unwrap();
 9036    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9037
 9038    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9039        .unwrap();
 9040    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9041
 9042    // Test case sensitivity
 9043    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9044    cx.update_editor(|e, window, cx| {
 9045        e.select_previous(&SelectPrevious::default(), window, cx)
 9046            .unwrap();
 9047        e.select_previous(&SelectPrevious::default(), window, cx)
 9048            .unwrap();
 9049    });
 9050    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9051
 9052    // Disable case sensitive search.
 9053    update_test_editor_settings(&mut cx, |settings| {
 9054        let mut search_settings = SearchSettingsContent::default();
 9055        search_settings.case_sensitive = Some(false);
 9056        settings.search = Some(search_settings);
 9057    });
 9058
 9059    cx.set_state("foo\nFOO\n«ˇFoo»");
 9060    cx.update_editor(|e, window, cx| {
 9061        e.select_previous(&SelectPrevious::default(), window, cx)
 9062            .unwrap();
 9063        e.select_previous(&SelectPrevious::default(), window, cx)
 9064            .unwrap();
 9065    });
 9066    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9067}
 9068
 9069#[gpui::test]
 9070async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9071    init_test(cx, |_| {});
 9072
 9073    let language = Arc::new(Language::new(
 9074        LanguageConfig::default(),
 9075        Some(tree_sitter_rust::LANGUAGE.into()),
 9076    ));
 9077
 9078    let text = r#"
 9079        use mod1::mod2::{mod3, mod4};
 9080
 9081        fn fn_1(param1: bool, param2: &str) {
 9082            let var1 = "text";
 9083        }
 9084    "#
 9085    .unindent();
 9086
 9087    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9088    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9089    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9090
 9091    editor
 9092        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9093        .await;
 9094
 9095    editor.update_in(cx, |editor, window, cx| {
 9096        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9097            s.select_display_ranges([
 9098                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9099                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9100                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9101            ]);
 9102        });
 9103        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9104    });
 9105    editor.update(cx, |editor, cx| {
 9106        assert_text_with_selections(
 9107            editor,
 9108            indoc! {r#"
 9109                use mod1::mod2::{mod3, «mod4ˇ»};
 9110
 9111                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9112                    let var1 = "«ˇtext»";
 9113                }
 9114            "#},
 9115            cx,
 9116        );
 9117    });
 9118
 9119    editor.update_in(cx, |editor, window, cx| {
 9120        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9121    });
 9122    editor.update(cx, |editor, cx| {
 9123        assert_text_with_selections(
 9124            editor,
 9125            indoc! {r#"
 9126                use mod1::mod2::«{mod3, mod4}ˇ»;
 9127
 9128                «ˇfn fn_1(param1: bool, param2: &str) {
 9129                    let var1 = "text";
 9130 9131            "#},
 9132            cx,
 9133        );
 9134    });
 9135
 9136    editor.update_in(cx, |editor, window, cx| {
 9137        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9138    });
 9139    assert_eq!(
 9140        editor.update(cx, |editor, cx| editor
 9141            .selections
 9142            .display_ranges(&editor.display_snapshot(cx))),
 9143        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9144    );
 9145
 9146    // Trying to expand the selected syntax node one more time has no effect.
 9147    editor.update_in(cx, |editor, window, cx| {
 9148        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9149    });
 9150    assert_eq!(
 9151        editor.update(cx, |editor, cx| editor
 9152            .selections
 9153            .display_ranges(&editor.display_snapshot(cx))),
 9154        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9155    );
 9156
 9157    editor.update_in(cx, |editor, window, cx| {
 9158        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9159    });
 9160    editor.update(cx, |editor, cx| {
 9161        assert_text_with_selections(
 9162            editor,
 9163            indoc! {r#"
 9164                use mod1::mod2::«{mod3, mod4}ˇ»;
 9165
 9166                «ˇfn fn_1(param1: bool, param2: &str) {
 9167                    let var1 = "text";
 9168 9169            "#},
 9170            cx,
 9171        );
 9172    });
 9173
 9174    editor.update_in(cx, |editor, window, cx| {
 9175        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9176    });
 9177    editor.update(cx, |editor, cx| {
 9178        assert_text_with_selections(
 9179            editor,
 9180            indoc! {r#"
 9181                use mod1::mod2::{mod3, «mod4ˇ»};
 9182
 9183                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9184                    let var1 = "«ˇtext»";
 9185                }
 9186            "#},
 9187            cx,
 9188        );
 9189    });
 9190
 9191    editor.update_in(cx, |editor, window, cx| {
 9192        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9193    });
 9194    editor.update(cx, |editor, cx| {
 9195        assert_text_with_selections(
 9196            editor,
 9197            indoc! {r#"
 9198                use mod1::mod2::{mod3, moˇd4};
 9199
 9200                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9201                    let var1 = "teˇxt";
 9202                }
 9203            "#},
 9204            cx,
 9205        );
 9206    });
 9207
 9208    // Trying to shrink the selected syntax node one more time has no effect.
 9209    editor.update_in(cx, |editor, window, cx| {
 9210        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9211    });
 9212    editor.update_in(cx, |editor, _, cx| {
 9213        assert_text_with_selections(
 9214            editor,
 9215            indoc! {r#"
 9216                use mod1::mod2::{mod3, moˇd4};
 9217
 9218                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9219                    let var1 = "teˇxt";
 9220                }
 9221            "#},
 9222            cx,
 9223        );
 9224    });
 9225
 9226    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9227    // a fold.
 9228    editor.update_in(cx, |editor, window, cx| {
 9229        editor.fold_creases(
 9230            vec![
 9231                Crease::simple(
 9232                    Point::new(0, 21)..Point::new(0, 24),
 9233                    FoldPlaceholder::test(),
 9234                ),
 9235                Crease::simple(
 9236                    Point::new(3, 20)..Point::new(3, 22),
 9237                    FoldPlaceholder::test(),
 9238                ),
 9239            ],
 9240            true,
 9241            window,
 9242            cx,
 9243        );
 9244        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9245    });
 9246    editor.update(cx, |editor, cx| {
 9247        assert_text_with_selections(
 9248            editor,
 9249            indoc! {r#"
 9250                use mod1::mod2::«{mod3, mod4}ˇ»;
 9251
 9252                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9253                    let var1 = "«ˇtext»";
 9254                }
 9255            "#},
 9256            cx,
 9257        );
 9258    });
 9259}
 9260
 9261#[gpui::test]
 9262async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9263    init_test(cx, |_| {});
 9264
 9265    let language = Arc::new(Language::new(
 9266        LanguageConfig::default(),
 9267        Some(tree_sitter_rust::LANGUAGE.into()),
 9268    ));
 9269
 9270    let text = "let a = 2;";
 9271
 9272    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9273    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9274    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9275
 9276    editor
 9277        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9278        .await;
 9279
 9280    // Test case 1: Cursor at end of word
 9281    editor.update_in(cx, |editor, window, cx| {
 9282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9283            s.select_display_ranges([
 9284                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9285            ]);
 9286        });
 9287    });
 9288    editor.update(cx, |editor, cx| {
 9289        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9290    });
 9291    editor.update_in(cx, |editor, window, cx| {
 9292        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9293    });
 9294    editor.update(cx, |editor, cx| {
 9295        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9296    });
 9297    editor.update_in(cx, |editor, window, cx| {
 9298        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9299    });
 9300    editor.update(cx, |editor, cx| {
 9301        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9302    });
 9303
 9304    // Test case 2: Cursor at end of statement
 9305    editor.update_in(cx, |editor, window, cx| {
 9306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9307            s.select_display_ranges([
 9308                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9309            ]);
 9310        });
 9311    });
 9312    editor.update(cx, |editor, cx| {
 9313        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9314    });
 9315    editor.update_in(cx, |editor, window, cx| {
 9316        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9317    });
 9318    editor.update(cx, |editor, cx| {
 9319        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9320    });
 9321}
 9322
 9323#[gpui::test]
 9324async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9325    init_test(cx, |_| {});
 9326
 9327    let language = Arc::new(Language::new(
 9328        LanguageConfig {
 9329            name: "JavaScript".into(),
 9330            ..Default::default()
 9331        },
 9332        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9333    ));
 9334
 9335    let text = r#"
 9336        let a = {
 9337            key: "value",
 9338        };
 9339    "#
 9340    .unindent();
 9341
 9342    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9343    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9344    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9345
 9346    editor
 9347        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9348        .await;
 9349
 9350    // Test case 1: Cursor after '{'
 9351    editor.update_in(cx, |editor, window, cx| {
 9352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9353            s.select_display_ranges([
 9354                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9355            ]);
 9356        });
 9357    });
 9358    editor.update(cx, |editor, cx| {
 9359        assert_text_with_selections(
 9360            editor,
 9361            indoc! {r#"
 9362                let a = {ˇ
 9363                    key: "value",
 9364                };
 9365            "#},
 9366            cx,
 9367        );
 9368    });
 9369    editor.update_in(cx, |editor, window, cx| {
 9370        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9371    });
 9372    editor.update(cx, |editor, cx| {
 9373        assert_text_with_selections(
 9374            editor,
 9375            indoc! {r#"
 9376                let a = «ˇ{
 9377                    key: "value",
 9378                }»;
 9379            "#},
 9380            cx,
 9381        );
 9382    });
 9383
 9384    // Test case 2: Cursor after ':'
 9385    editor.update_in(cx, |editor, window, cx| {
 9386        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9387            s.select_display_ranges([
 9388                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9389            ]);
 9390        });
 9391    });
 9392    editor.update(cx, |editor, cx| {
 9393        assert_text_with_selections(
 9394            editor,
 9395            indoc! {r#"
 9396                let a = {
 9397                    key:ˇ "value",
 9398                };
 9399            "#},
 9400            cx,
 9401        );
 9402    });
 9403    editor.update_in(cx, |editor, window, cx| {
 9404        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9405    });
 9406    editor.update(cx, |editor, cx| {
 9407        assert_text_with_selections(
 9408            editor,
 9409            indoc! {r#"
 9410                let a = {
 9411                    «ˇkey: "value"»,
 9412                };
 9413            "#},
 9414            cx,
 9415        );
 9416    });
 9417    editor.update_in(cx, |editor, window, cx| {
 9418        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9419    });
 9420    editor.update(cx, |editor, cx| {
 9421        assert_text_with_selections(
 9422            editor,
 9423            indoc! {r#"
 9424                let a = «ˇ{
 9425                    key: "value",
 9426                }»;
 9427            "#},
 9428            cx,
 9429        );
 9430    });
 9431
 9432    // Test case 3: Cursor after ','
 9433    editor.update_in(cx, |editor, window, cx| {
 9434        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9435            s.select_display_ranges([
 9436                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9437            ]);
 9438        });
 9439    });
 9440    editor.update(cx, |editor, cx| {
 9441        assert_text_with_selections(
 9442            editor,
 9443            indoc! {r#"
 9444                let a = {
 9445                    key: "value",ˇ
 9446                };
 9447            "#},
 9448            cx,
 9449        );
 9450    });
 9451    editor.update_in(cx, |editor, window, cx| {
 9452        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9453    });
 9454    editor.update(cx, |editor, cx| {
 9455        assert_text_with_selections(
 9456            editor,
 9457            indoc! {r#"
 9458                let a = «ˇ{
 9459                    key: "value",
 9460                }»;
 9461            "#},
 9462            cx,
 9463        );
 9464    });
 9465
 9466    // Test case 4: Cursor after ';'
 9467    editor.update_in(cx, |editor, window, cx| {
 9468        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9469            s.select_display_ranges([
 9470                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9471            ]);
 9472        });
 9473    });
 9474    editor.update(cx, |editor, cx| {
 9475        assert_text_with_selections(
 9476            editor,
 9477            indoc! {r#"
 9478                let a = {
 9479                    key: "value",
 9480                };ˇ
 9481            "#},
 9482            cx,
 9483        );
 9484    });
 9485    editor.update_in(cx, |editor, window, cx| {
 9486        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9487    });
 9488    editor.update(cx, |editor, cx| {
 9489        assert_text_with_selections(
 9490            editor,
 9491            indoc! {r#"
 9492                «ˇlet a = {
 9493                    key: "value",
 9494                };
 9495                »"#},
 9496            cx,
 9497        );
 9498    });
 9499}
 9500
 9501#[gpui::test]
 9502async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9503    init_test(cx, |_| {});
 9504
 9505    let language = Arc::new(Language::new(
 9506        LanguageConfig::default(),
 9507        Some(tree_sitter_rust::LANGUAGE.into()),
 9508    ));
 9509
 9510    let text = r#"
 9511        use mod1::mod2::{mod3, mod4};
 9512
 9513        fn fn_1(param1: bool, param2: &str) {
 9514            let var1 = "hello world";
 9515        }
 9516    "#
 9517    .unindent();
 9518
 9519    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9520    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9521    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9522
 9523    editor
 9524        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9525        .await;
 9526
 9527    // Test 1: Cursor on a letter of a string word
 9528    editor.update_in(cx, |editor, window, cx| {
 9529        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9530            s.select_display_ranges([
 9531                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9532            ]);
 9533        });
 9534    });
 9535    editor.update_in(cx, |editor, window, cx| {
 9536        assert_text_with_selections(
 9537            editor,
 9538            indoc! {r#"
 9539                use mod1::mod2::{mod3, mod4};
 9540
 9541                fn fn_1(param1: bool, param2: &str) {
 9542                    let var1 = "hˇello world";
 9543                }
 9544            "#},
 9545            cx,
 9546        );
 9547        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9548        assert_text_with_selections(
 9549            editor,
 9550            indoc! {r#"
 9551                use mod1::mod2::{mod3, mod4};
 9552
 9553                fn fn_1(param1: bool, param2: &str) {
 9554                    let var1 = "«ˇhello» world";
 9555                }
 9556            "#},
 9557            cx,
 9558        );
 9559    });
 9560
 9561    // Test 2: Partial selection within a word
 9562    editor.update_in(cx, |editor, window, cx| {
 9563        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9564            s.select_display_ranges([
 9565                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9566            ]);
 9567        });
 9568    });
 9569    editor.update_in(cx, |editor, window, cx| {
 9570        assert_text_with_selections(
 9571            editor,
 9572            indoc! {r#"
 9573                use mod1::mod2::{mod3, mod4};
 9574
 9575                fn fn_1(param1: bool, param2: &str) {
 9576                    let var1 = "h«elˇ»lo world";
 9577                }
 9578            "#},
 9579            cx,
 9580        );
 9581        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9582        assert_text_with_selections(
 9583            editor,
 9584            indoc! {r#"
 9585                use mod1::mod2::{mod3, mod4};
 9586
 9587                fn fn_1(param1: bool, param2: &str) {
 9588                    let var1 = "«ˇhello» world";
 9589                }
 9590            "#},
 9591            cx,
 9592        );
 9593    });
 9594
 9595    // Test 3: Complete word already selected
 9596    editor.update_in(cx, |editor, window, cx| {
 9597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9598            s.select_display_ranges([
 9599                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9600            ]);
 9601        });
 9602    });
 9603    editor.update_in(cx, |editor, window, cx| {
 9604        assert_text_with_selections(
 9605            editor,
 9606            indoc! {r#"
 9607                use mod1::mod2::{mod3, mod4};
 9608
 9609                fn fn_1(param1: bool, param2: &str) {
 9610                    let var1 = "«helloˇ» world";
 9611                }
 9612            "#},
 9613            cx,
 9614        );
 9615        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9616        assert_text_with_selections(
 9617            editor,
 9618            indoc! {r#"
 9619                use mod1::mod2::{mod3, mod4};
 9620
 9621                fn fn_1(param1: bool, param2: &str) {
 9622                    let var1 = "«hello worldˇ»";
 9623                }
 9624            "#},
 9625            cx,
 9626        );
 9627    });
 9628
 9629    // Test 4: Selection spanning across words
 9630    editor.update_in(cx, |editor, window, cx| {
 9631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9632            s.select_display_ranges([
 9633                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9634            ]);
 9635        });
 9636    });
 9637    editor.update_in(cx, |editor, window, cx| {
 9638        assert_text_with_selections(
 9639            editor,
 9640            indoc! {r#"
 9641                use mod1::mod2::{mod3, mod4};
 9642
 9643                fn fn_1(param1: bool, param2: &str) {
 9644                    let var1 = "hel«lo woˇ»rld";
 9645                }
 9646            "#},
 9647            cx,
 9648        );
 9649        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9650        assert_text_with_selections(
 9651            editor,
 9652            indoc! {r#"
 9653                use mod1::mod2::{mod3, mod4};
 9654
 9655                fn fn_1(param1: bool, param2: &str) {
 9656                    let var1 = "«ˇhello world»";
 9657                }
 9658            "#},
 9659            cx,
 9660        );
 9661    });
 9662
 9663    // Test 5: Expansion beyond string
 9664    editor.update_in(cx, |editor, window, cx| {
 9665        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9666        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9667        assert_text_with_selections(
 9668            editor,
 9669            indoc! {r#"
 9670                use mod1::mod2::{mod3, mod4};
 9671
 9672                fn fn_1(param1: bool, param2: &str) {
 9673                    «ˇlet var1 = "hello world";»
 9674                }
 9675            "#},
 9676            cx,
 9677        );
 9678    });
 9679}
 9680
 9681#[gpui::test]
 9682async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9683    init_test(cx, |_| {});
 9684
 9685    let mut cx = EditorTestContext::new(cx).await;
 9686
 9687    let language = Arc::new(Language::new(
 9688        LanguageConfig::default(),
 9689        Some(tree_sitter_rust::LANGUAGE.into()),
 9690    ));
 9691
 9692    cx.update_buffer(|buffer, cx| {
 9693        buffer.set_language(Some(language), cx);
 9694    });
 9695
 9696    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9697    cx.update_editor(|editor, window, cx| {
 9698        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9699    });
 9700
 9701    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9702
 9703    cx.set_state(indoc! { r#"fn a() {
 9704          // what
 9705          // a
 9706          // ˇlong
 9707          // method
 9708          // I
 9709          // sure
 9710          // hope
 9711          // it
 9712          // works
 9713    }"# });
 9714
 9715    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9716    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9717    cx.update(|_, cx| {
 9718        multi_buffer.update(cx, |multi_buffer, cx| {
 9719            multi_buffer.set_excerpts_for_path(
 9720                PathKey::for_buffer(&buffer, cx),
 9721                buffer,
 9722                [Point::new(1, 0)..Point::new(1, 0)],
 9723                3,
 9724                cx,
 9725            );
 9726        });
 9727    });
 9728
 9729    let editor2 = cx.new_window_entity(|window, cx| {
 9730        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9731    });
 9732
 9733    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9734    cx.update_editor(|editor, window, cx| {
 9735        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9736            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9737        })
 9738    });
 9739
 9740    cx.assert_editor_state(indoc! { "
 9741        fn a() {
 9742              // what
 9743              // a
 9744        ˇ      // long
 9745              // method"});
 9746
 9747    cx.update_editor(|editor, window, cx| {
 9748        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9749    });
 9750
 9751    // Although we could potentially make the action work when the syntax node
 9752    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9753    // did. Maybe we could also expand the excerpt to contain the range?
 9754    cx.assert_editor_state(indoc! { "
 9755        fn a() {
 9756              // what
 9757              // a
 9758        ˇ      // long
 9759              // method"});
 9760}
 9761
 9762#[gpui::test]
 9763async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9764    init_test(cx, |_| {});
 9765
 9766    let base_text = r#"
 9767        impl A {
 9768            // this is an uncommitted comment
 9769
 9770            fn b() {
 9771                c();
 9772            }
 9773
 9774            // this is another uncommitted comment
 9775
 9776            fn d() {
 9777                // e
 9778                // f
 9779            }
 9780        }
 9781
 9782        fn g() {
 9783            // h
 9784        }
 9785    "#
 9786    .unindent();
 9787
 9788    let text = r#"
 9789        ˇimpl A {
 9790
 9791            fn b() {
 9792                c();
 9793            }
 9794
 9795            fn d() {
 9796                // e
 9797                // f
 9798            }
 9799        }
 9800
 9801        fn g() {
 9802            // h
 9803        }
 9804    "#
 9805    .unindent();
 9806
 9807    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9808    cx.set_state(&text);
 9809    cx.set_head_text(&base_text);
 9810    cx.update_editor(|editor, window, cx| {
 9811        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9812    });
 9813
 9814    cx.assert_state_with_diff(
 9815        "
 9816        ˇimpl A {
 9817      -     // this is an uncommitted comment
 9818
 9819            fn b() {
 9820                c();
 9821            }
 9822
 9823      -     // this is another uncommitted comment
 9824      -
 9825            fn d() {
 9826                // e
 9827                // f
 9828            }
 9829        }
 9830
 9831        fn g() {
 9832            // h
 9833        }
 9834    "
 9835        .unindent(),
 9836    );
 9837
 9838    let expected_display_text = "
 9839        impl A {
 9840            // this is an uncommitted comment
 9841
 9842            fn b() {
 9843 9844            }
 9845
 9846            // this is another uncommitted comment
 9847
 9848            fn d() {
 9849 9850            }
 9851        }
 9852
 9853        fn g() {
 9854 9855        }
 9856        "
 9857    .unindent();
 9858
 9859    cx.update_editor(|editor, window, cx| {
 9860        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9861        assert_eq!(editor.display_text(cx), expected_display_text);
 9862    });
 9863}
 9864
 9865#[gpui::test]
 9866async fn test_autoindent(cx: &mut TestAppContext) {
 9867    init_test(cx, |_| {});
 9868
 9869    let language = Arc::new(
 9870        Language::new(
 9871            LanguageConfig {
 9872                brackets: BracketPairConfig {
 9873                    pairs: vec![
 9874                        BracketPair {
 9875                            start: "{".to_string(),
 9876                            end: "}".to_string(),
 9877                            close: false,
 9878                            surround: false,
 9879                            newline: true,
 9880                        },
 9881                        BracketPair {
 9882                            start: "(".to_string(),
 9883                            end: ")".to_string(),
 9884                            close: false,
 9885                            surround: false,
 9886                            newline: true,
 9887                        },
 9888                    ],
 9889                    ..Default::default()
 9890                },
 9891                ..Default::default()
 9892            },
 9893            Some(tree_sitter_rust::LANGUAGE.into()),
 9894        )
 9895        .with_indents_query(
 9896            r#"
 9897                (_ "(" ")" @end) @indent
 9898                (_ "{" "}" @end) @indent
 9899            "#,
 9900        )
 9901        .unwrap(),
 9902    );
 9903
 9904    let text = "fn a() {}";
 9905
 9906    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9907    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9908    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9909    editor
 9910        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9911        .await;
 9912
 9913    editor.update_in(cx, |editor, window, cx| {
 9914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9915            s.select_ranges([
 9916                MultiBufferOffset(5)..MultiBufferOffset(5),
 9917                MultiBufferOffset(8)..MultiBufferOffset(8),
 9918                MultiBufferOffset(9)..MultiBufferOffset(9),
 9919            ])
 9920        });
 9921        editor.newline(&Newline, window, cx);
 9922        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9923        assert_eq!(
 9924            editor.selections.ranges(&editor.display_snapshot(cx)),
 9925            &[
 9926                Point::new(1, 4)..Point::new(1, 4),
 9927                Point::new(3, 4)..Point::new(3, 4),
 9928                Point::new(5, 0)..Point::new(5, 0)
 9929            ]
 9930        );
 9931    });
 9932}
 9933
 9934#[gpui::test]
 9935async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9936    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9937
 9938    let language = Arc::new(
 9939        Language::new(
 9940            LanguageConfig {
 9941                brackets: BracketPairConfig {
 9942                    pairs: vec![
 9943                        BracketPair {
 9944                            start: "{".to_string(),
 9945                            end: "}".to_string(),
 9946                            close: false,
 9947                            surround: false,
 9948                            newline: true,
 9949                        },
 9950                        BracketPair {
 9951                            start: "(".to_string(),
 9952                            end: ")".to_string(),
 9953                            close: false,
 9954                            surround: false,
 9955                            newline: true,
 9956                        },
 9957                    ],
 9958                    ..Default::default()
 9959                },
 9960                ..Default::default()
 9961            },
 9962            Some(tree_sitter_rust::LANGUAGE.into()),
 9963        )
 9964        .with_indents_query(
 9965            r#"
 9966                (_ "(" ")" @end) @indent
 9967                (_ "{" "}" @end) @indent
 9968            "#,
 9969        )
 9970        .unwrap(),
 9971    );
 9972
 9973    let text = "fn a() {}";
 9974
 9975    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9976    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9977    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9978    editor
 9979        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9980        .await;
 9981
 9982    editor.update_in(cx, |editor, window, cx| {
 9983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9984            s.select_ranges([
 9985                MultiBufferOffset(5)..MultiBufferOffset(5),
 9986                MultiBufferOffset(8)..MultiBufferOffset(8),
 9987                MultiBufferOffset(9)..MultiBufferOffset(9),
 9988            ])
 9989        });
 9990        editor.newline(&Newline, window, cx);
 9991        assert_eq!(
 9992            editor.text(cx),
 9993            indoc!(
 9994                "
 9995                fn a(
 9996
 9997                ) {
 9998
 9999                }
10000                "
10001            )
10002        );
10003        assert_eq!(
10004            editor.selections.ranges(&editor.display_snapshot(cx)),
10005            &[
10006                Point::new(1, 0)..Point::new(1, 0),
10007                Point::new(3, 0)..Point::new(3, 0),
10008                Point::new(5, 0)..Point::new(5, 0)
10009            ]
10010        );
10011    });
10012}
10013
10014#[gpui::test]
10015async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10016    init_test(cx, |settings| {
10017        settings.defaults.auto_indent = Some(true);
10018        settings.languages.0.insert(
10019            "python".into(),
10020            LanguageSettingsContent {
10021                auto_indent: Some(false),
10022                ..Default::default()
10023            },
10024        );
10025    });
10026
10027    let mut cx = EditorTestContext::new(cx).await;
10028
10029    let injected_language = Arc::new(
10030        Language::new(
10031            LanguageConfig {
10032                brackets: BracketPairConfig {
10033                    pairs: vec![
10034                        BracketPair {
10035                            start: "{".to_string(),
10036                            end: "}".to_string(),
10037                            close: false,
10038                            surround: false,
10039                            newline: true,
10040                        },
10041                        BracketPair {
10042                            start: "(".to_string(),
10043                            end: ")".to_string(),
10044                            close: true,
10045                            surround: false,
10046                            newline: true,
10047                        },
10048                    ],
10049                    ..Default::default()
10050                },
10051                name: "python".into(),
10052                ..Default::default()
10053            },
10054            Some(tree_sitter_python::LANGUAGE.into()),
10055        )
10056        .with_indents_query(
10057            r#"
10058                (_ "(" ")" @end) @indent
10059                (_ "{" "}" @end) @indent
10060            "#,
10061        )
10062        .unwrap(),
10063    );
10064
10065    let language = Arc::new(
10066        Language::new(
10067            LanguageConfig {
10068                brackets: BracketPairConfig {
10069                    pairs: vec![
10070                        BracketPair {
10071                            start: "{".to_string(),
10072                            end: "}".to_string(),
10073                            close: false,
10074                            surround: false,
10075                            newline: true,
10076                        },
10077                        BracketPair {
10078                            start: "(".to_string(),
10079                            end: ")".to_string(),
10080                            close: true,
10081                            surround: false,
10082                            newline: true,
10083                        },
10084                    ],
10085                    ..Default::default()
10086                },
10087                name: LanguageName::new_static("rust"),
10088                ..Default::default()
10089            },
10090            Some(tree_sitter_rust::LANGUAGE.into()),
10091        )
10092        .with_indents_query(
10093            r#"
10094                (_ "(" ")" @end) @indent
10095                (_ "{" "}" @end) @indent
10096            "#,
10097        )
10098        .unwrap()
10099        .with_injection_query(
10100            r#"
10101            (macro_invocation
10102                macro: (identifier) @_macro_name
10103                (token_tree) @injection.content
10104                (#set! injection.language "python"))
10105           "#,
10106        )
10107        .unwrap(),
10108    );
10109
10110    cx.language_registry().add(injected_language);
10111    cx.language_registry().add(language.clone());
10112
10113    cx.update_buffer(|buffer, cx| {
10114        buffer.set_language(Some(language), cx);
10115    });
10116
10117    cx.set_state(r#"struct A {ˇ}"#);
10118
10119    cx.update_editor(|editor, window, cx| {
10120        editor.newline(&Default::default(), window, cx);
10121    });
10122
10123    cx.assert_editor_state(indoc!(
10124        "struct A {
10125            ˇ
10126        }"
10127    ));
10128
10129    cx.set_state(r#"select_biased!(ˇ)"#);
10130
10131    cx.update_editor(|editor, window, cx| {
10132        editor.newline(&Default::default(), window, cx);
10133        editor.handle_input("def ", window, cx);
10134        editor.handle_input("(", window, cx);
10135        editor.newline(&Default::default(), window, cx);
10136        editor.handle_input("a", window, cx);
10137    });
10138
10139    cx.assert_editor_state(indoc!(
10140        "select_biased!(
10141        def (
1014210143        )
10144        )"
10145    ));
10146}
10147
10148#[gpui::test]
10149async fn test_autoindent_selections(cx: &mut TestAppContext) {
10150    init_test(cx, |_| {});
10151
10152    {
10153        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10154        cx.set_state(indoc! {"
10155            impl A {
10156
10157                fn b() {}
10158
10159            «fn c() {
10160
10161            }ˇ»
10162            }
10163        "});
10164
10165        cx.update_editor(|editor, window, cx| {
10166            editor.autoindent(&Default::default(), window, cx);
10167        });
10168
10169        cx.assert_editor_state(indoc! {"
10170            impl A {
10171
10172                fn b() {}
10173
10174                «fn c() {
10175
10176                }ˇ»
10177            }
10178        "});
10179    }
10180
10181    {
10182        let mut cx = EditorTestContext::new_multibuffer(
10183            cx,
10184            [indoc! { "
10185                impl A {
10186                «
10187                // a
10188                fn b(){}
10189                »
10190                «
10191                    }
10192                    fn c(){}
10193                »
10194            "}],
10195        );
10196
10197        let buffer = cx.update_editor(|editor, _, cx| {
10198            let buffer = editor.buffer().update(cx, |buffer, _| {
10199                buffer.all_buffers().iter().next().unwrap().clone()
10200            });
10201            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10202            buffer
10203        });
10204
10205        cx.run_until_parked();
10206        cx.update_editor(|editor, window, cx| {
10207            editor.select_all(&Default::default(), window, cx);
10208            editor.autoindent(&Default::default(), window, cx)
10209        });
10210        cx.run_until_parked();
10211
10212        cx.update(|_, cx| {
10213            assert_eq!(
10214                buffer.read(cx).text(),
10215                indoc! { "
10216                    impl A {
10217
10218                        // a
10219                        fn b(){}
10220
10221
10222                    }
10223                    fn c(){}
10224
10225                " }
10226            )
10227        });
10228    }
10229}
10230
10231#[gpui::test]
10232async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10233    init_test(cx, |_| {});
10234
10235    let mut cx = EditorTestContext::new(cx).await;
10236
10237    let language = Arc::new(Language::new(
10238        LanguageConfig {
10239            brackets: BracketPairConfig {
10240                pairs: vec![
10241                    BracketPair {
10242                        start: "{".to_string(),
10243                        end: "}".to_string(),
10244                        close: true,
10245                        surround: true,
10246                        newline: true,
10247                    },
10248                    BracketPair {
10249                        start: "(".to_string(),
10250                        end: ")".to_string(),
10251                        close: true,
10252                        surround: true,
10253                        newline: true,
10254                    },
10255                    BracketPair {
10256                        start: "/*".to_string(),
10257                        end: " */".to_string(),
10258                        close: true,
10259                        surround: true,
10260                        newline: true,
10261                    },
10262                    BracketPair {
10263                        start: "[".to_string(),
10264                        end: "]".to_string(),
10265                        close: false,
10266                        surround: false,
10267                        newline: true,
10268                    },
10269                    BracketPair {
10270                        start: "\"".to_string(),
10271                        end: "\"".to_string(),
10272                        close: true,
10273                        surround: true,
10274                        newline: false,
10275                    },
10276                    BracketPair {
10277                        start: "<".to_string(),
10278                        end: ">".to_string(),
10279                        close: false,
10280                        surround: true,
10281                        newline: true,
10282                    },
10283                ],
10284                ..Default::default()
10285            },
10286            autoclose_before: "})]".to_string(),
10287            ..Default::default()
10288        },
10289        Some(tree_sitter_rust::LANGUAGE.into()),
10290    ));
10291
10292    cx.language_registry().add(language.clone());
10293    cx.update_buffer(|buffer, cx| {
10294        buffer.set_language(Some(language), cx);
10295    });
10296
10297    cx.set_state(
10298        &r#"
10299            🏀ˇ
10300            εˇ
10301            ❤️ˇ
10302        "#
10303        .unindent(),
10304    );
10305
10306    // autoclose multiple nested brackets at multiple cursors
10307    cx.update_editor(|editor, window, cx| {
10308        editor.handle_input("{", window, cx);
10309        editor.handle_input("{", window, cx);
10310        editor.handle_input("{", window, cx);
10311    });
10312    cx.assert_editor_state(
10313        &"
10314            🏀{{{ˇ}}}
10315            ε{{{ˇ}}}
10316            ❤️{{{ˇ}}}
10317        "
10318        .unindent(),
10319    );
10320
10321    // insert a different closing bracket
10322    cx.update_editor(|editor, window, cx| {
10323        editor.handle_input(")", window, cx);
10324    });
10325    cx.assert_editor_state(
10326        &"
10327            🏀{{{)ˇ}}}
10328            ε{{{)ˇ}}}
10329            ❤️{{{)ˇ}}}
10330        "
10331        .unindent(),
10332    );
10333
10334    // skip over the auto-closed brackets when typing a closing bracket
10335    cx.update_editor(|editor, window, cx| {
10336        editor.move_right(&MoveRight, window, cx);
10337        editor.handle_input("}", window, cx);
10338        editor.handle_input("}", window, cx);
10339        editor.handle_input("}", window, cx);
10340    });
10341    cx.assert_editor_state(
10342        &"
10343            🏀{{{)}}}}ˇ
10344            ε{{{)}}}}ˇ
10345            ❤️{{{)}}}}ˇ
10346        "
10347        .unindent(),
10348    );
10349
10350    // autoclose multi-character pairs
10351    cx.set_state(
10352        &"
10353            ˇ
10354            ˇ
10355        "
10356        .unindent(),
10357    );
10358    cx.update_editor(|editor, window, cx| {
10359        editor.handle_input("/", window, cx);
10360        editor.handle_input("*", window, cx);
10361    });
10362    cx.assert_editor_state(
10363        &"
10364            /*ˇ */
10365            /*ˇ */
10366        "
10367        .unindent(),
10368    );
10369
10370    // one cursor autocloses a multi-character pair, one cursor
10371    // does not autoclose.
10372    cx.set_state(
10373        &"
1037410375            ˇ
10376        "
10377        .unindent(),
10378    );
10379    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10380    cx.assert_editor_state(
10381        &"
10382            /*ˇ */
1038310384        "
10385        .unindent(),
10386    );
10387
10388    // Don't autoclose if the next character isn't whitespace and isn't
10389    // listed in the language's "autoclose_before" section.
10390    cx.set_state("ˇa b");
10391    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10392    cx.assert_editor_state("{ˇa b");
10393
10394    // Don't autoclose if `close` is false for the bracket pair
10395    cx.set_state("ˇ");
10396    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10397    cx.assert_editor_state("");
10398
10399    // Surround with brackets if text is selected
10400    cx.set_state("«aˇ» b");
10401    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10402    cx.assert_editor_state("{«aˇ»} b");
10403
10404    // Autoclose when not immediately after a word character
10405    cx.set_state("a ˇ");
10406    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10407    cx.assert_editor_state("a \"ˇ\"");
10408
10409    // Autoclose pair where the start and end characters are the same
10410    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10411    cx.assert_editor_state("a \"\"ˇ");
10412
10413    // Don't autoclose when immediately after a word character
10414    cx.set_state("");
10415    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10416    cx.assert_editor_state("a\"ˇ");
10417
10418    // Do autoclose when after a non-word character
10419    cx.set_state("");
10420    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10421    cx.assert_editor_state("{\"ˇ\"");
10422
10423    // Non identical pairs autoclose regardless of preceding character
10424    cx.set_state("");
10425    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10426    cx.assert_editor_state("a{ˇ}");
10427
10428    // Don't autoclose pair if autoclose is disabled
10429    cx.set_state("ˇ");
10430    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10431    cx.assert_editor_state("");
10432
10433    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10434    cx.set_state("«aˇ» b");
10435    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10436    cx.assert_editor_state("<«aˇ»> b");
10437}
10438
10439#[gpui::test]
10440async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10441    init_test(cx, |settings| {
10442        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10443    });
10444
10445    let mut cx = EditorTestContext::new(cx).await;
10446
10447    let language = Arc::new(Language::new(
10448        LanguageConfig {
10449            brackets: BracketPairConfig {
10450                pairs: vec![
10451                    BracketPair {
10452                        start: "{".to_string(),
10453                        end: "}".to_string(),
10454                        close: true,
10455                        surround: true,
10456                        newline: true,
10457                    },
10458                    BracketPair {
10459                        start: "(".to_string(),
10460                        end: ")".to_string(),
10461                        close: true,
10462                        surround: true,
10463                        newline: true,
10464                    },
10465                    BracketPair {
10466                        start: "[".to_string(),
10467                        end: "]".to_string(),
10468                        close: false,
10469                        surround: false,
10470                        newline: true,
10471                    },
10472                ],
10473                ..Default::default()
10474            },
10475            autoclose_before: "})]".to_string(),
10476            ..Default::default()
10477        },
10478        Some(tree_sitter_rust::LANGUAGE.into()),
10479    ));
10480
10481    cx.language_registry().add(language.clone());
10482    cx.update_buffer(|buffer, cx| {
10483        buffer.set_language(Some(language), cx);
10484    });
10485
10486    cx.set_state(
10487        &"
10488            ˇ
10489            ˇ
10490            ˇ
10491        "
10492        .unindent(),
10493    );
10494
10495    // ensure only matching closing brackets are skipped over
10496    cx.update_editor(|editor, window, cx| {
10497        editor.handle_input("}", window, cx);
10498        editor.move_left(&MoveLeft, window, cx);
10499        editor.handle_input(")", window, cx);
10500        editor.move_left(&MoveLeft, window, cx);
10501    });
10502    cx.assert_editor_state(
10503        &"
10504            ˇ)}
10505            ˇ)}
10506            ˇ)}
10507        "
10508        .unindent(),
10509    );
10510
10511    // skip-over closing brackets at multiple cursors
10512    cx.update_editor(|editor, window, cx| {
10513        editor.handle_input(")", window, cx);
10514        editor.handle_input("}", window, cx);
10515    });
10516    cx.assert_editor_state(
10517        &"
10518            )}ˇ
10519            )}ˇ
10520            )}ˇ
10521        "
10522        .unindent(),
10523    );
10524
10525    // ignore non-close brackets
10526    cx.update_editor(|editor, window, cx| {
10527        editor.handle_input("]", window, cx);
10528        editor.move_left(&MoveLeft, window, cx);
10529        editor.handle_input("]", window, cx);
10530    });
10531    cx.assert_editor_state(
10532        &"
10533            )}]ˇ]
10534            )}]ˇ]
10535            )}]ˇ]
10536        "
10537        .unindent(),
10538    );
10539}
10540
10541#[gpui::test]
10542async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10543    init_test(cx, |_| {});
10544
10545    let mut cx = EditorTestContext::new(cx).await;
10546
10547    let html_language = Arc::new(
10548        Language::new(
10549            LanguageConfig {
10550                name: "HTML".into(),
10551                brackets: BracketPairConfig {
10552                    pairs: vec![
10553                        BracketPair {
10554                            start: "<".into(),
10555                            end: ">".into(),
10556                            close: true,
10557                            ..Default::default()
10558                        },
10559                        BracketPair {
10560                            start: "{".into(),
10561                            end: "}".into(),
10562                            close: true,
10563                            ..Default::default()
10564                        },
10565                        BracketPair {
10566                            start: "(".into(),
10567                            end: ")".into(),
10568                            close: true,
10569                            ..Default::default()
10570                        },
10571                    ],
10572                    ..Default::default()
10573                },
10574                autoclose_before: "})]>".into(),
10575                ..Default::default()
10576            },
10577            Some(tree_sitter_html::LANGUAGE.into()),
10578        )
10579        .with_injection_query(
10580            r#"
10581            (script_element
10582                (raw_text) @injection.content
10583                (#set! injection.language "javascript"))
10584            "#,
10585        )
10586        .unwrap(),
10587    );
10588
10589    let javascript_language = Arc::new(Language::new(
10590        LanguageConfig {
10591            name: "JavaScript".into(),
10592            brackets: BracketPairConfig {
10593                pairs: vec![
10594                    BracketPair {
10595                        start: "/*".into(),
10596                        end: " */".into(),
10597                        close: true,
10598                        ..Default::default()
10599                    },
10600                    BracketPair {
10601                        start: "{".into(),
10602                        end: "}".into(),
10603                        close: true,
10604                        ..Default::default()
10605                    },
10606                    BracketPair {
10607                        start: "(".into(),
10608                        end: ")".into(),
10609                        close: true,
10610                        ..Default::default()
10611                    },
10612                ],
10613                ..Default::default()
10614            },
10615            autoclose_before: "})]>".into(),
10616            ..Default::default()
10617        },
10618        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10619    ));
10620
10621    cx.language_registry().add(html_language.clone());
10622    cx.language_registry().add(javascript_language);
10623    cx.executor().run_until_parked();
10624
10625    cx.update_buffer(|buffer, cx| {
10626        buffer.set_language(Some(html_language), cx);
10627    });
10628
10629    cx.set_state(
10630        &r#"
10631            <body>ˇ
10632                <script>
10633                    var x = 1;ˇ
10634                </script>
10635            </body>ˇ
10636        "#
10637        .unindent(),
10638    );
10639
10640    // Precondition: different languages are active at different locations.
10641    cx.update_editor(|editor, window, cx| {
10642        let snapshot = editor.snapshot(window, cx);
10643        let cursors = editor
10644            .selections
10645            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10646        let languages = cursors
10647            .iter()
10648            .map(|c| snapshot.language_at(c.start).unwrap().name())
10649            .collect::<Vec<_>>();
10650        assert_eq!(
10651            languages,
10652            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10653        );
10654    });
10655
10656    // Angle brackets autoclose in HTML, but not JavaScript.
10657    cx.update_editor(|editor, window, cx| {
10658        editor.handle_input("<", window, cx);
10659        editor.handle_input("a", window, cx);
10660    });
10661    cx.assert_editor_state(
10662        &r#"
10663            <body><aˇ>
10664                <script>
10665                    var x = 1;<aˇ
10666                </script>
10667            </body><aˇ>
10668        "#
10669        .unindent(),
10670    );
10671
10672    // Curly braces and parens autoclose in both HTML and JavaScript.
10673    cx.update_editor(|editor, window, cx| {
10674        editor.handle_input(" b=", window, cx);
10675        editor.handle_input("{", window, cx);
10676        editor.handle_input("c", window, cx);
10677        editor.handle_input("(", window, cx);
10678    });
10679    cx.assert_editor_state(
10680        &r#"
10681            <body><a b={c(ˇ)}>
10682                <script>
10683                    var x = 1;<a b={c(ˇ)}
10684                </script>
10685            </body><a b={c(ˇ)}>
10686        "#
10687        .unindent(),
10688    );
10689
10690    // Brackets that were already autoclosed are skipped.
10691    cx.update_editor(|editor, window, cx| {
10692        editor.handle_input(")", window, cx);
10693        editor.handle_input("d", window, cx);
10694        editor.handle_input("}", window, cx);
10695    });
10696    cx.assert_editor_state(
10697        &r#"
10698            <body><a b={c()d}ˇ>
10699                <script>
10700                    var x = 1;<a b={c()d}ˇ
10701                </script>
10702            </body><a b={c()d}ˇ>
10703        "#
10704        .unindent(),
10705    );
10706    cx.update_editor(|editor, window, cx| {
10707        editor.handle_input(">", window, cx);
10708    });
10709    cx.assert_editor_state(
10710        &r#"
10711            <body><a b={c()d}>ˇ
10712                <script>
10713                    var x = 1;<a b={c()d}>ˇ
10714                </script>
10715            </body><a b={c()d}>ˇ
10716        "#
10717        .unindent(),
10718    );
10719
10720    // Reset
10721    cx.set_state(
10722        &r#"
10723            <body>ˇ
10724                <script>
10725                    var x = 1;ˇ
10726                </script>
10727            </body>ˇ
10728        "#
10729        .unindent(),
10730    );
10731
10732    cx.update_editor(|editor, window, cx| {
10733        editor.handle_input("<", window, cx);
10734    });
10735    cx.assert_editor_state(
10736        &r#"
10737            <body><ˇ>
10738                <script>
10739                    var x = 1;<ˇ
10740                </script>
10741            </body><ˇ>
10742        "#
10743        .unindent(),
10744    );
10745
10746    // When backspacing, the closing angle brackets are removed.
10747    cx.update_editor(|editor, window, cx| {
10748        editor.backspace(&Backspace, window, cx);
10749    });
10750    cx.assert_editor_state(
10751        &r#"
10752            <body>ˇ
10753                <script>
10754                    var x = 1;ˇ
10755                </script>
10756            </body>ˇ
10757        "#
10758        .unindent(),
10759    );
10760
10761    // Block comments autoclose in JavaScript, but not HTML.
10762    cx.update_editor(|editor, window, cx| {
10763        editor.handle_input("/", window, cx);
10764        editor.handle_input("*", window, cx);
10765    });
10766    cx.assert_editor_state(
10767        &r#"
10768            <body>/*ˇ
10769                <script>
10770                    var x = 1;/*ˇ */
10771                </script>
10772            </body>/*ˇ
10773        "#
10774        .unindent(),
10775    );
10776}
10777
10778#[gpui::test]
10779async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10780    init_test(cx, |_| {});
10781
10782    let mut cx = EditorTestContext::new(cx).await;
10783
10784    let rust_language = Arc::new(
10785        Language::new(
10786            LanguageConfig {
10787                name: "Rust".into(),
10788                brackets: serde_json::from_value(json!([
10789                    { "start": "{", "end": "}", "close": true, "newline": true },
10790                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10791                ]))
10792                .unwrap(),
10793                autoclose_before: "})]>".into(),
10794                ..Default::default()
10795            },
10796            Some(tree_sitter_rust::LANGUAGE.into()),
10797        )
10798        .with_override_query("(string_literal) @string")
10799        .unwrap(),
10800    );
10801
10802    cx.language_registry().add(rust_language.clone());
10803    cx.update_buffer(|buffer, cx| {
10804        buffer.set_language(Some(rust_language), cx);
10805    });
10806
10807    cx.set_state(
10808        &r#"
10809            let x = ˇ
10810        "#
10811        .unindent(),
10812    );
10813
10814    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10815    cx.update_editor(|editor, window, cx| {
10816        editor.handle_input("\"", window, cx);
10817    });
10818    cx.assert_editor_state(
10819        &r#"
10820            let x = "ˇ"
10821        "#
10822        .unindent(),
10823    );
10824
10825    // Inserting another quotation mark. The cursor moves across the existing
10826    // automatically-inserted quotation mark.
10827    cx.update_editor(|editor, window, cx| {
10828        editor.handle_input("\"", window, cx);
10829    });
10830    cx.assert_editor_state(
10831        &r#"
10832            let x = ""ˇ
10833        "#
10834        .unindent(),
10835    );
10836
10837    // Reset
10838    cx.set_state(
10839        &r#"
10840            let x = ˇ
10841        "#
10842        .unindent(),
10843    );
10844
10845    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10846    cx.update_editor(|editor, window, cx| {
10847        editor.handle_input("\"", window, cx);
10848        editor.handle_input(" ", window, cx);
10849        editor.move_left(&Default::default(), window, cx);
10850        editor.handle_input("\\", window, cx);
10851        editor.handle_input("\"", window, cx);
10852    });
10853    cx.assert_editor_state(
10854        &r#"
10855            let x = "\"ˇ "
10856        "#
10857        .unindent(),
10858    );
10859
10860    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10861    // mark. Nothing is inserted.
10862    cx.update_editor(|editor, window, cx| {
10863        editor.move_right(&Default::default(), window, cx);
10864        editor.handle_input("\"", window, cx);
10865    });
10866    cx.assert_editor_state(
10867        &r#"
10868            let x = "\" "ˇ
10869        "#
10870        .unindent(),
10871    );
10872}
10873
10874#[gpui::test]
10875async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
10876    init_test(cx, |_| {});
10877
10878    let mut cx = EditorTestContext::new(cx).await;
10879    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10880
10881    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10882
10883    // Double quote inside single-quoted string
10884    cx.set_state(indoc! {r#"
10885        def main():
10886            items = ['"', ˇ]
10887    "#});
10888    cx.update_editor(|editor, window, cx| {
10889        editor.handle_input("\"", window, cx);
10890    });
10891    cx.assert_editor_state(indoc! {r#"
10892        def main():
10893            items = ['"', "ˇ"]
10894    "#});
10895
10896    // Two double quotes inside single-quoted string
10897    cx.set_state(indoc! {r#"
10898        def main():
10899            items = ['""', ˇ]
10900    "#});
10901    cx.update_editor(|editor, window, cx| {
10902        editor.handle_input("\"", window, cx);
10903    });
10904    cx.assert_editor_state(indoc! {r#"
10905        def main():
10906            items = ['""', "ˇ"]
10907    "#});
10908
10909    // Single quote inside double-quoted string
10910    cx.set_state(indoc! {r#"
10911        def main():
10912            items = ["'", ˇ]
10913    "#});
10914    cx.update_editor(|editor, window, cx| {
10915        editor.handle_input("'", window, cx);
10916    });
10917    cx.assert_editor_state(indoc! {r#"
10918        def main():
10919            items = ["'", 'ˇ']
10920    "#});
10921
10922    // Two single quotes inside double-quoted string
10923    cx.set_state(indoc! {r#"
10924        def main():
10925            items = ["''", ˇ]
10926    "#});
10927    cx.update_editor(|editor, window, cx| {
10928        editor.handle_input("'", window, cx);
10929    });
10930    cx.assert_editor_state(indoc! {r#"
10931        def main():
10932            items = ["''", 'ˇ']
10933    "#});
10934
10935    // Mixed quotes on same line
10936    cx.set_state(indoc! {r#"
10937        def main():
10938            items = ['"""', "'''''", ˇ]
10939    "#});
10940    cx.update_editor(|editor, window, cx| {
10941        editor.handle_input("\"", window, cx);
10942    });
10943    cx.assert_editor_state(indoc! {r#"
10944        def main():
10945            items = ['"""', "'''''", "ˇ"]
10946    "#});
10947    cx.update_editor(|editor, window, cx| {
10948        editor.move_right(&MoveRight, window, cx);
10949    });
10950    cx.update_editor(|editor, window, cx| {
10951        editor.handle_input(", ", window, cx);
10952    });
10953    cx.update_editor(|editor, window, cx| {
10954        editor.handle_input("'", window, cx);
10955    });
10956    cx.assert_editor_state(indoc! {r#"
10957        def main():
10958            items = ['"""', "'''''", "", 'ˇ']
10959    "#});
10960}
10961
10962#[gpui::test]
10963async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
10964    init_test(cx, |_| {});
10965
10966    let mut cx = EditorTestContext::new(cx).await;
10967    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10968    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10969
10970    cx.set_state(indoc! {r#"
10971        def main():
10972            items = ["🎉", ˇ]
10973    "#});
10974    cx.update_editor(|editor, window, cx| {
10975        editor.handle_input("\"", window, cx);
10976    });
10977    cx.assert_editor_state(indoc! {r#"
10978        def main():
10979            items = ["🎉", "ˇ"]
10980    "#});
10981}
10982
10983#[gpui::test]
10984async fn test_surround_with_pair(cx: &mut TestAppContext) {
10985    init_test(cx, |_| {});
10986
10987    let language = Arc::new(Language::new(
10988        LanguageConfig {
10989            brackets: BracketPairConfig {
10990                pairs: vec![
10991                    BracketPair {
10992                        start: "{".to_string(),
10993                        end: "}".to_string(),
10994                        close: true,
10995                        surround: true,
10996                        newline: true,
10997                    },
10998                    BracketPair {
10999                        start: "/* ".to_string(),
11000                        end: "*/".to_string(),
11001                        close: true,
11002                        surround: true,
11003                        ..Default::default()
11004                    },
11005                ],
11006                ..Default::default()
11007            },
11008            ..Default::default()
11009        },
11010        Some(tree_sitter_rust::LANGUAGE.into()),
11011    ));
11012
11013    let text = r#"
11014        a
11015        b
11016        c
11017    "#
11018    .unindent();
11019
11020    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11021    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11022    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11023    editor
11024        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11025        .await;
11026
11027    editor.update_in(cx, |editor, window, cx| {
11028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11029            s.select_display_ranges([
11030                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11031                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11032                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11033            ])
11034        });
11035
11036        editor.handle_input("{", window, cx);
11037        editor.handle_input("{", window, cx);
11038        editor.handle_input("{", window, cx);
11039        assert_eq!(
11040            editor.text(cx),
11041            "
11042                {{{a}}}
11043                {{{b}}}
11044                {{{c}}}
11045            "
11046            .unindent()
11047        );
11048        assert_eq!(
11049            display_ranges(editor, cx),
11050            [
11051                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11052                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11053                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11054            ]
11055        );
11056
11057        editor.undo(&Undo, window, cx);
11058        editor.undo(&Undo, window, cx);
11059        editor.undo(&Undo, window, cx);
11060        assert_eq!(
11061            editor.text(cx),
11062            "
11063                a
11064                b
11065                c
11066            "
11067            .unindent()
11068        );
11069        assert_eq!(
11070            display_ranges(editor, cx),
11071            [
11072                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11073                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11074                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11075            ]
11076        );
11077
11078        // Ensure inserting the first character of a multi-byte bracket pair
11079        // doesn't surround the selections with the bracket.
11080        editor.handle_input("/", window, cx);
11081        assert_eq!(
11082            editor.text(cx),
11083            "
11084                /
11085                /
11086                /
11087            "
11088            .unindent()
11089        );
11090        assert_eq!(
11091            display_ranges(editor, cx),
11092            [
11093                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11094                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11095                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11096            ]
11097        );
11098
11099        editor.undo(&Undo, window, cx);
11100        assert_eq!(
11101            editor.text(cx),
11102            "
11103                a
11104                b
11105                c
11106            "
11107            .unindent()
11108        );
11109        assert_eq!(
11110            display_ranges(editor, cx),
11111            [
11112                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11113                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11114                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11115            ]
11116        );
11117
11118        // Ensure inserting the last character of a multi-byte bracket pair
11119        // doesn't surround the selections with the bracket.
11120        editor.handle_input("*", window, cx);
11121        assert_eq!(
11122            editor.text(cx),
11123            "
11124                *
11125                *
11126                *
11127            "
11128            .unindent()
11129        );
11130        assert_eq!(
11131            display_ranges(editor, cx),
11132            [
11133                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11134                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11135                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11136            ]
11137        );
11138    });
11139}
11140
11141#[gpui::test]
11142async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11143    init_test(cx, |_| {});
11144
11145    let language = Arc::new(Language::new(
11146        LanguageConfig {
11147            brackets: BracketPairConfig {
11148                pairs: vec![BracketPair {
11149                    start: "{".to_string(),
11150                    end: "}".to_string(),
11151                    close: true,
11152                    surround: true,
11153                    newline: true,
11154                }],
11155                ..Default::default()
11156            },
11157            autoclose_before: "}".to_string(),
11158            ..Default::default()
11159        },
11160        Some(tree_sitter_rust::LANGUAGE.into()),
11161    ));
11162
11163    let text = r#"
11164        a
11165        b
11166        c
11167    "#
11168    .unindent();
11169
11170    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11171    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11172    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11173    editor
11174        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11175        .await;
11176
11177    editor.update_in(cx, |editor, window, cx| {
11178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11179            s.select_ranges([
11180                Point::new(0, 1)..Point::new(0, 1),
11181                Point::new(1, 1)..Point::new(1, 1),
11182                Point::new(2, 1)..Point::new(2, 1),
11183            ])
11184        });
11185
11186        editor.handle_input("{", window, cx);
11187        editor.handle_input("{", window, cx);
11188        editor.handle_input("_", window, cx);
11189        assert_eq!(
11190            editor.text(cx),
11191            "
11192                a{{_}}
11193                b{{_}}
11194                c{{_}}
11195            "
11196            .unindent()
11197        );
11198        assert_eq!(
11199            editor
11200                .selections
11201                .ranges::<Point>(&editor.display_snapshot(cx)),
11202            [
11203                Point::new(0, 4)..Point::new(0, 4),
11204                Point::new(1, 4)..Point::new(1, 4),
11205                Point::new(2, 4)..Point::new(2, 4)
11206            ]
11207        );
11208
11209        editor.backspace(&Default::default(), window, cx);
11210        editor.backspace(&Default::default(), window, cx);
11211        assert_eq!(
11212            editor.text(cx),
11213            "
11214                a{}
11215                b{}
11216                c{}
11217            "
11218            .unindent()
11219        );
11220        assert_eq!(
11221            editor
11222                .selections
11223                .ranges::<Point>(&editor.display_snapshot(cx)),
11224            [
11225                Point::new(0, 2)..Point::new(0, 2),
11226                Point::new(1, 2)..Point::new(1, 2),
11227                Point::new(2, 2)..Point::new(2, 2)
11228            ]
11229        );
11230
11231        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11232        assert_eq!(
11233            editor.text(cx),
11234            "
11235                a
11236                b
11237                c
11238            "
11239            .unindent()
11240        );
11241        assert_eq!(
11242            editor
11243                .selections
11244                .ranges::<Point>(&editor.display_snapshot(cx)),
11245            [
11246                Point::new(0, 1)..Point::new(0, 1),
11247                Point::new(1, 1)..Point::new(1, 1),
11248                Point::new(2, 1)..Point::new(2, 1)
11249            ]
11250        );
11251    });
11252}
11253
11254#[gpui::test]
11255async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11256    init_test(cx, |settings| {
11257        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11258    });
11259
11260    let mut cx = EditorTestContext::new(cx).await;
11261
11262    let language = Arc::new(Language::new(
11263        LanguageConfig {
11264            brackets: BracketPairConfig {
11265                pairs: vec![
11266                    BracketPair {
11267                        start: "{".to_string(),
11268                        end: "}".to_string(),
11269                        close: true,
11270                        surround: true,
11271                        newline: true,
11272                    },
11273                    BracketPair {
11274                        start: "(".to_string(),
11275                        end: ")".to_string(),
11276                        close: true,
11277                        surround: true,
11278                        newline: true,
11279                    },
11280                    BracketPair {
11281                        start: "[".to_string(),
11282                        end: "]".to_string(),
11283                        close: false,
11284                        surround: true,
11285                        newline: true,
11286                    },
11287                ],
11288                ..Default::default()
11289            },
11290            autoclose_before: "})]".to_string(),
11291            ..Default::default()
11292        },
11293        Some(tree_sitter_rust::LANGUAGE.into()),
11294    ));
11295
11296    cx.language_registry().add(language.clone());
11297    cx.update_buffer(|buffer, cx| {
11298        buffer.set_language(Some(language), cx);
11299    });
11300
11301    cx.set_state(
11302        &"
11303            {(ˇ)}
11304            [[ˇ]]
11305            {(ˇ)}
11306        "
11307        .unindent(),
11308    );
11309
11310    cx.update_editor(|editor, window, cx| {
11311        editor.backspace(&Default::default(), window, cx);
11312        editor.backspace(&Default::default(), window, cx);
11313    });
11314
11315    cx.assert_editor_state(
11316        &"
11317            ˇ
11318            ˇ]]
11319            ˇ
11320        "
11321        .unindent(),
11322    );
11323
11324    cx.update_editor(|editor, window, cx| {
11325        editor.handle_input("{", window, cx);
11326        editor.handle_input("{", window, cx);
11327        editor.move_right(&MoveRight, window, cx);
11328        editor.move_right(&MoveRight, window, cx);
11329        editor.move_left(&MoveLeft, window, cx);
11330        editor.move_left(&MoveLeft, window, cx);
11331        editor.backspace(&Default::default(), window, cx);
11332    });
11333
11334    cx.assert_editor_state(
11335        &"
11336            {ˇ}
11337            {ˇ}]]
11338            {ˇ}
11339        "
11340        .unindent(),
11341    );
11342
11343    cx.update_editor(|editor, window, cx| {
11344        editor.backspace(&Default::default(), window, cx);
11345    });
11346
11347    cx.assert_editor_state(
11348        &"
11349            ˇ
11350            ˇ]]
11351            ˇ
11352        "
11353        .unindent(),
11354    );
11355}
11356
11357#[gpui::test]
11358async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11359    init_test(cx, |_| {});
11360
11361    let language = Arc::new(Language::new(
11362        LanguageConfig::default(),
11363        Some(tree_sitter_rust::LANGUAGE.into()),
11364    ));
11365
11366    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11367    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11368    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11369    editor
11370        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11371        .await;
11372
11373    editor.update_in(cx, |editor, window, cx| {
11374        editor.set_auto_replace_emoji_shortcode(true);
11375
11376        editor.handle_input("Hello ", window, cx);
11377        editor.handle_input(":wave", window, cx);
11378        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11379
11380        editor.handle_input(":", window, cx);
11381        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11382
11383        editor.handle_input(" :smile", window, cx);
11384        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11385
11386        editor.handle_input(":", window, cx);
11387        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11388
11389        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11390        editor.handle_input(":wave", window, cx);
11391        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11392
11393        editor.handle_input(":", window, cx);
11394        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11395
11396        editor.handle_input(":1", window, cx);
11397        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11398
11399        editor.handle_input(":", window, cx);
11400        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11401
11402        // Ensure shortcode does not get replaced when it is part of a word
11403        editor.handle_input(" Test:wave", window, cx);
11404        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11405
11406        editor.handle_input(":", window, cx);
11407        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11408
11409        editor.set_auto_replace_emoji_shortcode(false);
11410
11411        // Ensure shortcode does not get replaced when auto replace is off
11412        editor.handle_input(" :wave", window, cx);
11413        assert_eq!(
11414            editor.text(cx),
11415            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11416        );
11417
11418        editor.handle_input(":", window, cx);
11419        assert_eq!(
11420            editor.text(cx),
11421            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11422        );
11423    });
11424}
11425
11426#[gpui::test]
11427async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11428    init_test(cx, |_| {});
11429
11430    let (text, insertion_ranges) = marked_text_ranges(
11431        indoc! {"
11432            ˇ
11433        "},
11434        false,
11435    );
11436
11437    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11438    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11439
11440    _ = editor.update_in(cx, |editor, window, cx| {
11441        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11442
11443        editor
11444            .insert_snippet(
11445                &insertion_ranges
11446                    .iter()
11447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11448                    .collect::<Vec<_>>(),
11449                snippet,
11450                window,
11451                cx,
11452            )
11453            .unwrap();
11454
11455        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11456            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11457            assert_eq!(editor.text(cx), expected_text);
11458            assert_eq!(
11459                editor.selections.ranges(&editor.display_snapshot(cx)),
11460                selection_ranges
11461                    .iter()
11462                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11463                    .collect::<Vec<_>>()
11464            );
11465        }
11466
11467        assert(
11468            editor,
11469            cx,
11470            indoc! {"
11471            type «» =•
11472            "},
11473        );
11474
11475        assert!(editor.context_menu_visible(), "There should be a matches");
11476    });
11477}
11478
11479#[gpui::test]
11480async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11481    init_test(cx, |_| {});
11482
11483    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11484        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11485        assert_eq!(editor.text(cx), expected_text);
11486        assert_eq!(
11487            editor.selections.ranges(&editor.display_snapshot(cx)),
11488            selection_ranges
11489                .iter()
11490                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11491                .collect::<Vec<_>>()
11492        );
11493    }
11494
11495    let (text, insertion_ranges) = marked_text_ranges(
11496        indoc! {"
11497            ˇ
11498        "},
11499        false,
11500    );
11501
11502    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11503    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11504
11505    _ = editor.update_in(cx, |editor, window, cx| {
11506        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11507
11508        editor
11509            .insert_snippet(
11510                &insertion_ranges
11511                    .iter()
11512                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11513                    .collect::<Vec<_>>(),
11514                snippet,
11515                window,
11516                cx,
11517            )
11518            .unwrap();
11519
11520        assert_state(
11521            editor,
11522            cx,
11523            indoc! {"
11524            type «» = ;•
11525            "},
11526        );
11527
11528        assert!(
11529            editor.context_menu_visible(),
11530            "Context menu should be visible for placeholder choices"
11531        );
11532
11533        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11534
11535        assert_state(
11536            editor,
11537            cx,
11538            indoc! {"
11539            type  = «»;•
11540            "},
11541        );
11542
11543        assert!(
11544            !editor.context_menu_visible(),
11545            "Context menu should be hidden after moving to next tabstop"
11546        );
11547
11548        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11549
11550        assert_state(
11551            editor,
11552            cx,
11553            indoc! {"
11554            type  = ; ˇ
11555            "},
11556        );
11557
11558        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11559
11560        assert_state(
11561            editor,
11562            cx,
11563            indoc! {"
11564            type  = ; ˇ
11565            "},
11566        );
11567    });
11568
11569    _ = editor.update_in(cx, |editor, window, cx| {
11570        editor.select_all(&SelectAll, window, cx);
11571        editor.backspace(&Backspace, window, cx);
11572
11573        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11574        let insertion_ranges = editor
11575            .selections
11576            .all(&editor.display_snapshot(cx))
11577            .iter()
11578            .map(|s| s.range())
11579            .collect::<Vec<_>>();
11580
11581        editor
11582            .insert_snippet(&insertion_ranges, snippet, window, cx)
11583            .unwrap();
11584
11585        assert_state(editor, cx, "fn «» = value;•");
11586
11587        assert!(
11588            editor.context_menu_visible(),
11589            "Context menu should be visible for placeholder choices"
11590        );
11591
11592        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11593
11594        assert_state(editor, cx, "fn  = «valueˇ»;•");
11595
11596        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11597
11598        assert_state(editor, cx, "fn «» = value;•");
11599
11600        assert!(
11601            editor.context_menu_visible(),
11602            "Context menu should be visible again after returning to first tabstop"
11603        );
11604
11605        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11606
11607        assert_state(editor, cx, "fn «» = value;•");
11608    });
11609}
11610
11611#[gpui::test]
11612async fn test_snippets(cx: &mut TestAppContext) {
11613    init_test(cx, |_| {});
11614
11615    let mut cx = EditorTestContext::new(cx).await;
11616
11617    cx.set_state(indoc! {"
11618        a.ˇ b
11619        a.ˇ b
11620        a.ˇ b
11621    "});
11622
11623    cx.update_editor(|editor, window, cx| {
11624        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11625        let insertion_ranges = editor
11626            .selections
11627            .all(&editor.display_snapshot(cx))
11628            .iter()
11629            .map(|s| s.range())
11630            .collect::<Vec<_>>();
11631        editor
11632            .insert_snippet(&insertion_ranges, snippet, window, cx)
11633            .unwrap();
11634    });
11635
11636    cx.assert_editor_state(indoc! {"
11637        a.f(«oneˇ», two, «threeˇ») b
11638        a.f(«oneˇ», two, «threeˇ») b
11639        a.f(«oneˇ», two, «threeˇ») b
11640    "});
11641
11642    // Can't move earlier than the first tab stop
11643    cx.update_editor(|editor, window, cx| {
11644        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11645    });
11646    cx.assert_editor_state(indoc! {"
11647        a.f(«oneˇ», two, «threeˇ») b
11648        a.f(«oneˇ», two, «threeˇ») b
11649        a.f(«oneˇ», two, «threeˇ») b
11650    "});
11651
11652    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11653    cx.assert_editor_state(indoc! {"
11654        a.f(one, «twoˇ», three) b
11655        a.f(one, «twoˇ», three) b
11656        a.f(one, «twoˇ», three) b
11657    "});
11658
11659    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11660    cx.assert_editor_state(indoc! {"
11661        a.f(«oneˇ», two, «threeˇ») b
11662        a.f(«oneˇ», two, «threeˇ») b
11663        a.f(«oneˇ», two, «threeˇ») b
11664    "});
11665
11666    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11667    cx.assert_editor_state(indoc! {"
11668        a.f(one, «twoˇ», three) b
11669        a.f(one, «twoˇ», three) b
11670        a.f(one, «twoˇ», three) b
11671    "});
11672    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11673    cx.assert_editor_state(indoc! {"
11674        a.f(one, two, three)ˇ b
11675        a.f(one, two, three)ˇ b
11676        a.f(one, two, three)ˇ b
11677    "});
11678
11679    // As soon as the last tab stop is reached, snippet state is gone
11680    cx.update_editor(|editor, window, cx| {
11681        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11682    });
11683    cx.assert_editor_state(indoc! {"
11684        a.f(one, two, three)ˇ b
11685        a.f(one, two, three)ˇ b
11686        a.f(one, two, three)ˇ b
11687    "});
11688}
11689
11690#[gpui::test]
11691async fn test_snippet_indentation(cx: &mut TestAppContext) {
11692    init_test(cx, |_| {});
11693
11694    let mut cx = EditorTestContext::new(cx).await;
11695
11696    cx.update_editor(|editor, window, cx| {
11697        let snippet = Snippet::parse(indoc! {"
11698            /*
11699             * Multiline comment with leading indentation
11700             *
11701             * $1
11702             */
11703            $0"})
11704        .unwrap();
11705        let insertion_ranges = editor
11706            .selections
11707            .all(&editor.display_snapshot(cx))
11708            .iter()
11709            .map(|s| s.range())
11710            .collect::<Vec<_>>();
11711        editor
11712            .insert_snippet(&insertion_ranges, snippet, window, cx)
11713            .unwrap();
11714    });
11715
11716    cx.assert_editor_state(indoc! {"
11717        /*
11718         * Multiline comment with leading indentation
11719         *
11720         * ˇ
11721         */
11722    "});
11723
11724    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11725    cx.assert_editor_state(indoc! {"
11726        /*
11727         * Multiline comment with leading indentation
11728         *
11729         *•
11730         */
11731        ˇ"});
11732}
11733
11734#[gpui::test]
11735async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11736    init_test(cx, |_| {});
11737
11738    let mut cx = EditorTestContext::new(cx).await;
11739    cx.update_editor(|editor, _, cx| {
11740        editor.project().unwrap().update(cx, |project, cx| {
11741            project.snippets().update(cx, |snippets, _cx| {
11742                let snippet = project::snippet_provider::Snippet {
11743                    prefix: vec!["multi word".to_string()],
11744                    body: "this is many words".to_string(),
11745                    description: Some("description".to_string()),
11746                    name: "multi-word snippet test".to_string(),
11747                };
11748                snippets.add_snippet_for_test(
11749                    None,
11750                    PathBuf::from("test_snippets.json"),
11751                    vec![Arc::new(snippet)],
11752                );
11753            });
11754        })
11755    });
11756
11757    for (input_to_simulate, should_match_snippet) in [
11758        ("m", true),
11759        ("m ", true),
11760        ("m w", true),
11761        ("aa m w", true),
11762        ("aa m g", false),
11763    ] {
11764        cx.set_state("ˇ");
11765        cx.simulate_input(input_to_simulate); // fails correctly
11766
11767        cx.update_editor(|editor, _, _| {
11768            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11769            else {
11770                assert!(!should_match_snippet); // no completions! don't even show the menu
11771                return;
11772            };
11773            assert!(context_menu.visible());
11774            let completions = context_menu.completions.borrow();
11775
11776            assert_eq!(!completions.is_empty(), should_match_snippet);
11777        });
11778    }
11779}
11780
11781#[gpui::test]
11782async fn test_document_format_during_save(cx: &mut TestAppContext) {
11783    init_test(cx, |_| {});
11784
11785    let fs = FakeFs::new(cx.executor());
11786    fs.insert_file(path!("/file.rs"), Default::default()).await;
11787
11788    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11789
11790    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11791    language_registry.add(rust_lang());
11792    let mut fake_servers = language_registry.register_fake_lsp(
11793        "Rust",
11794        FakeLspAdapter {
11795            capabilities: lsp::ServerCapabilities {
11796                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11797                ..Default::default()
11798            },
11799            ..Default::default()
11800        },
11801    );
11802
11803    let buffer = project
11804        .update(cx, |project, cx| {
11805            project.open_local_buffer(path!("/file.rs"), cx)
11806        })
11807        .await
11808        .unwrap();
11809
11810    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11811    let (editor, cx) = cx.add_window_view(|window, cx| {
11812        build_editor_with_project(project.clone(), buffer, window, cx)
11813    });
11814    editor.update_in(cx, |editor, window, cx| {
11815        editor.set_text("one\ntwo\nthree\n", window, cx)
11816    });
11817    assert!(cx.read(|cx| editor.is_dirty(cx)));
11818
11819    cx.executor().start_waiting();
11820    let fake_server = fake_servers.next().await.unwrap();
11821
11822    {
11823        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11824            move |params, _| async move {
11825                assert_eq!(
11826                    params.text_document.uri,
11827                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11828                );
11829                assert_eq!(params.options.tab_size, 4);
11830                Ok(Some(vec![lsp::TextEdit::new(
11831                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11832                    ", ".to_string(),
11833                )]))
11834            },
11835        );
11836        let save = editor
11837            .update_in(cx, |editor, window, cx| {
11838                editor.save(
11839                    SaveOptions {
11840                        format: true,
11841                        autosave: false,
11842                    },
11843                    project.clone(),
11844                    window,
11845                    cx,
11846                )
11847            })
11848            .unwrap();
11849        cx.executor().start_waiting();
11850        save.await;
11851
11852        assert_eq!(
11853            editor.update(cx, |editor, cx| editor.text(cx)),
11854            "one, two\nthree\n"
11855        );
11856        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11857    }
11858
11859    {
11860        editor.update_in(cx, |editor, window, cx| {
11861            editor.set_text("one\ntwo\nthree\n", window, cx)
11862        });
11863        assert!(cx.read(|cx| editor.is_dirty(cx)));
11864
11865        // Ensure we can still save even if formatting hangs.
11866        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11867            move |params, _| async move {
11868                assert_eq!(
11869                    params.text_document.uri,
11870                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11871                );
11872                futures::future::pending::<()>().await;
11873                unreachable!()
11874            },
11875        );
11876        let save = editor
11877            .update_in(cx, |editor, window, cx| {
11878                editor.save(
11879                    SaveOptions {
11880                        format: true,
11881                        autosave: false,
11882                    },
11883                    project.clone(),
11884                    window,
11885                    cx,
11886                )
11887            })
11888            .unwrap();
11889        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11890        cx.executor().start_waiting();
11891        save.await;
11892        assert_eq!(
11893            editor.update(cx, |editor, cx| editor.text(cx)),
11894            "one\ntwo\nthree\n"
11895        );
11896    }
11897
11898    // Set rust language override and assert overridden tabsize is sent to language server
11899    update_test_language_settings(cx, |settings| {
11900        settings.languages.0.insert(
11901            "Rust".into(),
11902            LanguageSettingsContent {
11903                tab_size: NonZeroU32::new(8),
11904                ..Default::default()
11905            },
11906        );
11907    });
11908
11909    {
11910        editor.update_in(cx, |editor, window, cx| {
11911            editor.set_text("somehting_new\n", window, cx)
11912        });
11913        assert!(cx.read(|cx| editor.is_dirty(cx)));
11914        let _formatting_request_signal = fake_server
11915            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11916                assert_eq!(
11917                    params.text_document.uri,
11918                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11919                );
11920                assert_eq!(params.options.tab_size, 8);
11921                Ok(Some(vec![]))
11922            });
11923        let save = editor
11924            .update_in(cx, |editor, window, cx| {
11925                editor.save(
11926                    SaveOptions {
11927                        format: true,
11928                        autosave: false,
11929                    },
11930                    project.clone(),
11931                    window,
11932                    cx,
11933                )
11934            })
11935            .unwrap();
11936        cx.executor().start_waiting();
11937        save.await;
11938    }
11939}
11940
11941#[gpui::test]
11942async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11943    init_test(cx, |settings| {
11944        settings.defaults.ensure_final_newline_on_save = Some(false);
11945    });
11946
11947    let fs = FakeFs::new(cx.executor());
11948    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11949
11950    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11951
11952    let buffer = project
11953        .update(cx, |project, cx| {
11954            project.open_local_buffer(path!("/file.txt"), cx)
11955        })
11956        .await
11957        .unwrap();
11958
11959    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11960    let (editor, cx) = cx.add_window_view(|window, cx| {
11961        build_editor_with_project(project.clone(), buffer, window, cx)
11962    });
11963    editor.update_in(cx, |editor, window, cx| {
11964        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11965            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11966        });
11967    });
11968    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11969
11970    editor.update_in(cx, |editor, window, cx| {
11971        editor.handle_input("\n", window, cx)
11972    });
11973    cx.run_until_parked();
11974    save(&editor, &project, cx).await;
11975    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11976
11977    editor.update_in(cx, |editor, window, cx| {
11978        editor.undo(&Default::default(), window, cx);
11979    });
11980    save(&editor, &project, cx).await;
11981    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11982
11983    editor.update_in(cx, |editor, window, cx| {
11984        editor.redo(&Default::default(), window, cx);
11985    });
11986    cx.run_until_parked();
11987    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11988
11989    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11990        let save = editor
11991            .update_in(cx, |editor, window, cx| {
11992                editor.save(
11993                    SaveOptions {
11994                        format: true,
11995                        autosave: false,
11996                    },
11997                    project.clone(),
11998                    window,
11999                    cx,
12000                )
12001            })
12002            .unwrap();
12003        cx.executor().start_waiting();
12004        save.await;
12005        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12006    }
12007}
12008
12009#[gpui::test]
12010async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12011    init_test(cx, |_| {});
12012
12013    let cols = 4;
12014    let rows = 10;
12015    let sample_text_1 = sample_text(rows, cols, 'a');
12016    assert_eq!(
12017        sample_text_1,
12018        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12019    );
12020    let sample_text_2 = sample_text(rows, cols, 'l');
12021    assert_eq!(
12022        sample_text_2,
12023        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12024    );
12025    let sample_text_3 = sample_text(rows, cols, 'v');
12026    assert_eq!(
12027        sample_text_3,
12028        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12029    );
12030
12031    let fs = FakeFs::new(cx.executor());
12032    fs.insert_tree(
12033        path!("/a"),
12034        json!({
12035            "main.rs": sample_text_1,
12036            "other.rs": sample_text_2,
12037            "lib.rs": sample_text_3,
12038        }),
12039    )
12040    .await;
12041
12042    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12043    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12044    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12045
12046    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12047    language_registry.add(rust_lang());
12048    let mut fake_servers = language_registry.register_fake_lsp(
12049        "Rust",
12050        FakeLspAdapter {
12051            capabilities: lsp::ServerCapabilities {
12052                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12053                ..Default::default()
12054            },
12055            ..Default::default()
12056        },
12057    );
12058
12059    let worktree = project.update(cx, |project, cx| {
12060        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12061        assert_eq!(worktrees.len(), 1);
12062        worktrees.pop().unwrap()
12063    });
12064    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12065
12066    let buffer_1 = project
12067        .update(cx, |project, cx| {
12068            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12069        })
12070        .await
12071        .unwrap();
12072    let buffer_2 = project
12073        .update(cx, |project, cx| {
12074            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12075        })
12076        .await
12077        .unwrap();
12078    let buffer_3 = project
12079        .update(cx, |project, cx| {
12080            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12081        })
12082        .await
12083        .unwrap();
12084
12085    let multi_buffer = cx.new(|cx| {
12086        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12087        multi_buffer.push_excerpts(
12088            buffer_1.clone(),
12089            [
12090                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12091                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12092                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12093            ],
12094            cx,
12095        );
12096        multi_buffer.push_excerpts(
12097            buffer_2.clone(),
12098            [
12099                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12100                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12101                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12102            ],
12103            cx,
12104        );
12105        multi_buffer.push_excerpts(
12106            buffer_3.clone(),
12107            [
12108                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12109                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12110                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12111            ],
12112            cx,
12113        );
12114        multi_buffer
12115    });
12116    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12117        Editor::new(
12118            EditorMode::full(),
12119            multi_buffer,
12120            Some(project.clone()),
12121            window,
12122            cx,
12123        )
12124    });
12125
12126    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12127        editor.change_selections(
12128            SelectionEffects::scroll(Autoscroll::Next),
12129            window,
12130            cx,
12131            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12132        );
12133        editor.insert("|one|two|three|", window, cx);
12134    });
12135    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12136    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12137        editor.change_selections(
12138            SelectionEffects::scroll(Autoscroll::Next),
12139            window,
12140            cx,
12141            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12142        );
12143        editor.insert("|four|five|six|", window, cx);
12144    });
12145    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12146
12147    // First two buffers should be edited, but not the third one.
12148    assert_eq!(
12149        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12150        "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}",
12151    );
12152    buffer_1.update(cx, |buffer, _| {
12153        assert!(buffer.is_dirty());
12154        assert_eq!(
12155            buffer.text(),
12156            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12157        )
12158    });
12159    buffer_2.update(cx, |buffer, _| {
12160        assert!(buffer.is_dirty());
12161        assert_eq!(
12162            buffer.text(),
12163            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12164        )
12165    });
12166    buffer_3.update(cx, |buffer, _| {
12167        assert!(!buffer.is_dirty());
12168        assert_eq!(buffer.text(), sample_text_3,)
12169    });
12170    cx.executor().run_until_parked();
12171
12172    cx.executor().start_waiting();
12173    let save = multi_buffer_editor
12174        .update_in(cx, |editor, window, cx| {
12175            editor.save(
12176                SaveOptions {
12177                    format: true,
12178                    autosave: false,
12179                },
12180                project.clone(),
12181                window,
12182                cx,
12183            )
12184        })
12185        .unwrap();
12186
12187    let fake_server = fake_servers.next().await.unwrap();
12188    fake_server
12189        .server
12190        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12191            Ok(Some(vec![lsp::TextEdit::new(
12192                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12193                format!("[{} formatted]", params.text_document.uri),
12194            )]))
12195        })
12196        .detach();
12197    save.await;
12198
12199    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12200    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12201    assert_eq!(
12202        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12203        uri!(
12204            "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}"
12205        ),
12206    );
12207    buffer_1.update(cx, |buffer, _| {
12208        assert!(!buffer.is_dirty());
12209        assert_eq!(
12210            buffer.text(),
12211            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12212        )
12213    });
12214    buffer_2.update(cx, |buffer, _| {
12215        assert!(!buffer.is_dirty());
12216        assert_eq!(
12217            buffer.text(),
12218            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12219        )
12220    });
12221    buffer_3.update(cx, |buffer, _| {
12222        assert!(!buffer.is_dirty());
12223        assert_eq!(buffer.text(), sample_text_3,)
12224    });
12225}
12226
12227#[gpui::test]
12228async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12229    init_test(cx, |_| {});
12230
12231    let fs = FakeFs::new(cx.executor());
12232    fs.insert_tree(
12233        path!("/dir"),
12234        json!({
12235            "file1.rs": "fn main() { println!(\"hello\"); }",
12236            "file2.rs": "fn test() { println!(\"test\"); }",
12237            "file3.rs": "fn other() { println!(\"other\"); }\n",
12238        }),
12239    )
12240    .await;
12241
12242    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12243    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12244    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12245
12246    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12247    language_registry.add(rust_lang());
12248
12249    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12250    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12251
12252    // Open three buffers
12253    let buffer_1 = project
12254        .update(cx, |project, cx| {
12255            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12256        })
12257        .await
12258        .unwrap();
12259    let buffer_2 = project
12260        .update(cx, |project, cx| {
12261            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12262        })
12263        .await
12264        .unwrap();
12265    let buffer_3 = project
12266        .update(cx, |project, cx| {
12267            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12268        })
12269        .await
12270        .unwrap();
12271
12272    // Create a multi-buffer with all three buffers
12273    let multi_buffer = cx.new(|cx| {
12274        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12275        multi_buffer.push_excerpts(
12276            buffer_1.clone(),
12277            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12278            cx,
12279        );
12280        multi_buffer.push_excerpts(
12281            buffer_2.clone(),
12282            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12283            cx,
12284        );
12285        multi_buffer.push_excerpts(
12286            buffer_3.clone(),
12287            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12288            cx,
12289        );
12290        multi_buffer
12291    });
12292
12293    let editor = cx.new_window_entity(|window, cx| {
12294        Editor::new(
12295            EditorMode::full(),
12296            multi_buffer,
12297            Some(project.clone()),
12298            window,
12299            cx,
12300        )
12301    });
12302
12303    // Edit only the first buffer
12304    editor.update_in(cx, |editor, window, cx| {
12305        editor.change_selections(
12306            SelectionEffects::scroll(Autoscroll::Next),
12307            window,
12308            cx,
12309            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12310        );
12311        editor.insert("// edited", window, cx);
12312    });
12313
12314    // Verify that only buffer 1 is dirty
12315    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12316    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12317    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12318
12319    // Get write counts after file creation (files were created with initial content)
12320    // We expect each file to have been written once during creation
12321    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12322    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12323    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12324
12325    // Perform autosave
12326    let save_task = editor.update_in(cx, |editor, window, cx| {
12327        editor.save(
12328            SaveOptions {
12329                format: true,
12330                autosave: true,
12331            },
12332            project.clone(),
12333            window,
12334            cx,
12335        )
12336    });
12337    save_task.await.unwrap();
12338
12339    // Only the dirty buffer should have been saved
12340    assert_eq!(
12341        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12342        1,
12343        "Buffer 1 was dirty, so it should have been written once during autosave"
12344    );
12345    assert_eq!(
12346        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12347        0,
12348        "Buffer 2 was clean, so it should not have been written during autosave"
12349    );
12350    assert_eq!(
12351        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12352        0,
12353        "Buffer 3 was clean, so it should not have been written during autosave"
12354    );
12355
12356    // Verify buffer states after autosave
12357    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12358    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12359    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12360
12361    // Now perform a manual save (format = true)
12362    let save_task = editor.update_in(cx, |editor, window, cx| {
12363        editor.save(
12364            SaveOptions {
12365                format: true,
12366                autosave: false,
12367            },
12368            project.clone(),
12369            window,
12370            cx,
12371        )
12372    });
12373    save_task.await.unwrap();
12374
12375    // During manual save, clean buffers don't get written to disk
12376    // They just get did_save called for language server notifications
12377    assert_eq!(
12378        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12379        1,
12380        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12381    );
12382    assert_eq!(
12383        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12384        0,
12385        "Buffer 2 should not have been written at all"
12386    );
12387    assert_eq!(
12388        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12389        0,
12390        "Buffer 3 should not have been written at all"
12391    );
12392}
12393
12394async fn setup_range_format_test(
12395    cx: &mut TestAppContext,
12396) -> (
12397    Entity<Project>,
12398    Entity<Editor>,
12399    &mut gpui::VisualTestContext,
12400    lsp::FakeLanguageServer,
12401) {
12402    init_test(cx, |_| {});
12403
12404    let fs = FakeFs::new(cx.executor());
12405    fs.insert_file(path!("/file.rs"), Default::default()).await;
12406
12407    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12408
12409    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12410    language_registry.add(rust_lang());
12411    let mut fake_servers = language_registry.register_fake_lsp(
12412        "Rust",
12413        FakeLspAdapter {
12414            capabilities: lsp::ServerCapabilities {
12415                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12416                ..lsp::ServerCapabilities::default()
12417            },
12418            ..FakeLspAdapter::default()
12419        },
12420    );
12421
12422    let buffer = project
12423        .update(cx, |project, cx| {
12424            project.open_local_buffer(path!("/file.rs"), cx)
12425        })
12426        .await
12427        .unwrap();
12428
12429    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12430    let (editor, cx) = cx.add_window_view(|window, cx| {
12431        build_editor_with_project(project.clone(), buffer, window, cx)
12432    });
12433
12434    cx.executor().start_waiting();
12435    let fake_server = fake_servers.next().await.unwrap();
12436
12437    (project, editor, cx, fake_server)
12438}
12439
12440#[gpui::test]
12441async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12442    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12443
12444    editor.update_in(cx, |editor, window, cx| {
12445        editor.set_text("one\ntwo\nthree\n", window, cx)
12446    });
12447    assert!(cx.read(|cx| editor.is_dirty(cx)));
12448
12449    let save = editor
12450        .update_in(cx, |editor, window, cx| {
12451            editor.save(
12452                SaveOptions {
12453                    format: true,
12454                    autosave: false,
12455                },
12456                project.clone(),
12457                window,
12458                cx,
12459            )
12460        })
12461        .unwrap();
12462    fake_server
12463        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12464            assert_eq!(
12465                params.text_document.uri,
12466                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12467            );
12468            assert_eq!(params.options.tab_size, 4);
12469            Ok(Some(vec![lsp::TextEdit::new(
12470                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12471                ", ".to_string(),
12472            )]))
12473        })
12474        .next()
12475        .await;
12476    cx.executor().start_waiting();
12477    save.await;
12478    assert_eq!(
12479        editor.update(cx, |editor, cx| editor.text(cx)),
12480        "one, two\nthree\n"
12481    );
12482    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12483}
12484
12485#[gpui::test]
12486async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12487    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12488
12489    editor.update_in(cx, |editor, window, cx| {
12490        editor.set_text("one\ntwo\nthree\n", window, cx)
12491    });
12492    assert!(cx.read(|cx| editor.is_dirty(cx)));
12493
12494    // Test that save still works when formatting hangs
12495    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12496        move |params, _| async move {
12497            assert_eq!(
12498                params.text_document.uri,
12499                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12500            );
12501            futures::future::pending::<()>().await;
12502            unreachable!()
12503        },
12504    );
12505    let save = editor
12506        .update_in(cx, |editor, window, cx| {
12507            editor.save(
12508                SaveOptions {
12509                    format: true,
12510                    autosave: false,
12511                },
12512                project.clone(),
12513                window,
12514                cx,
12515            )
12516        })
12517        .unwrap();
12518    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12519    cx.executor().start_waiting();
12520    save.await;
12521    assert_eq!(
12522        editor.update(cx, |editor, cx| editor.text(cx)),
12523        "one\ntwo\nthree\n"
12524    );
12525    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12526}
12527
12528#[gpui::test]
12529async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12530    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12531
12532    // Buffer starts clean, no formatting should be requested
12533    let save = editor
12534        .update_in(cx, |editor, window, cx| {
12535            editor.save(
12536                SaveOptions {
12537                    format: false,
12538                    autosave: false,
12539                },
12540                project.clone(),
12541                window,
12542                cx,
12543            )
12544        })
12545        .unwrap();
12546    let _pending_format_request = fake_server
12547        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12548            panic!("Should not be invoked");
12549        })
12550        .next();
12551    cx.executor().start_waiting();
12552    save.await;
12553    cx.run_until_parked();
12554}
12555
12556#[gpui::test]
12557async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12558    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12559
12560    // Set Rust language override and assert overridden tabsize is sent to language server
12561    update_test_language_settings(cx, |settings| {
12562        settings.languages.0.insert(
12563            "Rust".into(),
12564            LanguageSettingsContent {
12565                tab_size: NonZeroU32::new(8),
12566                ..Default::default()
12567            },
12568        );
12569    });
12570
12571    editor.update_in(cx, |editor, window, cx| {
12572        editor.set_text("something_new\n", window, cx)
12573    });
12574    assert!(cx.read(|cx| editor.is_dirty(cx)));
12575    let save = editor
12576        .update_in(cx, |editor, window, cx| {
12577            editor.save(
12578                SaveOptions {
12579                    format: true,
12580                    autosave: false,
12581                },
12582                project.clone(),
12583                window,
12584                cx,
12585            )
12586        })
12587        .unwrap();
12588    fake_server
12589        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12590            assert_eq!(
12591                params.text_document.uri,
12592                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12593            );
12594            assert_eq!(params.options.tab_size, 8);
12595            Ok(Some(Vec::new()))
12596        })
12597        .next()
12598        .await;
12599    save.await;
12600}
12601
12602#[gpui::test]
12603async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12604    init_test(cx, |settings| {
12605        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12606            settings::LanguageServerFormatterSpecifier::Current,
12607        )))
12608    });
12609
12610    let fs = FakeFs::new(cx.executor());
12611    fs.insert_file(path!("/file.rs"), Default::default()).await;
12612
12613    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12614
12615    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12616    language_registry.add(Arc::new(Language::new(
12617        LanguageConfig {
12618            name: "Rust".into(),
12619            matcher: LanguageMatcher {
12620                path_suffixes: vec!["rs".to_string()],
12621                ..Default::default()
12622            },
12623            ..LanguageConfig::default()
12624        },
12625        Some(tree_sitter_rust::LANGUAGE.into()),
12626    )));
12627    update_test_language_settings(cx, |settings| {
12628        // Enable Prettier formatting for the same buffer, and ensure
12629        // LSP is called instead of Prettier.
12630        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12631    });
12632    let mut fake_servers = language_registry.register_fake_lsp(
12633        "Rust",
12634        FakeLspAdapter {
12635            capabilities: lsp::ServerCapabilities {
12636                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12637                ..Default::default()
12638            },
12639            ..Default::default()
12640        },
12641    );
12642
12643    let buffer = project
12644        .update(cx, |project, cx| {
12645            project.open_local_buffer(path!("/file.rs"), cx)
12646        })
12647        .await
12648        .unwrap();
12649
12650    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12651    let (editor, cx) = cx.add_window_view(|window, cx| {
12652        build_editor_with_project(project.clone(), buffer, window, cx)
12653    });
12654    editor.update_in(cx, |editor, window, cx| {
12655        editor.set_text("one\ntwo\nthree\n", window, cx)
12656    });
12657
12658    cx.executor().start_waiting();
12659    let fake_server = fake_servers.next().await.unwrap();
12660
12661    let format = editor
12662        .update_in(cx, |editor, window, cx| {
12663            editor.perform_format(
12664                project.clone(),
12665                FormatTrigger::Manual,
12666                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12667                window,
12668                cx,
12669            )
12670        })
12671        .unwrap();
12672    fake_server
12673        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12674            assert_eq!(
12675                params.text_document.uri,
12676                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12677            );
12678            assert_eq!(params.options.tab_size, 4);
12679            Ok(Some(vec![lsp::TextEdit::new(
12680                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12681                ", ".to_string(),
12682            )]))
12683        })
12684        .next()
12685        .await;
12686    cx.executor().start_waiting();
12687    format.await;
12688    assert_eq!(
12689        editor.update(cx, |editor, cx| editor.text(cx)),
12690        "one, two\nthree\n"
12691    );
12692
12693    editor.update_in(cx, |editor, window, cx| {
12694        editor.set_text("one\ntwo\nthree\n", window, cx)
12695    });
12696    // Ensure we don't lock if formatting hangs.
12697    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12698        move |params, _| async move {
12699            assert_eq!(
12700                params.text_document.uri,
12701                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12702            );
12703            futures::future::pending::<()>().await;
12704            unreachable!()
12705        },
12706    );
12707    let format = editor
12708        .update_in(cx, |editor, window, cx| {
12709            editor.perform_format(
12710                project,
12711                FormatTrigger::Manual,
12712                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12713                window,
12714                cx,
12715            )
12716        })
12717        .unwrap();
12718    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12719    cx.executor().start_waiting();
12720    format.await;
12721    assert_eq!(
12722        editor.update(cx, |editor, cx| editor.text(cx)),
12723        "one\ntwo\nthree\n"
12724    );
12725}
12726
12727#[gpui::test]
12728async fn test_multiple_formatters(cx: &mut TestAppContext) {
12729    init_test(cx, |settings| {
12730        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12731        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12732            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12733            Formatter::CodeAction("code-action-1".into()),
12734            Formatter::CodeAction("code-action-2".into()),
12735        ]))
12736    });
12737
12738    let fs = FakeFs::new(cx.executor());
12739    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12740        .await;
12741
12742    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12743    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12744    language_registry.add(rust_lang());
12745
12746    let mut fake_servers = language_registry.register_fake_lsp(
12747        "Rust",
12748        FakeLspAdapter {
12749            capabilities: lsp::ServerCapabilities {
12750                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12751                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12752                    commands: vec!["the-command-for-code-action-1".into()],
12753                    ..Default::default()
12754                }),
12755                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12756                ..Default::default()
12757            },
12758            ..Default::default()
12759        },
12760    );
12761
12762    let buffer = project
12763        .update(cx, |project, cx| {
12764            project.open_local_buffer(path!("/file.rs"), cx)
12765        })
12766        .await
12767        .unwrap();
12768
12769    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12770    let (editor, cx) = cx.add_window_view(|window, cx| {
12771        build_editor_with_project(project.clone(), buffer, window, cx)
12772    });
12773
12774    cx.executor().start_waiting();
12775
12776    let fake_server = fake_servers.next().await.unwrap();
12777    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12778        move |_params, _| async move {
12779            Ok(Some(vec![lsp::TextEdit::new(
12780                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12781                "applied-formatting\n".to_string(),
12782            )]))
12783        },
12784    );
12785    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12786        move |params, _| async move {
12787            let requested_code_actions = params.context.only.expect("Expected code action request");
12788            assert_eq!(requested_code_actions.len(), 1);
12789
12790            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12791            let code_action = match requested_code_actions[0].as_str() {
12792                "code-action-1" => lsp::CodeAction {
12793                    kind: Some("code-action-1".into()),
12794                    edit: Some(lsp::WorkspaceEdit::new(
12795                        [(
12796                            uri,
12797                            vec![lsp::TextEdit::new(
12798                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12799                                "applied-code-action-1-edit\n".to_string(),
12800                            )],
12801                        )]
12802                        .into_iter()
12803                        .collect(),
12804                    )),
12805                    command: Some(lsp::Command {
12806                        command: "the-command-for-code-action-1".into(),
12807                        ..Default::default()
12808                    }),
12809                    ..Default::default()
12810                },
12811                "code-action-2" => lsp::CodeAction {
12812                    kind: Some("code-action-2".into()),
12813                    edit: Some(lsp::WorkspaceEdit::new(
12814                        [(
12815                            uri,
12816                            vec![lsp::TextEdit::new(
12817                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12818                                "applied-code-action-2-edit\n".to_string(),
12819                            )],
12820                        )]
12821                        .into_iter()
12822                        .collect(),
12823                    )),
12824                    ..Default::default()
12825                },
12826                req => panic!("Unexpected code action request: {:?}", req),
12827            };
12828            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12829                code_action,
12830            )]))
12831        },
12832    );
12833
12834    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12835        move |params, _| async move { Ok(params) }
12836    });
12837
12838    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12839    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12840        let fake = fake_server.clone();
12841        let lock = command_lock.clone();
12842        move |params, _| {
12843            assert_eq!(params.command, "the-command-for-code-action-1");
12844            let fake = fake.clone();
12845            let lock = lock.clone();
12846            async move {
12847                lock.lock().await;
12848                fake.server
12849                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12850                        label: None,
12851                        edit: lsp::WorkspaceEdit {
12852                            changes: Some(
12853                                [(
12854                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12855                                    vec![lsp::TextEdit {
12856                                        range: lsp::Range::new(
12857                                            lsp::Position::new(0, 0),
12858                                            lsp::Position::new(0, 0),
12859                                        ),
12860                                        new_text: "applied-code-action-1-command\n".into(),
12861                                    }],
12862                                )]
12863                                .into_iter()
12864                                .collect(),
12865                            ),
12866                            ..Default::default()
12867                        },
12868                    })
12869                    .await
12870                    .into_response()
12871                    .unwrap();
12872                Ok(Some(json!(null)))
12873            }
12874        }
12875    });
12876
12877    cx.executor().start_waiting();
12878    editor
12879        .update_in(cx, |editor, window, cx| {
12880            editor.perform_format(
12881                project.clone(),
12882                FormatTrigger::Manual,
12883                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12884                window,
12885                cx,
12886            )
12887        })
12888        .unwrap()
12889        .await;
12890    editor.update(cx, |editor, cx| {
12891        assert_eq!(
12892            editor.text(cx),
12893            r#"
12894                applied-code-action-2-edit
12895                applied-code-action-1-command
12896                applied-code-action-1-edit
12897                applied-formatting
12898                one
12899                two
12900                three
12901            "#
12902            .unindent()
12903        );
12904    });
12905
12906    editor.update_in(cx, |editor, window, cx| {
12907        editor.undo(&Default::default(), window, cx);
12908        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12909    });
12910
12911    // Perform a manual edit while waiting for an LSP command
12912    // that's being run as part of a formatting code action.
12913    let lock_guard = command_lock.lock().await;
12914    let format = editor
12915        .update_in(cx, |editor, window, cx| {
12916            editor.perform_format(
12917                project.clone(),
12918                FormatTrigger::Manual,
12919                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12920                window,
12921                cx,
12922            )
12923        })
12924        .unwrap();
12925    cx.run_until_parked();
12926    editor.update(cx, |editor, cx| {
12927        assert_eq!(
12928            editor.text(cx),
12929            r#"
12930                applied-code-action-1-edit
12931                applied-formatting
12932                one
12933                two
12934                three
12935            "#
12936            .unindent()
12937        );
12938
12939        editor.buffer.update(cx, |buffer, cx| {
12940            let ix = buffer.len(cx);
12941            buffer.edit([(ix..ix, "edited\n")], None, cx);
12942        });
12943    });
12944
12945    // Allow the LSP command to proceed. Because the buffer was edited,
12946    // the second code action will not be run.
12947    drop(lock_guard);
12948    format.await;
12949    editor.update_in(cx, |editor, window, cx| {
12950        assert_eq!(
12951            editor.text(cx),
12952            r#"
12953                applied-code-action-1-command
12954                applied-code-action-1-edit
12955                applied-formatting
12956                one
12957                two
12958                three
12959                edited
12960            "#
12961            .unindent()
12962        );
12963
12964        // The manual edit is undone first, because it is the last thing the user did
12965        // (even though the command completed afterwards).
12966        editor.undo(&Default::default(), window, cx);
12967        assert_eq!(
12968            editor.text(cx),
12969            r#"
12970                applied-code-action-1-command
12971                applied-code-action-1-edit
12972                applied-formatting
12973                one
12974                two
12975                three
12976            "#
12977            .unindent()
12978        );
12979
12980        // All the formatting (including the command, which completed after the manual edit)
12981        // is undone together.
12982        editor.undo(&Default::default(), window, cx);
12983        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12984    });
12985}
12986
12987#[gpui::test]
12988async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12989    init_test(cx, |settings| {
12990        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12991            settings::LanguageServerFormatterSpecifier::Current,
12992        )]))
12993    });
12994
12995    let fs = FakeFs::new(cx.executor());
12996    fs.insert_file(path!("/file.ts"), Default::default()).await;
12997
12998    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12999
13000    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13001    language_registry.add(Arc::new(Language::new(
13002        LanguageConfig {
13003            name: "TypeScript".into(),
13004            matcher: LanguageMatcher {
13005                path_suffixes: vec!["ts".to_string()],
13006                ..Default::default()
13007            },
13008            ..LanguageConfig::default()
13009        },
13010        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13011    )));
13012    update_test_language_settings(cx, |settings| {
13013        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13014    });
13015    let mut fake_servers = language_registry.register_fake_lsp(
13016        "TypeScript",
13017        FakeLspAdapter {
13018            capabilities: lsp::ServerCapabilities {
13019                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13020                ..Default::default()
13021            },
13022            ..Default::default()
13023        },
13024    );
13025
13026    let buffer = project
13027        .update(cx, |project, cx| {
13028            project.open_local_buffer(path!("/file.ts"), 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    editor.update_in(cx, |editor, window, cx| {
13038        editor.set_text(
13039            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13040            window,
13041            cx,
13042        )
13043    });
13044
13045    cx.executor().start_waiting();
13046    let fake_server = fake_servers.next().await.unwrap();
13047
13048    let format = editor
13049        .update_in(cx, |editor, window, cx| {
13050            editor.perform_code_action_kind(
13051                project.clone(),
13052                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13053                window,
13054                cx,
13055            )
13056        })
13057        .unwrap();
13058    fake_server
13059        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13060            assert_eq!(
13061                params.text_document.uri,
13062                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13063            );
13064            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13065                lsp::CodeAction {
13066                    title: "Organize Imports".to_string(),
13067                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13068                    edit: Some(lsp::WorkspaceEdit {
13069                        changes: Some(
13070                            [(
13071                                params.text_document.uri.clone(),
13072                                vec![lsp::TextEdit::new(
13073                                    lsp::Range::new(
13074                                        lsp::Position::new(1, 0),
13075                                        lsp::Position::new(2, 0),
13076                                    ),
13077                                    "".to_string(),
13078                                )],
13079                            )]
13080                            .into_iter()
13081                            .collect(),
13082                        ),
13083                        ..Default::default()
13084                    }),
13085                    ..Default::default()
13086                },
13087            )]))
13088        })
13089        .next()
13090        .await;
13091    cx.executor().start_waiting();
13092    format.await;
13093    assert_eq!(
13094        editor.update(cx, |editor, cx| editor.text(cx)),
13095        "import { a } from 'module';\n\nconst x = a;\n"
13096    );
13097
13098    editor.update_in(cx, |editor, window, cx| {
13099        editor.set_text(
13100            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13101            window,
13102            cx,
13103        )
13104    });
13105    // Ensure we don't lock if code action hangs.
13106    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13107        move |params, _| async move {
13108            assert_eq!(
13109                params.text_document.uri,
13110                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13111            );
13112            futures::future::pending::<()>().await;
13113            unreachable!()
13114        },
13115    );
13116    let format = editor
13117        .update_in(cx, |editor, window, cx| {
13118            editor.perform_code_action_kind(
13119                project,
13120                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13121                window,
13122                cx,
13123            )
13124        })
13125        .unwrap();
13126    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13127    cx.executor().start_waiting();
13128    format.await;
13129    assert_eq!(
13130        editor.update(cx, |editor, cx| editor.text(cx)),
13131        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13132    );
13133}
13134
13135#[gpui::test]
13136async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13137    init_test(cx, |_| {});
13138
13139    let mut cx = EditorLspTestContext::new_rust(
13140        lsp::ServerCapabilities {
13141            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13142            ..Default::default()
13143        },
13144        cx,
13145    )
13146    .await;
13147
13148    cx.set_state(indoc! {"
13149        one.twoˇ
13150    "});
13151
13152    // The format request takes a long time. When it completes, it inserts
13153    // a newline and an indent before the `.`
13154    cx.lsp
13155        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13156            let executor = cx.background_executor().clone();
13157            async move {
13158                executor.timer(Duration::from_millis(100)).await;
13159                Ok(Some(vec![lsp::TextEdit {
13160                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13161                    new_text: "\n    ".into(),
13162                }]))
13163            }
13164        });
13165
13166    // Submit a format request.
13167    let format_1 = cx
13168        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13169        .unwrap();
13170    cx.executor().run_until_parked();
13171
13172    // Submit a second format request.
13173    let format_2 = cx
13174        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13175        .unwrap();
13176    cx.executor().run_until_parked();
13177
13178    // Wait for both format requests to complete
13179    cx.executor().advance_clock(Duration::from_millis(200));
13180    cx.executor().start_waiting();
13181    format_1.await.unwrap();
13182    cx.executor().start_waiting();
13183    format_2.await.unwrap();
13184
13185    // The formatting edits only happens once.
13186    cx.assert_editor_state(indoc! {"
13187        one
13188            .twoˇ
13189    "});
13190}
13191
13192#[gpui::test]
13193async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13194    init_test(cx, |settings| {
13195        settings.defaults.formatter = Some(FormatterList::default())
13196    });
13197
13198    let mut cx = EditorLspTestContext::new_rust(
13199        lsp::ServerCapabilities {
13200            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13201            ..Default::default()
13202        },
13203        cx,
13204    )
13205    .await;
13206
13207    // Record which buffer changes have been sent to the language server
13208    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13209    cx.lsp
13210        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13211            let buffer_changes = buffer_changes.clone();
13212            move |params, _| {
13213                buffer_changes.lock().extend(
13214                    params
13215                        .content_changes
13216                        .into_iter()
13217                        .map(|e| (e.range.unwrap(), e.text)),
13218                );
13219            }
13220        });
13221    // Handle formatting requests to the language server.
13222    cx.lsp
13223        .set_request_handler::<lsp::request::Formatting, _, _>({
13224            let buffer_changes = buffer_changes.clone();
13225            move |_, _| {
13226                let buffer_changes = buffer_changes.clone();
13227                // Insert blank lines between each line of the buffer.
13228                async move {
13229                    // When formatting is requested, trailing whitespace has already been stripped,
13230                    // and the trailing newline has already been added.
13231                    assert_eq!(
13232                        &buffer_changes.lock()[1..],
13233                        &[
13234                            (
13235                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13236                                "".into()
13237                            ),
13238                            (
13239                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13240                                "".into()
13241                            ),
13242                            (
13243                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13244                                "\n".into()
13245                            ),
13246                        ]
13247                    );
13248
13249                    Ok(Some(vec![
13250                        lsp::TextEdit {
13251                            range: lsp::Range::new(
13252                                lsp::Position::new(1, 0),
13253                                lsp::Position::new(1, 0),
13254                            ),
13255                            new_text: "\n".into(),
13256                        },
13257                        lsp::TextEdit {
13258                            range: lsp::Range::new(
13259                                lsp::Position::new(2, 0),
13260                                lsp::Position::new(2, 0),
13261                            ),
13262                            new_text: "\n".into(),
13263                        },
13264                    ]))
13265                }
13266            }
13267        });
13268
13269    // Set up a buffer white some trailing whitespace and no trailing newline.
13270    cx.set_state(
13271        &[
13272            "one ",   //
13273            "twoˇ",   //
13274            "three ", //
13275            "four",   //
13276        ]
13277        .join("\n"),
13278    );
13279    cx.run_until_parked();
13280
13281    // Submit a format request.
13282    let format = cx
13283        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13284        .unwrap();
13285
13286    cx.run_until_parked();
13287    // After formatting the buffer, the trailing whitespace is stripped,
13288    // a newline is appended, and the edits provided by the language server
13289    // have been applied.
13290    format.await.unwrap();
13291
13292    cx.assert_editor_state(
13293        &[
13294            "one",   //
13295            "",      //
13296            "twoˇ",  //
13297            "",      //
13298            "three", //
13299            "four",  //
13300            "",      //
13301        ]
13302        .join("\n"),
13303    );
13304
13305    // Undoing the formatting undoes the trailing whitespace removal, the
13306    // trailing newline, and the LSP edits.
13307    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13308    cx.assert_editor_state(
13309        &[
13310            "one ",   //
13311            "twoˇ",   //
13312            "three ", //
13313            "four",   //
13314        ]
13315        .join("\n"),
13316    );
13317}
13318
13319#[gpui::test]
13320async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13321    cx: &mut TestAppContext,
13322) {
13323    init_test(cx, |_| {});
13324
13325    cx.update(|cx| {
13326        cx.update_global::<SettingsStore, _>(|settings, cx| {
13327            settings.update_user_settings(cx, |settings| {
13328                settings.editor.auto_signature_help = Some(true);
13329            });
13330        });
13331    });
13332
13333    let mut cx = EditorLspTestContext::new_rust(
13334        lsp::ServerCapabilities {
13335            signature_help_provider: Some(lsp::SignatureHelpOptions {
13336                ..Default::default()
13337            }),
13338            ..Default::default()
13339        },
13340        cx,
13341    )
13342    .await;
13343
13344    let language = Language::new(
13345        LanguageConfig {
13346            name: "Rust".into(),
13347            brackets: BracketPairConfig {
13348                pairs: vec![
13349                    BracketPair {
13350                        start: "{".to_string(),
13351                        end: "}".to_string(),
13352                        close: true,
13353                        surround: true,
13354                        newline: true,
13355                    },
13356                    BracketPair {
13357                        start: "(".to_string(),
13358                        end: ")".to_string(),
13359                        close: true,
13360                        surround: true,
13361                        newline: true,
13362                    },
13363                    BracketPair {
13364                        start: "/*".to_string(),
13365                        end: " */".to_string(),
13366                        close: true,
13367                        surround: true,
13368                        newline: true,
13369                    },
13370                    BracketPair {
13371                        start: "[".to_string(),
13372                        end: "]".to_string(),
13373                        close: false,
13374                        surround: false,
13375                        newline: true,
13376                    },
13377                    BracketPair {
13378                        start: "\"".to_string(),
13379                        end: "\"".to_string(),
13380                        close: true,
13381                        surround: true,
13382                        newline: false,
13383                    },
13384                    BracketPair {
13385                        start: "<".to_string(),
13386                        end: ">".to_string(),
13387                        close: false,
13388                        surround: true,
13389                        newline: true,
13390                    },
13391                ],
13392                ..Default::default()
13393            },
13394            autoclose_before: "})]".to_string(),
13395            ..Default::default()
13396        },
13397        Some(tree_sitter_rust::LANGUAGE.into()),
13398    );
13399    let language = Arc::new(language);
13400
13401    cx.language_registry().add(language.clone());
13402    cx.update_buffer(|buffer, cx| {
13403        buffer.set_language(Some(language), cx);
13404    });
13405
13406    cx.set_state(
13407        &r#"
13408            fn main() {
13409                sampleˇ
13410            }
13411        "#
13412        .unindent(),
13413    );
13414
13415    cx.update_editor(|editor, window, cx| {
13416        editor.handle_input("(", window, cx);
13417    });
13418    cx.assert_editor_state(
13419        &"
13420            fn main() {
13421                sample(ˇ)
13422            }
13423        "
13424        .unindent(),
13425    );
13426
13427    let mocked_response = lsp::SignatureHelp {
13428        signatures: vec![lsp::SignatureInformation {
13429            label: "fn sample(param1: u8, param2: u8)".to_string(),
13430            documentation: None,
13431            parameters: Some(vec![
13432                lsp::ParameterInformation {
13433                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13434                    documentation: None,
13435                },
13436                lsp::ParameterInformation {
13437                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13438                    documentation: None,
13439                },
13440            ]),
13441            active_parameter: None,
13442        }],
13443        active_signature: Some(0),
13444        active_parameter: Some(0),
13445    };
13446    handle_signature_help_request(&mut cx, mocked_response).await;
13447
13448    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13449        .await;
13450
13451    cx.editor(|editor, _, _| {
13452        let signature_help_state = editor.signature_help_state.popover().cloned();
13453        let signature = signature_help_state.unwrap();
13454        assert_eq!(
13455            signature.signatures[signature.current_signature].label,
13456            "fn sample(param1: u8, param2: u8)"
13457        );
13458    });
13459}
13460
13461#[gpui::test]
13462async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13463    init_test(cx, |_| {});
13464
13465    cx.update(|cx| {
13466        cx.update_global::<SettingsStore, _>(|settings, cx| {
13467            settings.update_user_settings(cx, |settings| {
13468                settings.editor.auto_signature_help = Some(false);
13469                settings.editor.show_signature_help_after_edits = Some(false);
13470            });
13471        });
13472    });
13473
13474    let mut cx = EditorLspTestContext::new_rust(
13475        lsp::ServerCapabilities {
13476            signature_help_provider: Some(lsp::SignatureHelpOptions {
13477                ..Default::default()
13478            }),
13479            ..Default::default()
13480        },
13481        cx,
13482    )
13483    .await;
13484
13485    let language = Language::new(
13486        LanguageConfig {
13487            name: "Rust".into(),
13488            brackets: BracketPairConfig {
13489                pairs: vec![
13490                    BracketPair {
13491                        start: "{".to_string(),
13492                        end: "}".to_string(),
13493                        close: true,
13494                        surround: true,
13495                        newline: true,
13496                    },
13497                    BracketPair {
13498                        start: "(".to_string(),
13499                        end: ")".to_string(),
13500                        close: true,
13501                        surround: true,
13502                        newline: true,
13503                    },
13504                    BracketPair {
13505                        start: "/*".to_string(),
13506                        end: " */".to_string(),
13507                        close: true,
13508                        surround: true,
13509                        newline: true,
13510                    },
13511                    BracketPair {
13512                        start: "[".to_string(),
13513                        end: "]".to_string(),
13514                        close: false,
13515                        surround: false,
13516                        newline: true,
13517                    },
13518                    BracketPair {
13519                        start: "\"".to_string(),
13520                        end: "\"".to_string(),
13521                        close: true,
13522                        surround: true,
13523                        newline: false,
13524                    },
13525                    BracketPair {
13526                        start: "<".to_string(),
13527                        end: ">".to_string(),
13528                        close: false,
13529                        surround: true,
13530                        newline: true,
13531                    },
13532                ],
13533                ..Default::default()
13534            },
13535            autoclose_before: "})]".to_string(),
13536            ..Default::default()
13537        },
13538        Some(tree_sitter_rust::LANGUAGE.into()),
13539    );
13540    let language = Arc::new(language);
13541
13542    cx.language_registry().add(language.clone());
13543    cx.update_buffer(|buffer, cx| {
13544        buffer.set_language(Some(language), cx);
13545    });
13546
13547    // Ensure that signature_help is not called when no signature help is enabled.
13548    cx.set_state(
13549        &r#"
13550            fn main() {
13551                sampleˇ
13552            }
13553        "#
13554        .unindent(),
13555    );
13556    cx.update_editor(|editor, window, cx| {
13557        editor.handle_input("(", window, cx);
13558    });
13559    cx.assert_editor_state(
13560        &"
13561            fn main() {
13562                sample(ˇ)
13563            }
13564        "
13565        .unindent(),
13566    );
13567    cx.editor(|editor, _, _| {
13568        assert!(editor.signature_help_state.task().is_none());
13569    });
13570
13571    let mocked_response = lsp::SignatureHelp {
13572        signatures: vec![lsp::SignatureInformation {
13573            label: "fn sample(param1: u8, param2: u8)".to_string(),
13574            documentation: None,
13575            parameters: Some(vec![
13576                lsp::ParameterInformation {
13577                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13578                    documentation: None,
13579                },
13580                lsp::ParameterInformation {
13581                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13582                    documentation: None,
13583                },
13584            ]),
13585            active_parameter: None,
13586        }],
13587        active_signature: Some(0),
13588        active_parameter: Some(0),
13589    };
13590
13591    // Ensure that signature_help is called when enabled afte edits
13592    cx.update(|_, cx| {
13593        cx.update_global::<SettingsStore, _>(|settings, cx| {
13594            settings.update_user_settings(cx, |settings| {
13595                settings.editor.auto_signature_help = Some(false);
13596                settings.editor.show_signature_help_after_edits = Some(true);
13597            });
13598        });
13599    });
13600    cx.set_state(
13601        &r#"
13602            fn main() {
13603                sampleˇ
13604            }
13605        "#
13606        .unindent(),
13607    );
13608    cx.update_editor(|editor, window, cx| {
13609        editor.handle_input("(", window, cx);
13610    });
13611    cx.assert_editor_state(
13612        &"
13613            fn main() {
13614                sample(ˇ)
13615            }
13616        "
13617        .unindent(),
13618    );
13619    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13620    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13621        .await;
13622    cx.update_editor(|editor, _, _| {
13623        let signature_help_state = editor.signature_help_state.popover().cloned();
13624        assert!(signature_help_state.is_some());
13625        let signature = signature_help_state.unwrap();
13626        assert_eq!(
13627            signature.signatures[signature.current_signature].label,
13628            "fn sample(param1: u8, param2: u8)"
13629        );
13630        editor.signature_help_state = SignatureHelpState::default();
13631    });
13632
13633    // Ensure that signature_help is called when auto signature help override is enabled
13634    cx.update(|_, cx| {
13635        cx.update_global::<SettingsStore, _>(|settings, cx| {
13636            settings.update_user_settings(cx, |settings| {
13637                settings.editor.auto_signature_help = Some(true);
13638                settings.editor.show_signature_help_after_edits = Some(false);
13639            });
13640        });
13641    });
13642    cx.set_state(
13643        &r#"
13644            fn main() {
13645                sampleˇ
13646            }
13647        "#
13648        .unindent(),
13649    );
13650    cx.update_editor(|editor, window, cx| {
13651        editor.handle_input("(", window, cx);
13652    });
13653    cx.assert_editor_state(
13654        &"
13655            fn main() {
13656                sample(ˇ)
13657            }
13658        "
13659        .unindent(),
13660    );
13661    handle_signature_help_request(&mut cx, mocked_response).await;
13662    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13663        .await;
13664    cx.editor(|editor, _, _| {
13665        let signature_help_state = editor.signature_help_state.popover().cloned();
13666        assert!(signature_help_state.is_some());
13667        let signature = signature_help_state.unwrap();
13668        assert_eq!(
13669            signature.signatures[signature.current_signature].label,
13670            "fn sample(param1: u8, param2: u8)"
13671        );
13672    });
13673}
13674
13675#[gpui::test]
13676async fn test_signature_help(cx: &mut TestAppContext) {
13677    init_test(cx, |_| {});
13678    cx.update(|cx| {
13679        cx.update_global::<SettingsStore, _>(|settings, cx| {
13680            settings.update_user_settings(cx, |settings| {
13681                settings.editor.auto_signature_help = Some(true);
13682            });
13683        });
13684    });
13685
13686    let mut cx = EditorLspTestContext::new_rust(
13687        lsp::ServerCapabilities {
13688            signature_help_provider: Some(lsp::SignatureHelpOptions {
13689                ..Default::default()
13690            }),
13691            ..Default::default()
13692        },
13693        cx,
13694    )
13695    .await;
13696
13697    // A test that directly calls `show_signature_help`
13698    cx.update_editor(|editor, window, cx| {
13699        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13700    });
13701
13702    let mocked_response = lsp::SignatureHelp {
13703        signatures: vec![lsp::SignatureInformation {
13704            label: "fn sample(param1: u8, param2: u8)".to_string(),
13705            documentation: None,
13706            parameters: Some(vec![
13707                lsp::ParameterInformation {
13708                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13709                    documentation: None,
13710                },
13711                lsp::ParameterInformation {
13712                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13713                    documentation: None,
13714                },
13715            ]),
13716            active_parameter: None,
13717        }],
13718        active_signature: Some(0),
13719        active_parameter: Some(0),
13720    };
13721    handle_signature_help_request(&mut cx, mocked_response).await;
13722
13723    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13724        .await;
13725
13726    cx.editor(|editor, _, _| {
13727        let signature_help_state = editor.signature_help_state.popover().cloned();
13728        assert!(signature_help_state.is_some());
13729        let signature = signature_help_state.unwrap();
13730        assert_eq!(
13731            signature.signatures[signature.current_signature].label,
13732            "fn sample(param1: u8, param2: u8)"
13733        );
13734    });
13735
13736    // When exiting outside from inside the brackets, `signature_help` is closed.
13737    cx.set_state(indoc! {"
13738        fn main() {
13739            sample(ˇ);
13740        }
13741
13742        fn sample(param1: u8, param2: u8) {}
13743    "});
13744
13745    cx.update_editor(|editor, window, cx| {
13746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13747            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13748        });
13749    });
13750
13751    let mocked_response = lsp::SignatureHelp {
13752        signatures: Vec::new(),
13753        active_signature: None,
13754        active_parameter: None,
13755    };
13756    handle_signature_help_request(&mut cx, mocked_response).await;
13757
13758    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13759        .await;
13760
13761    cx.editor(|editor, _, _| {
13762        assert!(!editor.signature_help_state.is_shown());
13763    });
13764
13765    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13766    cx.set_state(indoc! {"
13767        fn main() {
13768            sample(ˇ);
13769        }
13770
13771        fn sample(param1: u8, param2: u8) {}
13772    "});
13773
13774    let mocked_response = lsp::SignatureHelp {
13775        signatures: vec![lsp::SignatureInformation {
13776            label: "fn sample(param1: u8, param2: u8)".to_string(),
13777            documentation: None,
13778            parameters: Some(vec![
13779                lsp::ParameterInformation {
13780                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13781                    documentation: None,
13782                },
13783                lsp::ParameterInformation {
13784                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13785                    documentation: None,
13786                },
13787            ]),
13788            active_parameter: None,
13789        }],
13790        active_signature: Some(0),
13791        active_parameter: Some(0),
13792    };
13793    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13794    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13795        .await;
13796    cx.editor(|editor, _, _| {
13797        assert!(editor.signature_help_state.is_shown());
13798    });
13799
13800    // Restore the popover with more parameter input
13801    cx.set_state(indoc! {"
13802        fn main() {
13803            sample(param1, param2ˇ);
13804        }
13805
13806        fn sample(param1: u8, param2: u8) {}
13807    "});
13808
13809    let mocked_response = lsp::SignatureHelp {
13810        signatures: vec![lsp::SignatureInformation {
13811            label: "fn sample(param1: u8, param2: u8)".to_string(),
13812            documentation: None,
13813            parameters: Some(vec![
13814                lsp::ParameterInformation {
13815                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13816                    documentation: None,
13817                },
13818                lsp::ParameterInformation {
13819                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13820                    documentation: None,
13821                },
13822            ]),
13823            active_parameter: None,
13824        }],
13825        active_signature: Some(0),
13826        active_parameter: Some(1),
13827    };
13828    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13829    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13830        .await;
13831
13832    // When selecting a range, the popover is gone.
13833    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13834    cx.update_editor(|editor, window, cx| {
13835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13836            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13837        })
13838    });
13839    cx.assert_editor_state(indoc! {"
13840        fn main() {
13841            sample(param1, «ˇparam2»);
13842        }
13843
13844        fn sample(param1: u8, param2: u8) {}
13845    "});
13846    cx.editor(|editor, _, _| {
13847        assert!(!editor.signature_help_state.is_shown());
13848    });
13849
13850    // When unselecting again, the popover is back if within the brackets.
13851    cx.update_editor(|editor, window, cx| {
13852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13853            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13854        })
13855    });
13856    cx.assert_editor_state(indoc! {"
13857        fn main() {
13858            sample(param1, ˇparam2);
13859        }
13860
13861        fn sample(param1: u8, param2: u8) {}
13862    "});
13863    handle_signature_help_request(&mut cx, mocked_response).await;
13864    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13865        .await;
13866    cx.editor(|editor, _, _| {
13867        assert!(editor.signature_help_state.is_shown());
13868    });
13869
13870    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13871    cx.update_editor(|editor, window, cx| {
13872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13873            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13874            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13875        })
13876    });
13877    cx.assert_editor_state(indoc! {"
13878        fn main() {
13879            sample(param1, ˇparam2);
13880        }
13881
13882        fn sample(param1: u8, param2: u8) {}
13883    "});
13884
13885    let mocked_response = lsp::SignatureHelp {
13886        signatures: vec![lsp::SignatureInformation {
13887            label: "fn sample(param1: u8, param2: u8)".to_string(),
13888            documentation: None,
13889            parameters: Some(vec![
13890                lsp::ParameterInformation {
13891                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13892                    documentation: None,
13893                },
13894                lsp::ParameterInformation {
13895                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13896                    documentation: None,
13897                },
13898            ]),
13899            active_parameter: None,
13900        }],
13901        active_signature: Some(0),
13902        active_parameter: Some(1),
13903    };
13904    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13905    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13906        .await;
13907    cx.update_editor(|editor, _, cx| {
13908        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13909    });
13910    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13911        .await;
13912    cx.update_editor(|editor, window, cx| {
13913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13914            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13915        })
13916    });
13917    cx.assert_editor_state(indoc! {"
13918        fn main() {
13919            sample(param1, «ˇparam2»);
13920        }
13921
13922        fn sample(param1: u8, param2: u8) {}
13923    "});
13924    cx.update_editor(|editor, window, cx| {
13925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13926            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13927        })
13928    });
13929    cx.assert_editor_state(indoc! {"
13930        fn main() {
13931            sample(param1, ˇparam2);
13932        }
13933
13934        fn sample(param1: u8, param2: u8) {}
13935    "});
13936    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13937        .await;
13938}
13939
13940#[gpui::test]
13941async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13942    init_test(cx, |_| {});
13943
13944    let mut cx = EditorLspTestContext::new_rust(
13945        lsp::ServerCapabilities {
13946            signature_help_provider: Some(lsp::SignatureHelpOptions {
13947                ..Default::default()
13948            }),
13949            ..Default::default()
13950        },
13951        cx,
13952    )
13953    .await;
13954
13955    cx.set_state(indoc! {"
13956        fn main() {
13957            overloadedˇ
13958        }
13959    "});
13960
13961    cx.update_editor(|editor, window, cx| {
13962        editor.handle_input("(", window, cx);
13963        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13964    });
13965
13966    // Mock response with 3 signatures
13967    let mocked_response = lsp::SignatureHelp {
13968        signatures: vec![
13969            lsp::SignatureInformation {
13970                label: "fn overloaded(x: i32)".to_string(),
13971                documentation: None,
13972                parameters: Some(vec![lsp::ParameterInformation {
13973                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13974                    documentation: None,
13975                }]),
13976                active_parameter: None,
13977            },
13978            lsp::SignatureInformation {
13979                label: "fn overloaded(x: i32, y: i32)".to_string(),
13980                documentation: None,
13981                parameters: Some(vec![
13982                    lsp::ParameterInformation {
13983                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13984                        documentation: None,
13985                    },
13986                    lsp::ParameterInformation {
13987                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13988                        documentation: None,
13989                    },
13990                ]),
13991                active_parameter: None,
13992            },
13993            lsp::SignatureInformation {
13994                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13995                documentation: None,
13996                parameters: Some(vec![
13997                    lsp::ParameterInformation {
13998                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13999                        documentation: None,
14000                    },
14001                    lsp::ParameterInformation {
14002                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14003                        documentation: None,
14004                    },
14005                    lsp::ParameterInformation {
14006                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14007                        documentation: None,
14008                    },
14009                ]),
14010                active_parameter: None,
14011            },
14012        ],
14013        active_signature: Some(1),
14014        active_parameter: Some(0),
14015    };
14016    handle_signature_help_request(&mut cx, mocked_response).await;
14017
14018    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14019        .await;
14020
14021    // Verify we have multiple signatures and the right one is selected
14022    cx.editor(|editor, _, _| {
14023        let popover = editor.signature_help_state.popover().cloned().unwrap();
14024        assert_eq!(popover.signatures.len(), 3);
14025        // active_signature was 1, so that should be the current
14026        assert_eq!(popover.current_signature, 1);
14027        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14028        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14029        assert_eq!(
14030            popover.signatures[2].label,
14031            "fn overloaded(x: i32, y: i32, z: i32)"
14032        );
14033    });
14034
14035    // Test navigation functionality
14036    cx.update_editor(|editor, window, cx| {
14037        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14038    });
14039
14040    cx.editor(|editor, _, _| {
14041        let popover = editor.signature_help_state.popover().cloned().unwrap();
14042        assert_eq!(popover.current_signature, 2);
14043    });
14044
14045    // Test wrap around
14046    cx.update_editor(|editor, window, cx| {
14047        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14048    });
14049
14050    cx.editor(|editor, _, _| {
14051        let popover = editor.signature_help_state.popover().cloned().unwrap();
14052        assert_eq!(popover.current_signature, 0);
14053    });
14054
14055    // Test previous navigation
14056    cx.update_editor(|editor, window, cx| {
14057        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14058    });
14059
14060    cx.editor(|editor, _, _| {
14061        let popover = editor.signature_help_state.popover().cloned().unwrap();
14062        assert_eq!(popover.current_signature, 2);
14063    });
14064}
14065
14066#[gpui::test]
14067async fn test_completion_mode(cx: &mut TestAppContext) {
14068    init_test(cx, |_| {});
14069    let mut cx = EditorLspTestContext::new_rust(
14070        lsp::ServerCapabilities {
14071            completion_provider: Some(lsp::CompletionOptions {
14072                resolve_provider: Some(true),
14073                ..Default::default()
14074            }),
14075            ..Default::default()
14076        },
14077        cx,
14078    )
14079    .await;
14080
14081    struct Run {
14082        run_description: &'static str,
14083        initial_state: String,
14084        buffer_marked_text: String,
14085        completion_label: &'static str,
14086        completion_text: &'static str,
14087        expected_with_insert_mode: String,
14088        expected_with_replace_mode: String,
14089        expected_with_replace_subsequence_mode: String,
14090        expected_with_replace_suffix_mode: String,
14091    }
14092
14093    let runs = [
14094        Run {
14095            run_description: "Start of word matches completion text",
14096            initial_state: "before ediˇ after".into(),
14097            buffer_marked_text: "before <edi|> after".into(),
14098            completion_label: "editor",
14099            completion_text: "editor",
14100            expected_with_insert_mode: "before editorˇ after".into(),
14101            expected_with_replace_mode: "before editorˇ after".into(),
14102            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14103            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14104        },
14105        Run {
14106            run_description: "Accept same text at the middle of the word",
14107            initial_state: "before ediˇtor after".into(),
14108            buffer_marked_text: "before <edi|tor> after".into(),
14109            completion_label: "editor",
14110            completion_text: "editor",
14111            expected_with_insert_mode: "before editorˇtor after".into(),
14112            expected_with_replace_mode: "before editorˇ after".into(),
14113            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14114            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14115        },
14116        Run {
14117            run_description: "End of word matches completion text -- cursor at end",
14118            initial_state: "before torˇ after".into(),
14119            buffer_marked_text: "before <tor|> after".into(),
14120            completion_label: "editor",
14121            completion_text: "editor",
14122            expected_with_insert_mode: "before editorˇ after".into(),
14123            expected_with_replace_mode: "before editorˇ after".into(),
14124            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14125            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14126        },
14127        Run {
14128            run_description: "End of word matches completion text -- cursor at start",
14129            initial_state: "before ˇtor after".into(),
14130            buffer_marked_text: "before <|tor> after".into(),
14131            completion_label: "editor",
14132            completion_text: "editor",
14133            expected_with_insert_mode: "before editorˇtor after".into(),
14134            expected_with_replace_mode: "before editorˇ after".into(),
14135            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14136            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14137        },
14138        Run {
14139            run_description: "Prepend text containing whitespace",
14140            initial_state: "pˇfield: bool".into(),
14141            buffer_marked_text: "<p|field>: bool".into(),
14142            completion_label: "pub ",
14143            completion_text: "pub ",
14144            expected_with_insert_mode: "pub ˇfield: bool".into(),
14145            expected_with_replace_mode: "pub ˇ: bool".into(),
14146            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14147            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14148        },
14149        Run {
14150            run_description: "Add element to start of list",
14151            initial_state: "[element_ˇelement_2]".into(),
14152            buffer_marked_text: "[<element_|element_2>]".into(),
14153            completion_label: "element_1",
14154            completion_text: "element_1",
14155            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14156            expected_with_replace_mode: "[element_1ˇ]".into(),
14157            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14158            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14159        },
14160        Run {
14161            run_description: "Add element to start of list -- first and second elements are equal",
14162            initial_state: "[elˇelement]".into(),
14163            buffer_marked_text: "[<el|element>]".into(),
14164            completion_label: "element",
14165            completion_text: "element",
14166            expected_with_insert_mode: "[elementˇelement]".into(),
14167            expected_with_replace_mode: "[elementˇ]".into(),
14168            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14169            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14170        },
14171        Run {
14172            run_description: "Ends with matching suffix",
14173            initial_state: "SubˇError".into(),
14174            buffer_marked_text: "<Sub|Error>".into(),
14175            completion_label: "SubscriptionError",
14176            completion_text: "SubscriptionError",
14177            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14178            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14179            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14180            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14181        },
14182        Run {
14183            run_description: "Suffix is a subsequence -- contiguous",
14184            initial_state: "SubˇErr".into(),
14185            buffer_marked_text: "<Sub|Err>".into(),
14186            completion_label: "SubscriptionError",
14187            completion_text: "SubscriptionError",
14188            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14189            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14190            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14191            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14192        },
14193        Run {
14194            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14195            initial_state: "Suˇscrirr".into(),
14196            buffer_marked_text: "<Su|scrirr>".into(),
14197            completion_label: "SubscriptionError",
14198            completion_text: "SubscriptionError",
14199            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14200            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14201            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14202            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14203        },
14204        Run {
14205            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14206            initial_state: "foo(indˇix)".into(),
14207            buffer_marked_text: "foo(<ind|ix>)".into(),
14208            completion_label: "node_index",
14209            completion_text: "node_index",
14210            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14211            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14212            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14213            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14214        },
14215        Run {
14216            run_description: "Replace range ends before cursor - should extend to cursor",
14217            initial_state: "before editˇo after".into(),
14218            buffer_marked_text: "before <{ed}>it|o after".into(),
14219            completion_label: "editor",
14220            completion_text: "editor",
14221            expected_with_insert_mode: "before editorˇo after".into(),
14222            expected_with_replace_mode: "before editorˇo after".into(),
14223            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14224            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14225        },
14226        Run {
14227            run_description: "Uses label for suffix matching",
14228            initial_state: "before ediˇtor after".into(),
14229            buffer_marked_text: "before <edi|tor> after".into(),
14230            completion_label: "editor",
14231            completion_text: "editor()",
14232            expected_with_insert_mode: "before editor()ˇtor after".into(),
14233            expected_with_replace_mode: "before editor()ˇ after".into(),
14234            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14235            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14236        },
14237        Run {
14238            run_description: "Case insensitive subsequence and suffix matching",
14239            initial_state: "before EDiˇtoR after".into(),
14240            buffer_marked_text: "before <EDi|toR> after".into(),
14241            completion_label: "editor",
14242            completion_text: "editor",
14243            expected_with_insert_mode: "before editorˇtoR after".into(),
14244            expected_with_replace_mode: "before editorˇ after".into(),
14245            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14246            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14247        },
14248    ];
14249
14250    for run in runs {
14251        let run_variations = [
14252            (LspInsertMode::Insert, run.expected_with_insert_mode),
14253            (LspInsertMode::Replace, run.expected_with_replace_mode),
14254            (
14255                LspInsertMode::ReplaceSubsequence,
14256                run.expected_with_replace_subsequence_mode,
14257            ),
14258            (
14259                LspInsertMode::ReplaceSuffix,
14260                run.expected_with_replace_suffix_mode,
14261            ),
14262        ];
14263
14264        for (lsp_insert_mode, expected_text) in run_variations {
14265            eprintln!(
14266                "run = {:?}, mode = {lsp_insert_mode:.?}",
14267                run.run_description,
14268            );
14269
14270            update_test_language_settings(&mut cx, |settings| {
14271                settings.defaults.completions = Some(CompletionSettingsContent {
14272                    lsp_insert_mode: Some(lsp_insert_mode),
14273                    words: Some(WordsCompletionMode::Disabled),
14274                    words_min_length: Some(0),
14275                    ..Default::default()
14276                });
14277            });
14278
14279            cx.set_state(&run.initial_state);
14280            cx.update_editor(|editor, window, cx| {
14281                editor.show_completions(&ShowCompletions, window, cx);
14282            });
14283
14284            let counter = Arc::new(AtomicUsize::new(0));
14285            handle_completion_request_with_insert_and_replace(
14286                &mut cx,
14287                &run.buffer_marked_text,
14288                vec![(run.completion_label, run.completion_text)],
14289                counter.clone(),
14290            )
14291            .await;
14292            cx.condition(|editor, _| editor.context_menu_visible())
14293                .await;
14294            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14295
14296            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14297                editor
14298                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14299                    .unwrap()
14300            });
14301            cx.assert_editor_state(&expected_text);
14302            handle_resolve_completion_request(&mut cx, None).await;
14303            apply_additional_edits.await.unwrap();
14304        }
14305    }
14306}
14307
14308#[gpui::test]
14309async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14310    init_test(cx, |_| {});
14311    let mut cx = EditorLspTestContext::new_rust(
14312        lsp::ServerCapabilities {
14313            completion_provider: Some(lsp::CompletionOptions {
14314                resolve_provider: Some(true),
14315                ..Default::default()
14316            }),
14317            ..Default::default()
14318        },
14319        cx,
14320    )
14321    .await;
14322
14323    let initial_state = "SubˇError";
14324    let buffer_marked_text = "<Sub|Error>";
14325    let completion_text = "SubscriptionError";
14326    let expected_with_insert_mode = "SubscriptionErrorˇError";
14327    let expected_with_replace_mode = "SubscriptionErrorˇ";
14328
14329    update_test_language_settings(&mut cx, |settings| {
14330        settings.defaults.completions = Some(CompletionSettingsContent {
14331            words: Some(WordsCompletionMode::Disabled),
14332            words_min_length: Some(0),
14333            // set the opposite here to ensure that the action is overriding the default behavior
14334            lsp_insert_mode: Some(LspInsertMode::Insert),
14335            ..Default::default()
14336        });
14337    });
14338
14339    cx.set_state(initial_state);
14340    cx.update_editor(|editor, window, cx| {
14341        editor.show_completions(&ShowCompletions, window, cx);
14342    });
14343
14344    let counter = Arc::new(AtomicUsize::new(0));
14345    handle_completion_request_with_insert_and_replace(
14346        &mut cx,
14347        buffer_marked_text,
14348        vec![(completion_text, completion_text)],
14349        counter.clone(),
14350    )
14351    .await;
14352    cx.condition(|editor, _| editor.context_menu_visible())
14353        .await;
14354    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14355
14356    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14357        editor
14358            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14359            .unwrap()
14360    });
14361    cx.assert_editor_state(expected_with_replace_mode);
14362    handle_resolve_completion_request(&mut cx, None).await;
14363    apply_additional_edits.await.unwrap();
14364
14365    update_test_language_settings(&mut cx, |settings| {
14366        settings.defaults.completions = Some(CompletionSettingsContent {
14367            words: Some(WordsCompletionMode::Disabled),
14368            words_min_length: Some(0),
14369            // set the opposite here to ensure that the action is overriding the default behavior
14370            lsp_insert_mode: Some(LspInsertMode::Replace),
14371            ..Default::default()
14372        });
14373    });
14374
14375    cx.set_state(initial_state);
14376    cx.update_editor(|editor, window, cx| {
14377        editor.show_completions(&ShowCompletions, window, cx);
14378    });
14379    handle_completion_request_with_insert_and_replace(
14380        &mut cx,
14381        buffer_marked_text,
14382        vec![(completion_text, completion_text)],
14383        counter.clone(),
14384    )
14385    .await;
14386    cx.condition(|editor, _| editor.context_menu_visible())
14387        .await;
14388    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14389
14390    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14391        editor
14392            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14393            .unwrap()
14394    });
14395    cx.assert_editor_state(expected_with_insert_mode);
14396    handle_resolve_completion_request(&mut cx, None).await;
14397    apply_additional_edits.await.unwrap();
14398}
14399
14400#[gpui::test]
14401async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14402    init_test(cx, |_| {});
14403    let mut cx = EditorLspTestContext::new_rust(
14404        lsp::ServerCapabilities {
14405            completion_provider: Some(lsp::CompletionOptions {
14406                resolve_provider: Some(true),
14407                ..Default::default()
14408            }),
14409            ..Default::default()
14410        },
14411        cx,
14412    )
14413    .await;
14414
14415    // scenario: surrounding text matches completion text
14416    let completion_text = "to_offset";
14417    let initial_state = indoc! {"
14418        1. buf.to_offˇsuffix
14419        2. buf.to_offˇsuf
14420        3. buf.to_offˇfix
14421        4. buf.to_offˇ
14422        5. into_offˇensive
14423        6. ˇsuffix
14424        7. let ˇ //
14425        8. aaˇzz
14426        9. buf.to_off«zzzzzˇ»suffix
14427        10. buf.«ˇzzzzz»suffix
14428        11. to_off«ˇzzzzz»
14429
14430        buf.to_offˇsuffix  // newest cursor
14431    "};
14432    let completion_marked_buffer = indoc! {"
14433        1. buf.to_offsuffix
14434        2. buf.to_offsuf
14435        3. buf.to_offfix
14436        4. buf.to_off
14437        5. into_offensive
14438        6. suffix
14439        7. let  //
14440        8. aazz
14441        9. buf.to_offzzzzzsuffix
14442        10. buf.zzzzzsuffix
14443        11. to_offzzzzz
14444
14445        buf.<to_off|suffix>  // newest cursor
14446    "};
14447    let expected = indoc! {"
14448        1. buf.to_offsetˇ
14449        2. buf.to_offsetˇsuf
14450        3. buf.to_offsetˇfix
14451        4. buf.to_offsetˇ
14452        5. into_offsetˇensive
14453        6. to_offsetˇsuffix
14454        7. let to_offsetˇ //
14455        8. aato_offsetˇzz
14456        9. buf.to_offsetˇ
14457        10. buf.to_offsetˇsuffix
14458        11. to_offsetˇ
14459
14460        buf.to_offsetˇ  // newest cursor
14461    "};
14462    cx.set_state(initial_state);
14463    cx.update_editor(|editor, window, cx| {
14464        editor.show_completions(&ShowCompletions, window, cx);
14465    });
14466    handle_completion_request_with_insert_and_replace(
14467        &mut cx,
14468        completion_marked_buffer,
14469        vec![(completion_text, completion_text)],
14470        Arc::new(AtomicUsize::new(0)),
14471    )
14472    .await;
14473    cx.condition(|editor, _| editor.context_menu_visible())
14474        .await;
14475    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14476        editor
14477            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14478            .unwrap()
14479    });
14480    cx.assert_editor_state(expected);
14481    handle_resolve_completion_request(&mut cx, None).await;
14482    apply_additional_edits.await.unwrap();
14483
14484    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14485    let completion_text = "foo_and_bar";
14486    let initial_state = indoc! {"
14487        1. ooanbˇ
14488        2. zooanbˇ
14489        3. ooanbˇz
14490        4. zooanbˇz
14491        5. ooanˇ
14492        6. oanbˇ
14493
14494        ooanbˇ
14495    "};
14496    let completion_marked_buffer = indoc! {"
14497        1. ooanb
14498        2. zooanb
14499        3. ooanbz
14500        4. zooanbz
14501        5. ooan
14502        6. oanb
14503
14504        <ooanb|>
14505    "};
14506    let expected = indoc! {"
14507        1. foo_and_barˇ
14508        2. zfoo_and_barˇ
14509        3. foo_and_barˇz
14510        4. zfoo_and_barˇz
14511        5. ooanfoo_and_barˇ
14512        6. oanbfoo_and_barˇ
14513
14514        foo_and_barˇ
14515    "};
14516    cx.set_state(initial_state);
14517    cx.update_editor(|editor, window, cx| {
14518        editor.show_completions(&ShowCompletions, window, cx);
14519    });
14520    handle_completion_request_with_insert_and_replace(
14521        &mut cx,
14522        completion_marked_buffer,
14523        vec![(completion_text, completion_text)],
14524        Arc::new(AtomicUsize::new(0)),
14525    )
14526    .await;
14527    cx.condition(|editor, _| editor.context_menu_visible())
14528        .await;
14529    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14530        editor
14531            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14532            .unwrap()
14533    });
14534    cx.assert_editor_state(expected);
14535    handle_resolve_completion_request(&mut cx, None).await;
14536    apply_additional_edits.await.unwrap();
14537
14538    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14539    // (expects the same as if it was inserted at the end)
14540    let completion_text = "foo_and_bar";
14541    let initial_state = indoc! {"
14542        1. ooˇanb
14543        2. zooˇanb
14544        3. ooˇanbz
14545        4. zooˇanbz
14546
14547        ooˇanb
14548    "};
14549    let completion_marked_buffer = indoc! {"
14550        1. ooanb
14551        2. zooanb
14552        3. ooanbz
14553        4. zooanbz
14554
14555        <oo|anb>
14556    "};
14557    let expected = indoc! {"
14558        1. foo_and_barˇ
14559        2. zfoo_and_barˇ
14560        3. foo_and_barˇz
14561        4. zfoo_and_barˇz
14562
14563        foo_and_barˇ
14564    "};
14565    cx.set_state(initial_state);
14566    cx.update_editor(|editor, window, cx| {
14567        editor.show_completions(&ShowCompletions, window, cx);
14568    });
14569    handle_completion_request_with_insert_and_replace(
14570        &mut cx,
14571        completion_marked_buffer,
14572        vec![(completion_text, completion_text)],
14573        Arc::new(AtomicUsize::new(0)),
14574    )
14575    .await;
14576    cx.condition(|editor, _| editor.context_menu_visible())
14577        .await;
14578    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14579        editor
14580            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14581            .unwrap()
14582    });
14583    cx.assert_editor_state(expected);
14584    handle_resolve_completion_request(&mut cx, None).await;
14585    apply_additional_edits.await.unwrap();
14586}
14587
14588// This used to crash
14589#[gpui::test]
14590async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14591    init_test(cx, |_| {});
14592
14593    let buffer_text = indoc! {"
14594        fn main() {
14595            10.satu;
14596
14597            //
14598            // separate cursors so they open in different excerpts (manually reproducible)
14599            //
14600
14601            10.satu20;
14602        }
14603    "};
14604    let multibuffer_text_with_selections = indoc! {"
14605        fn main() {
14606            10.satuˇ;
14607
14608            //
14609
14610            //
14611
14612            10.satuˇ20;
14613        }
14614    "};
14615    let expected_multibuffer = indoc! {"
14616        fn main() {
14617            10.saturating_sub()ˇ;
14618
14619            //
14620
14621            //
14622
14623            10.saturating_sub()ˇ;
14624        }
14625    "};
14626
14627    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14628    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14629
14630    let fs = FakeFs::new(cx.executor());
14631    fs.insert_tree(
14632        path!("/a"),
14633        json!({
14634            "main.rs": buffer_text,
14635        }),
14636    )
14637    .await;
14638
14639    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14640    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14641    language_registry.add(rust_lang());
14642    let mut fake_servers = language_registry.register_fake_lsp(
14643        "Rust",
14644        FakeLspAdapter {
14645            capabilities: lsp::ServerCapabilities {
14646                completion_provider: Some(lsp::CompletionOptions {
14647                    resolve_provider: None,
14648                    ..lsp::CompletionOptions::default()
14649                }),
14650                ..lsp::ServerCapabilities::default()
14651            },
14652            ..FakeLspAdapter::default()
14653        },
14654    );
14655    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14656    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14657    let buffer = project
14658        .update(cx, |project, cx| {
14659            project.open_local_buffer(path!("/a/main.rs"), cx)
14660        })
14661        .await
14662        .unwrap();
14663
14664    let multi_buffer = cx.new(|cx| {
14665        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14666        multi_buffer.push_excerpts(
14667            buffer.clone(),
14668            [ExcerptRange::new(0..first_excerpt_end)],
14669            cx,
14670        );
14671        multi_buffer.push_excerpts(
14672            buffer.clone(),
14673            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14674            cx,
14675        );
14676        multi_buffer
14677    });
14678
14679    let editor = workspace
14680        .update(cx, |_, window, cx| {
14681            cx.new(|cx| {
14682                Editor::new(
14683                    EditorMode::Full {
14684                        scale_ui_elements_with_buffer_font_size: false,
14685                        show_active_line_background: false,
14686                        sizing_behavior: SizingBehavior::Default,
14687                    },
14688                    multi_buffer.clone(),
14689                    Some(project.clone()),
14690                    window,
14691                    cx,
14692                )
14693            })
14694        })
14695        .unwrap();
14696
14697    let pane = workspace
14698        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14699        .unwrap();
14700    pane.update_in(cx, |pane, window, cx| {
14701        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14702    });
14703
14704    let fake_server = fake_servers.next().await.unwrap();
14705
14706    editor.update_in(cx, |editor, window, cx| {
14707        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14708            s.select_ranges([
14709                Point::new(1, 11)..Point::new(1, 11),
14710                Point::new(7, 11)..Point::new(7, 11),
14711            ])
14712        });
14713
14714        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14715    });
14716
14717    editor.update_in(cx, |editor, window, cx| {
14718        editor.show_completions(&ShowCompletions, window, cx);
14719    });
14720
14721    fake_server
14722        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14723            let completion_item = lsp::CompletionItem {
14724                label: "saturating_sub()".into(),
14725                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14726                    lsp::InsertReplaceEdit {
14727                        new_text: "saturating_sub()".to_owned(),
14728                        insert: lsp::Range::new(
14729                            lsp::Position::new(7, 7),
14730                            lsp::Position::new(7, 11),
14731                        ),
14732                        replace: lsp::Range::new(
14733                            lsp::Position::new(7, 7),
14734                            lsp::Position::new(7, 13),
14735                        ),
14736                    },
14737                )),
14738                ..lsp::CompletionItem::default()
14739            };
14740
14741            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14742        })
14743        .next()
14744        .await
14745        .unwrap();
14746
14747    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14748        .await;
14749
14750    editor
14751        .update_in(cx, |editor, window, cx| {
14752            editor
14753                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14754                .unwrap()
14755        })
14756        .await
14757        .unwrap();
14758
14759    editor.update(cx, |editor, cx| {
14760        assert_text_with_selections(editor, expected_multibuffer, cx);
14761    })
14762}
14763
14764#[gpui::test]
14765async fn test_completion(cx: &mut TestAppContext) {
14766    init_test(cx, |_| {});
14767
14768    let mut cx = EditorLspTestContext::new_rust(
14769        lsp::ServerCapabilities {
14770            completion_provider: Some(lsp::CompletionOptions {
14771                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14772                resolve_provider: Some(true),
14773                ..Default::default()
14774            }),
14775            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14776            ..Default::default()
14777        },
14778        cx,
14779    )
14780    .await;
14781    let counter = Arc::new(AtomicUsize::new(0));
14782
14783    cx.set_state(indoc! {"
14784        oneˇ
14785        two
14786        three
14787    "});
14788    cx.simulate_keystroke(".");
14789    handle_completion_request(
14790        indoc! {"
14791            one.|<>
14792            two
14793            three
14794        "},
14795        vec!["first_completion", "second_completion"],
14796        true,
14797        counter.clone(),
14798        &mut cx,
14799    )
14800    .await;
14801    cx.condition(|editor, _| editor.context_menu_visible())
14802        .await;
14803    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14804
14805    let _handler = handle_signature_help_request(
14806        &mut cx,
14807        lsp::SignatureHelp {
14808            signatures: vec![lsp::SignatureInformation {
14809                label: "test signature".to_string(),
14810                documentation: None,
14811                parameters: Some(vec![lsp::ParameterInformation {
14812                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14813                    documentation: None,
14814                }]),
14815                active_parameter: None,
14816            }],
14817            active_signature: None,
14818            active_parameter: None,
14819        },
14820    );
14821    cx.update_editor(|editor, window, cx| {
14822        assert!(
14823            !editor.signature_help_state.is_shown(),
14824            "No signature help was called for"
14825        );
14826        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14827    });
14828    cx.run_until_parked();
14829    cx.update_editor(|editor, _, _| {
14830        assert!(
14831            !editor.signature_help_state.is_shown(),
14832            "No signature help should be shown when completions menu is open"
14833        );
14834    });
14835
14836    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14837        editor.context_menu_next(&Default::default(), window, cx);
14838        editor
14839            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14840            .unwrap()
14841    });
14842    cx.assert_editor_state(indoc! {"
14843        one.second_completionˇ
14844        two
14845        three
14846    "});
14847
14848    handle_resolve_completion_request(
14849        &mut cx,
14850        Some(vec![
14851            (
14852                //This overlaps with the primary completion edit which is
14853                //misbehavior from the LSP spec, test that we filter it out
14854                indoc! {"
14855                    one.second_ˇcompletion
14856                    two
14857                    threeˇ
14858                "},
14859                "overlapping additional edit",
14860            ),
14861            (
14862                indoc! {"
14863                    one.second_completion
14864                    two
14865                    threeˇ
14866                "},
14867                "\nadditional edit",
14868            ),
14869        ]),
14870    )
14871    .await;
14872    apply_additional_edits.await.unwrap();
14873    cx.assert_editor_state(indoc! {"
14874        one.second_completionˇ
14875        two
14876        three
14877        additional edit
14878    "});
14879
14880    cx.set_state(indoc! {"
14881        one.second_completion
14882        twoˇ
14883        threeˇ
14884        additional edit
14885    "});
14886    cx.simulate_keystroke(" ");
14887    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14888    cx.simulate_keystroke("s");
14889    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14890
14891    cx.assert_editor_state(indoc! {"
14892        one.second_completion
14893        two sˇ
14894        three sˇ
14895        additional edit
14896    "});
14897    handle_completion_request(
14898        indoc! {"
14899            one.second_completion
14900            two s
14901            three <s|>
14902            additional edit
14903        "},
14904        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14905        true,
14906        counter.clone(),
14907        &mut cx,
14908    )
14909    .await;
14910    cx.condition(|editor, _| editor.context_menu_visible())
14911        .await;
14912    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14913
14914    cx.simulate_keystroke("i");
14915
14916    handle_completion_request(
14917        indoc! {"
14918            one.second_completion
14919            two si
14920            three <si|>
14921            additional edit
14922        "},
14923        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14924        true,
14925        counter.clone(),
14926        &mut cx,
14927    )
14928    .await;
14929    cx.condition(|editor, _| editor.context_menu_visible())
14930        .await;
14931    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14932
14933    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14934        editor
14935            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14936            .unwrap()
14937    });
14938    cx.assert_editor_state(indoc! {"
14939        one.second_completion
14940        two sixth_completionˇ
14941        three sixth_completionˇ
14942        additional edit
14943    "});
14944
14945    apply_additional_edits.await.unwrap();
14946
14947    update_test_language_settings(&mut cx, |settings| {
14948        settings.defaults.show_completions_on_input = Some(false);
14949    });
14950    cx.set_state("editorˇ");
14951    cx.simulate_keystroke(".");
14952    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14953    cx.simulate_keystrokes("c l o");
14954    cx.assert_editor_state("editor.cloˇ");
14955    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14956    cx.update_editor(|editor, window, cx| {
14957        editor.show_completions(&ShowCompletions, window, cx);
14958    });
14959    handle_completion_request(
14960        "editor.<clo|>",
14961        vec!["close", "clobber"],
14962        true,
14963        counter.clone(),
14964        &mut cx,
14965    )
14966    .await;
14967    cx.condition(|editor, _| editor.context_menu_visible())
14968        .await;
14969    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14970
14971    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14972        editor
14973            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14974            .unwrap()
14975    });
14976    cx.assert_editor_state("editor.clobberˇ");
14977    handle_resolve_completion_request(&mut cx, None).await;
14978    apply_additional_edits.await.unwrap();
14979}
14980
14981#[gpui::test]
14982async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14983    init_test(cx, |_| {});
14984
14985    let fs = FakeFs::new(cx.executor());
14986    fs.insert_tree(
14987        path!("/a"),
14988        json!({
14989            "main.rs": "",
14990        }),
14991    )
14992    .await;
14993
14994    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14995    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14996    language_registry.add(rust_lang());
14997    let command_calls = Arc::new(AtomicUsize::new(0));
14998    let registered_command = "_the/command";
14999
15000    let closure_command_calls = command_calls.clone();
15001    let mut fake_servers = language_registry.register_fake_lsp(
15002        "Rust",
15003        FakeLspAdapter {
15004            capabilities: lsp::ServerCapabilities {
15005                completion_provider: Some(lsp::CompletionOptions {
15006                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15007                    ..lsp::CompletionOptions::default()
15008                }),
15009                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15010                    commands: vec![registered_command.to_owned()],
15011                    ..lsp::ExecuteCommandOptions::default()
15012                }),
15013                ..lsp::ServerCapabilities::default()
15014            },
15015            initializer: Some(Box::new(move |fake_server| {
15016                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15017                    move |params, _| async move {
15018                        Ok(Some(lsp::CompletionResponse::Array(vec![
15019                            lsp::CompletionItem {
15020                                label: "registered_command".to_owned(),
15021                                text_edit: gen_text_edit(&params, ""),
15022                                command: Some(lsp::Command {
15023                                    title: registered_command.to_owned(),
15024                                    command: "_the/command".to_owned(),
15025                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15026                                }),
15027                                ..lsp::CompletionItem::default()
15028                            },
15029                            lsp::CompletionItem {
15030                                label: "unregistered_command".to_owned(),
15031                                text_edit: gen_text_edit(&params, ""),
15032                                command: Some(lsp::Command {
15033                                    title: "????????????".to_owned(),
15034                                    command: "????????????".to_owned(),
15035                                    arguments: Some(vec![serde_json::Value::Null]),
15036                                }),
15037                                ..lsp::CompletionItem::default()
15038                            },
15039                        ])))
15040                    },
15041                );
15042                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15043                    let command_calls = closure_command_calls.clone();
15044                    move |params, _| {
15045                        assert_eq!(params.command, registered_command);
15046                        let command_calls = command_calls.clone();
15047                        async move {
15048                            command_calls.fetch_add(1, atomic::Ordering::Release);
15049                            Ok(Some(json!(null)))
15050                        }
15051                    }
15052                });
15053            })),
15054            ..FakeLspAdapter::default()
15055        },
15056    );
15057    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15058    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15059    let editor = workspace
15060        .update(cx, |workspace, window, cx| {
15061            workspace.open_abs_path(
15062                PathBuf::from(path!("/a/main.rs")),
15063                OpenOptions::default(),
15064                window,
15065                cx,
15066            )
15067        })
15068        .unwrap()
15069        .await
15070        .unwrap()
15071        .downcast::<Editor>()
15072        .unwrap();
15073    let _fake_server = fake_servers.next().await.unwrap();
15074
15075    editor.update_in(cx, |editor, window, cx| {
15076        cx.focus_self(window);
15077        editor.move_to_end(&MoveToEnd, window, cx);
15078        editor.handle_input(".", window, cx);
15079    });
15080    cx.run_until_parked();
15081    editor.update(cx, |editor, _| {
15082        assert!(editor.context_menu_visible());
15083        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15084        {
15085            let completion_labels = menu
15086                .completions
15087                .borrow()
15088                .iter()
15089                .map(|c| c.label.text.clone())
15090                .collect::<Vec<_>>();
15091            assert_eq!(
15092                completion_labels,
15093                &["registered_command", "unregistered_command",],
15094            );
15095        } else {
15096            panic!("expected completion menu to be open");
15097        }
15098    });
15099
15100    editor
15101        .update_in(cx, |editor, window, cx| {
15102            editor
15103                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15104                .unwrap()
15105        })
15106        .await
15107        .unwrap();
15108    cx.run_until_parked();
15109    assert_eq!(
15110        command_calls.load(atomic::Ordering::Acquire),
15111        1,
15112        "For completion with a registered command, Zed should send a command execution request",
15113    );
15114
15115    editor.update_in(cx, |editor, window, cx| {
15116        cx.focus_self(window);
15117        editor.handle_input(".", window, cx);
15118    });
15119    cx.run_until_parked();
15120    editor.update(cx, |editor, _| {
15121        assert!(editor.context_menu_visible());
15122        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15123        {
15124            let completion_labels = menu
15125                .completions
15126                .borrow()
15127                .iter()
15128                .map(|c| c.label.text.clone())
15129                .collect::<Vec<_>>();
15130            assert_eq!(
15131                completion_labels,
15132                &["registered_command", "unregistered_command",],
15133            );
15134        } else {
15135            panic!("expected completion menu to be open");
15136        }
15137    });
15138    editor
15139        .update_in(cx, |editor, window, cx| {
15140            editor.context_menu_next(&Default::default(), window, cx);
15141            editor
15142                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15143                .unwrap()
15144        })
15145        .await
15146        .unwrap();
15147    cx.run_until_parked();
15148    assert_eq!(
15149        command_calls.load(atomic::Ordering::Acquire),
15150        1,
15151        "For completion with an unregistered command, Zed should not send a command execution request",
15152    );
15153}
15154
15155#[gpui::test]
15156async fn test_completion_reuse(cx: &mut TestAppContext) {
15157    init_test(cx, |_| {});
15158
15159    let mut cx = EditorLspTestContext::new_rust(
15160        lsp::ServerCapabilities {
15161            completion_provider: Some(lsp::CompletionOptions {
15162                trigger_characters: Some(vec![".".to_string()]),
15163                ..Default::default()
15164            }),
15165            ..Default::default()
15166        },
15167        cx,
15168    )
15169    .await;
15170
15171    let counter = Arc::new(AtomicUsize::new(0));
15172    cx.set_state("objˇ");
15173    cx.simulate_keystroke(".");
15174
15175    // Initial completion request returns complete results
15176    let is_incomplete = false;
15177    handle_completion_request(
15178        "obj.|<>",
15179        vec!["a", "ab", "abc"],
15180        is_incomplete,
15181        counter.clone(),
15182        &mut cx,
15183    )
15184    .await;
15185    cx.run_until_parked();
15186    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15187    cx.assert_editor_state("obj.ˇ");
15188    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15189
15190    // Type "a" - filters existing completions
15191    cx.simulate_keystroke("a");
15192    cx.run_until_parked();
15193    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15194    cx.assert_editor_state("obj.aˇ");
15195    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15196
15197    // Type "b" - filters existing completions
15198    cx.simulate_keystroke("b");
15199    cx.run_until_parked();
15200    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15201    cx.assert_editor_state("obj.abˇ");
15202    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15203
15204    // Type "c" - filters existing completions
15205    cx.simulate_keystroke("c");
15206    cx.run_until_parked();
15207    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15208    cx.assert_editor_state("obj.abcˇ");
15209    check_displayed_completions(vec!["abc"], &mut cx);
15210
15211    // Backspace to delete "c" - filters existing completions
15212    cx.update_editor(|editor, window, cx| {
15213        editor.backspace(&Backspace, window, cx);
15214    });
15215    cx.run_until_parked();
15216    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15217    cx.assert_editor_state("obj.abˇ");
15218    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15219
15220    // Moving cursor to the left dismisses menu.
15221    cx.update_editor(|editor, window, cx| {
15222        editor.move_left(&MoveLeft, window, cx);
15223    });
15224    cx.run_until_parked();
15225    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15226    cx.assert_editor_state("obj.aˇb");
15227    cx.update_editor(|editor, _, _| {
15228        assert_eq!(editor.context_menu_visible(), false);
15229    });
15230
15231    // Type "b" - new request
15232    cx.simulate_keystroke("b");
15233    let is_incomplete = false;
15234    handle_completion_request(
15235        "obj.<ab|>a",
15236        vec!["ab", "abc"],
15237        is_incomplete,
15238        counter.clone(),
15239        &mut cx,
15240    )
15241    .await;
15242    cx.run_until_parked();
15243    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15244    cx.assert_editor_state("obj.abˇb");
15245    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15246
15247    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15248    cx.update_editor(|editor, window, cx| {
15249        editor.backspace(&Backspace, window, cx);
15250    });
15251    let is_incomplete = false;
15252    handle_completion_request(
15253        "obj.<a|>b",
15254        vec!["a", "ab", "abc"],
15255        is_incomplete,
15256        counter.clone(),
15257        &mut cx,
15258    )
15259    .await;
15260    cx.run_until_parked();
15261    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15262    cx.assert_editor_state("obj.aˇb");
15263    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15264
15265    // Backspace to delete "a" - dismisses menu.
15266    cx.update_editor(|editor, window, cx| {
15267        editor.backspace(&Backspace, window, cx);
15268    });
15269    cx.run_until_parked();
15270    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15271    cx.assert_editor_state("obj.ˇb");
15272    cx.update_editor(|editor, _, _| {
15273        assert_eq!(editor.context_menu_visible(), false);
15274    });
15275}
15276
15277#[gpui::test]
15278async fn test_word_completion(cx: &mut TestAppContext) {
15279    let lsp_fetch_timeout_ms = 10;
15280    init_test(cx, |language_settings| {
15281        language_settings.defaults.completions = Some(CompletionSettingsContent {
15282            words_min_length: Some(0),
15283            lsp_fetch_timeout_ms: Some(10),
15284            lsp_insert_mode: Some(LspInsertMode::Insert),
15285            ..Default::default()
15286        });
15287    });
15288
15289    let mut cx = EditorLspTestContext::new_rust(
15290        lsp::ServerCapabilities {
15291            completion_provider: Some(lsp::CompletionOptions {
15292                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15293                ..lsp::CompletionOptions::default()
15294            }),
15295            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15296            ..lsp::ServerCapabilities::default()
15297        },
15298        cx,
15299    )
15300    .await;
15301
15302    let throttle_completions = Arc::new(AtomicBool::new(false));
15303
15304    let lsp_throttle_completions = throttle_completions.clone();
15305    let _completion_requests_handler =
15306        cx.lsp
15307            .server
15308            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15309                let lsp_throttle_completions = lsp_throttle_completions.clone();
15310                let cx = cx.clone();
15311                async move {
15312                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15313                        cx.background_executor()
15314                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15315                            .await;
15316                    }
15317                    Ok(Some(lsp::CompletionResponse::Array(vec![
15318                        lsp::CompletionItem {
15319                            label: "first".into(),
15320                            ..lsp::CompletionItem::default()
15321                        },
15322                        lsp::CompletionItem {
15323                            label: "last".into(),
15324                            ..lsp::CompletionItem::default()
15325                        },
15326                    ])))
15327                }
15328            });
15329
15330    cx.set_state(indoc! {"
15331        oneˇ
15332        two
15333        three
15334    "});
15335    cx.simulate_keystroke(".");
15336    cx.executor().run_until_parked();
15337    cx.condition(|editor, _| editor.context_menu_visible())
15338        .await;
15339    cx.update_editor(|editor, window, cx| {
15340        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15341        {
15342            assert_eq!(
15343                completion_menu_entries(menu),
15344                &["first", "last"],
15345                "When LSP server is fast to reply, no fallback word completions are used"
15346            );
15347        } else {
15348            panic!("expected completion menu to be open");
15349        }
15350        editor.cancel(&Cancel, window, cx);
15351    });
15352    cx.executor().run_until_parked();
15353    cx.condition(|editor, _| !editor.context_menu_visible())
15354        .await;
15355
15356    throttle_completions.store(true, atomic::Ordering::Release);
15357    cx.simulate_keystroke(".");
15358    cx.executor()
15359        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15360    cx.executor().run_until_parked();
15361    cx.condition(|editor, _| editor.context_menu_visible())
15362        .await;
15363    cx.update_editor(|editor, _, _| {
15364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15365        {
15366            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15367                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15368        } else {
15369            panic!("expected completion menu to be open");
15370        }
15371    });
15372}
15373
15374#[gpui::test]
15375async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15376    init_test(cx, |language_settings| {
15377        language_settings.defaults.completions = Some(CompletionSettingsContent {
15378            words: Some(WordsCompletionMode::Enabled),
15379            words_min_length: Some(0),
15380            lsp_insert_mode: Some(LspInsertMode::Insert),
15381            ..Default::default()
15382        });
15383    });
15384
15385    let mut cx = EditorLspTestContext::new_rust(
15386        lsp::ServerCapabilities {
15387            completion_provider: Some(lsp::CompletionOptions {
15388                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15389                ..lsp::CompletionOptions::default()
15390            }),
15391            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15392            ..lsp::ServerCapabilities::default()
15393        },
15394        cx,
15395    )
15396    .await;
15397
15398    let _completion_requests_handler =
15399        cx.lsp
15400            .server
15401            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15402                Ok(Some(lsp::CompletionResponse::Array(vec![
15403                    lsp::CompletionItem {
15404                        label: "first".into(),
15405                        ..lsp::CompletionItem::default()
15406                    },
15407                    lsp::CompletionItem {
15408                        label: "last".into(),
15409                        ..lsp::CompletionItem::default()
15410                    },
15411                ])))
15412            });
15413
15414    cx.set_state(indoc! {"ˇ
15415        first
15416        last
15417        second
15418    "});
15419    cx.simulate_keystroke(".");
15420    cx.executor().run_until_parked();
15421    cx.condition(|editor, _| editor.context_menu_visible())
15422        .await;
15423    cx.update_editor(|editor, _, _| {
15424        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15425        {
15426            assert_eq!(
15427                completion_menu_entries(menu),
15428                &["first", "last", "second"],
15429                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15430            );
15431        } else {
15432            panic!("expected completion menu to be open");
15433        }
15434    });
15435}
15436
15437#[gpui::test]
15438async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15439    init_test(cx, |language_settings| {
15440        language_settings.defaults.completions = Some(CompletionSettingsContent {
15441            words: Some(WordsCompletionMode::Disabled),
15442            words_min_length: Some(0),
15443            lsp_insert_mode: Some(LspInsertMode::Insert),
15444            ..Default::default()
15445        });
15446    });
15447
15448    let mut cx = EditorLspTestContext::new_rust(
15449        lsp::ServerCapabilities {
15450            completion_provider: Some(lsp::CompletionOptions {
15451                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15452                ..lsp::CompletionOptions::default()
15453            }),
15454            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15455            ..lsp::ServerCapabilities::default()
15456        },
15457        cx,
15458    )
15459    .await;
15460
15461    let _completion_requests_handler =
15462        cx.lsp
15463            .server
15464            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15465                panic!("LSP completions should not be queried when dealing with word completions")
15466            });
15467
15468    cx.set_state(indoc! {"ˇ
15469        first
15470        last
15471        second
15472    "});
15473    cx.update_editor(|editor, window, cx| {
15474        editor.show_word_completions(&ShowWordCompletions, window, cx);
15475    });
15476    cx.executor().run_until_parked();
15477    cx.condition(|editor, _| editor.context_menu_visible())
15478        .await;
15479    cx.update_editor(|editor, _, _| {
15480        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15481        {
15482            assert_eq!(
15483                completion_menu_entries(menu),
15484                &["first", "last", "second"],
15485                "`ShowWordCompletions` action should show word completions"
15486            );
15487        } else {
15488            panic!("expected completion menu to be open");
15489        }
15490    });
15491
15492    cx.simulate_keystroke("l");
15493    cx.executor().run_until_parked();
15494    cx.condition(|editor, _| editor.context_menu_visible())
15495        .await;
15496    cx.update_editor(|editor, _, _| {
15497        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15498        {
15499            assert_eq!(
15500                completion_menu_entries(menu),
15501                &["last"],
15502                "After showing word completions, further editing should filter them and not query the LSP"
15503            );
15504        } else {
15505            panic!("expected completion menu to be open");
15506        }
15507    });
15508}
15509
15510#[gpui::test]
15511async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15512    init_test(cx, |language_settings| {
15513        language_settings.defaults.completions = Some(CompletionSettingsContent {
15514            words_min_length: Some(0),
15515            lsp: Some(false),
15516            lsp_insert_mode: Some(LspInsertMode::Insert),
15517            ..Default::default()
15518        });
15519    });
15520
15521    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15522
15523    cx.set_state(indoc! {"ˇ
15524        0_usize
15525        let
15526        33
15527        4.5f32
15528    "});
15529    cx.update_editor(|editor, window, cx| {
15530        editor.show_completions(&ShowCompletions, window, cx);
15531    });
15532    cx.executor().run_until_parked();
15533    cx.condition(|editor, _| editor.context_menu_visible())
15534        .await;
15535    cx.update_editor(|editor, window, cx| {
15536        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15537        {
15538            assert_eq!(
15539                completion_menu_entries(menu),
15540                &["let"],
15541                "With no digits in the completion query, no digits should be in the word completions"
15542            );
15543        } else {
15544            panic!("expected completion menu to be open");
15545        }
15546        editor.cancel(&Cancel, window, cx);
15547    });
15548
15549    cx.set_state(indoc! {"15550        0_usize
15551        let
15552        3
15553        33.35f32
15554    "});
15555    cx.update_editor(|editor, window, cx| {
15556        editor.show_completions(&ShowCompletions, window, cx);
15557    });
15558    cx.executor().run_until_parked();
15559    cx.condition(|editor, _| editor.context_menu_visible())
15560        .await;
15561    cx.update_editor(|editor, _, _| {
15562        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15563        {
15564            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15565                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15566        } else {
15567            panic!("expected completion menu to be open");
15568        }
15569    });
15570}
15571
15572#[gpui::test]
15573async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15574    init_test(cx, |language_settings| {
15575        language_settings.defaults.completions = Some(CompletionSettingsContent {
15576            words: Some(WordsCompletionMode::Enabled),
15577            words_min_length: Some(3),
15578            lsp_insert_mode: Some(LspInsertMode::Insert),
15579            ..Default::default()
15580        });
15581    });
15582
15583    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15584    cx.set_state(indoc! {"ˇ
15585        wow
15586        wowen
15587        wowser
15588    "});
15589    cx.simulate_keystroke("w");
15590    cx.executor().run_until_parked();
15591    cx.update_editor(|editor, _, _| {
15592        if editor.context_menu.borrow_mut().is_some() {
15593            panic!(
15594                "expected completion menu to be hidden, as words completion threshold is not met"
15595            );
15596        }
15597    });
15598
15599    cx.update_editor(|editor, window, cx| {
15600        editor.show_word_completions(&ShowWordCompletions, window, cx);
15601    });
15602    cx.executor().run_until_parked();
15603    cx.update_editor(|editor, window, cx| {
15604        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15605        {
15606            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");
15607        } else {
15608            panic!("expected completion menu to be open after the word completions are called with an action");
15609        }
15610
15611        editor.cancel(&Cancel, window, cx);
15612    });
15613    cx.update_editor(|editor, _, _| {
15614        if editor.context_menu.borrow_mut().is_some() {
15615            panic!("expected completion menu to be hidden after canceling");
15616        }
15617    });
15618
15619    cx.simulate_keystroke("o");
15620    cx.executor().run_until_parked();
15621    cx.update_editor(|editor, _, _| {
15622        if editor.context_menu.borrow_mut().is_some() {
15623            panic!(
15624                "expected completion menu to be hidden, as words completion threshold is not met still"
15625            );
15626        }
15627    });
15628
15629    cx.simulate_keystroke("w");
15630    cx.executor().run_until_parked();
15631    cx.update_editor(|editor, _, _| {
15632        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15633        {
15634            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15635        } else {
15636            panic!("expected completion menu to be open after the word completions threshold is met");
15637        }
15638    });
15639}
15640
15641#[gpui::test]
15642async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15643    init_test(cx, |language_settings| {
15644        language_settings.defaults.completions = Some(CompletionSettingsContent {
15645            words: Some(WordsCompletionMode::Enabled),
15646            words_min_length: Some(0),
15647            lsp_insert_mode: Some(LspInsertMode::Insert),
15648            ..Default::default()
15649        });
15650    });
15651
15652    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15653    cx.update_editor(|editor, _, _| {
15654        editor.disable_word_completions();
15655    });
15656    cx.set_state(indoc! {"ˇ
15657        wow
15658        wowen
15659        wowser
15660    "});
15661    cx.simulate_keystroke("w");
15662    cx.executor().run_until_parked();
15663    cx.update_editor(|editor, _, _| {
15664        if editor.context_menu.borrow_mut().is_some() {
15665            panic!(
15666                "expected completion menu to be hidden, as words completion are disabled for this editor"
15667            );
15668        }
15669    });
15670
15671    cx.update_editor(|editor, window, cx| {
15672        editor.show_word_completions(&ShowWordCompletions, window, cx);
15673    });
15674    cx.executor().run_until_parked();
15675    cx.update_editor(|editor, _, _| {
15676        if editor.context_menu.borrow_mut().is_some() {
15677            panic!(
15678                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15679            );
15680        }
15681    });
15682}
15683
15684#[gpui::test]
15685async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15686    init_test(cx, |language_settings| {
15687        language_settings.defaults.completions = Some(CompletionSettingsContent {
15688            words: Some(WordsCompletionMode::Disabled),
15689            words_min_length: Some(0),
15690            lsp_insert_mode: Some(LspInsertMode::Insert),
15691            ..Default::default()
15692        });
15693    });
15694
15695    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15696    cx.update_editor(|editor, _, _| {
15697        editor.set_completion_provider(None);
15698    });
15699    cx.set_state(indoc! {"ˇ
15700        wow
15701        wowen
15702        wowser
15703    "});
15704    cx.simulate_keystroke("w");
15705    cx.executor().run_until_parked();
15706    cx.update_editor(|editor, _, _| {
15707        if editor.context_menu.borrow_mut().is_some() {
15708            panic!("expected completion menu to be hidden, as disabled in settings");
15709        }
15710    });
15711}
15712
15713fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15714    let position = || lsp::Position {
15715        line: params.text_document_position.position.line,
15716        character: params.text_document_position.position.character,
15717    };
15718    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15719        range: lsp::Range {
15720            start: position(),
15721            end: position(),
15722        },
15723        new_text: text.to_string(),
15724    }))
15725}
15726
15727#[gpui::test]
15728async fn test_multiline_completion(cx: &mut TestAppContext) {
15729    init_test(cx, |_| {});
15730
15731    let fs = FakeFs::new(cx.executor());
15732    fs.insert_tree(
15733        path!("/a"),
15734        json!({
15735            "main.ts": "a",
15736        }),
15737    )
15738    .await;
15739
15740    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15741    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15742    let typescript_language = Arc::new(Language::new(
15743        LanguageConfig {
15744            name: "TypeScript".into(),
15745            matcher: LanguageMatcher {
15746                path_suffixes: vec!["ts".to_string()],
15747                ..LanguageMatcher::default()
15748            },
15749            line_comments: vec!["// ".into()],
15750            ..LanguageConfig::default()
15751        },
15752        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15753    ));
15754    language_registry.add(typescript_language.clone());
15755    let mut fake_servers = language_registry.register_fake_lsp(
15756        "TypeScript",
15757        FakeLspAdapter {
15758            capabilities: lsp::ServerCapabilities {
15759                completion_provider: Some(lsp::CompletionOptions {
15760                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15761                    ..lsp::CompletionOptions::default()
15762                }),
15763                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15764                ..lsp::ServerCapabilities::default()
15765            },
15766            // Emulate vtsls label generation
15767            label_for_completion: Some(Box::new(|item, _| {
15768                let text = if let Some(description) = item
15769                    .label_details
15770                    .as_ref()
15771                    .and_then(|label_details| label_details.description.as_ref())
15772                {
15773                    format!("{} {}", item.label, description)
15774                } else if let Some(detail) = &item.detail {
15775                    format!("{} {}", item.label, detail)
15776                } else {
15777                    item.label.clone()
15778                };
15779                Some(language::CodeLabel::plain(text, None))
15780            })),
15781            ..FakeLspAdapter::default()
15782        },
15783    );
15784    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15785    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15786    let worktree_id = workspace
15787        .update(cx, |workspace, _window, cx| {
15788            workspace.project().update(cx, |project, cx| {
15789                project.worktrees(cx).next().unwrap().read(cx).id()
15790            })
15791        })
15792        .unwrap();
15793    let _buffer = project
15794        .update(cx, |project, cx| {
15795            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15796        })
15797        .await
15798        .unwrap();
15799    let editor = workspace
15800        .update(cx, |workspace, window, cx| {
15801            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15802        })
15803        .unwrap()
15804        .await
15805        .unwrap()
15806        .downcast::<Editor>()
15807        .unwrap();
15808    let fake_server = fake_servers.next().await.unwrap();
15809
15810    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15811    let multiline_label_2 = "a\nb\nc\n";
15812    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15813    let multiline_description = "d\ne\nf\n";
15814    let multiline_detail_2 = "g\nh\ni\n";
15815
15816    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15817        move |params, _| async move {
15818            Ok(Some(lsp::CompletionResponse::Array(vec![
15819                lsp::CompletionItem {
15820                    label: multiline_label.to_string(),
15821                    text_edit: gen_text_edit(&params, "new_text_1"),
15822                    ..lsp::CompletionItem::default()
15823                },
15824                lsp::CompletionItem {
15825                    label: "single line label 1".to_string(),
15826                    detail: Some(multiline_detail.to_string()),
15827                    text_edit: gen_text_edit(&params, "new_text_2"),
15828                    ..lsp::CompletionItem::default()
15829                },
15830                lsp::CompletionItem {
15831                    label: "single line label 2".to_string(),
15832                    label_details: Some(lsp::CompletionItemLabelDetails {
15833                        description: Some(multiline_description.to_string()),
15834                        detail: None,
15835                    }),
15836                    text_edit: gen_text_edit(&params, "new_text_2"),
15837                    ..lsp::CompletionItem::default()
15838                },
15839                lsp::CompletionItem {
15840                    label: multiline_label_2.to_string(),
15841                    detail: Some(multiline_detail_2.to_string()),
15842                    text_edit: gen_text_edit(&params, "new_text_3"),
15843                    ..lsp::CompletionItem::default()
15844                },
15845                lsp::CompletionItem {
15846                    label: "Label with many     spaces and \t but without newlines".to_string(),
15847                    detail: Some(
15848                        "Details with many     spaces and \t but without newlines".to_string(),
15849                    ),
15850                    text_edit: gen_text_edit(&params, "new_text_4"),
15851                    ..lsp::CompletionItem::default()
15852                },
15853            ])))
15854        },
15855    );
15856
15857    editor.update_in(cx, |editor, window, cx| {
15858        cx.focus_self(window);
15859        editor.move_to_end(&MoveToEnd, window, cx);
15860        editor.handle_input(".", window, cx);
15861    });
15862    cx.run_until_parked();
15863    completion_handle.next().await.unwrap();
15864
15865    editor.update(cx, |editor, _| {
15866        assert!(editor.context_menu_visible());
15867        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15868        {
15869            let completion_labels = menu
15870                .completions
15871                .borrow()
15872                .iter()
15873                .map(|c| c.label.text.clone())
15874                .collect::<Vec<_>>();
15875            assert_eq!(
15876                completion_labels,
15877                &[
15878                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15879                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15880                    "single line label 2 d e f ",
15881                    "a b c g h i ",
15882                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15883                ],
15884                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15885            );
15886
15887            for completion in menu
15888                .completions
15889                .borrow()
15890                .iter() {
15891                    assert_eq!(
15892                        completion.label.filter_range,
15893                        0..completion.label.text.len(),
15894                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15895                    );
15896                }
15897        } else {
15898            panic!("expected completion menu to be open");
15899        }
15900    });
15901}
15902
15903#[gpui::test]
15904async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15905    init_test(cx, |_| {});
15906    let mut cx = EditorLspTestContext::new_rust(
15907        lsp::ServerCapabilities {
15908            completion_provider: Some(lsp::CompletionOptions {
15909                trigger_characters: Some(vec![".".to_string()]),
15910                ..Default::default()
15911            }),
15912            ..Default::default()
15913        },
15914        cx,
15915    )
15916    .await;
15917    cx.lsp
15918        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15919            Ok(Some(lsp::CompletionResponse::Array(vec![
15920                lsp::CompletionItem {
15921                    label: "first".into(),
15922                    ..Default::default()
15923                },
15924                lsp::CompletionItem {
15925                    label: "last".into(),
15926                    ..Default::default()
15927                },
15928            ])))
15929        });
15930    cx.set_state("variableˇ");
15931    cx.simulate_keystroke(".");
15932    cx.executor().run_until_parked();
15933
15934    cx.update_editor(|editor, _, _| {
15935        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15936        {
15937            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15938        } else {
15939            panic!("expected completion menu to be open");
15940        }
15941    });
15942
15943    cx.update_editor(|editor, window, cx| {
15944        editor.move_page_down(&MovePageDown::default(), window, cx);
15945        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15946        {
15947            assert!(
15948                menu.selected_item == 1,
15949                "expected PageDown to select the last item from the context menu"
15950            );
15951        } else {
15952            panic!("expected completion menu to stay open after PageDown");
15953        }
15954    });
15955
15956    cx.update_editor(|editor, window, cx| {
15957        editor.move_page_up(&MovePageUp::default(), window, cx);
15958        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15959        {
15960            assert!(
15961                menu.selected_item == 0,
15962                "expected PageUp to select the first item from the context menu"
15963            );
15964        } else {
15965            panic!("expected completion menu to stay open after PageUp");
15966        }
15967    });
15968}
15969
15970#[gpui::test]
15971async fn test_as_is_completions(cx: &mut TestAppContext) {
15972    init_test(cx, |_| {});
15973    let mut cx = EditorLspTestContext::new_rust(
15974        lsp::ServerCapabilities {
15975            completion_provider: Some(lsp::CompletionOptions {
15976                ..Default::default()
15977            }),
15978            ..Default::default()
15979        },
15980        cx,
15981    )
15982    .await;
15983    cx.lsp
15984        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15985            Ok(Some(lsp::CompletionResponse::Array(vec![
15986                lsp::CompletionItem {
15987                    label: "unsafe".into(),
15988                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15989                        range: lsp::Range {
15990                            start: lsp::Position {
15991                                line: 1,
15992                                character: 2,
15993                            },
15994                            end: lsp::Position {
15995                                line: 1,
15996                                character: 3,
15997                            },
15998                        },
15999                        new_text: "unsafe".to_string(),
16000                    })),
16001                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16002                    ..Default::default()
16003                },
16004            ])))
16005        });
16006    cx.set_state("fn a() {}\n");
16007    cx.executor().run_until_parked();
16008    cx.update_editor(|editor, window, cx| {
16009        editor.trigger_completion_on_input("n", true, window, cx)
16010    });
16011    cx.executor().run_until_parked();
16012
16013    cx.update_editor(|editor, window, cx| {
16014        editor.confirm_completion(&Default::default(), window, cx)
16015    });
16016    cx.executor().run_until_parked();
16017    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16018}
16019
16020#[gpui::test]
16021async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16022    init_test(cx, |_| {});
16023    let language =
16024        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16025    let mut cx = EditorLspTestContext::new(
16026        language,
16027        lsp::ServerCapabilities {
16028            completion_provider: Some(lsp::CompletionOptions {
16029                ..lsp::CompletionOptions::default()
16030            }),
16031            ..lsp::ServerCapabilities::default()
16032        },
16033        cx,
16034    )
16035    .await;
16036
16037    cx.set_state(
16038        "#ifndef BAR_H
16039#define BAR_H
16040
16041#include <stdbool.h>
16042
16043int fn_branch(bool do_branch1, bool do_branch2);
16044
16045#endif // BAR_H
16046ˇ",
16047    );
16048    cx.executor().run_until_parked();
16049    cx.update_editor(|editor, window, cx| {
16050        editor.handle_input("#", window, cx);
16051    });
16052    cx.executor().run_until_parked();
16053    cx.update_editor(|editor, window, cx| {
16054        editor.handle_input("i", window, cx);
16055    });
16056    cx.executor().run_until_parked();
16057    cx.update_editor(|editor, window, cx| {
16058        editor.handle_input("n", window, cx);
16059    });
16060    cx.executor().run_until_parked();
16061    cx.assert_editor_state(
16062        "#ifndef BAR_H
16063#define BAR_H
16064
16065#include <stdbool.h>
16066
16067int fn_branch(bool do_branch1, bool do_branch2);
16068
16069#endif // BAR_H
16070#inˇ",
16071    );
16072
16073    cx.lsp
16074        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16075            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16076                is_incomplete: false,
16077                item_defaults: None,
16078                items: vec![lsp::CompletionItem {
16079                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16080                    label_details: Some(lsp::CompletionItemLabelDetails {
16081                        detail: Some("header".to_string()),
16082                        description: None,
16083                    }),
16084                    label: " include".to_string(),
16085                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16086                        range: lsp::Range {
16087                            start: lsp::Position {
16088                                line: 8,
16089                                character: 1,
16090                            },
16091                            end: lsp::Position {
16092                                line: 8,
16093                                character: 1,
16094                            },
16095                        },
16096                        new_text: "include \"$0\"".to_string(),
16097                    })),
16098                    sort_text: Some("40b67681include".to_string()),
16099                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16100                    filter_text: Some("include".to_string()),
16101                    insert_text: Some("include \"$0\"".to_string()),
16102                    ..lsp::CompletionItem::default()
16103                }],
16104            })))
16105        });
16106    cx.update_editor(|editor, window, cx| {
16107        editor.show_completions(&ShowCompletions, window, cx);
16108    });
16109    cx.executor().run_until_parked();
16110    cx.update_editor(|editor, window, cx| {
16111        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16112    });
16113    cx.executor().run_until_parked();
16114    cx.assert_editor_state(
16115        "#ifndef BAR_H
16116#define BAR_H
16117
16118#include <stdbool.h>
16119
16120int fn_branch(bool do_branch1, bool do_branch2);
16121
16122#endif // BAR_H
16123#include \"ˇ\"",
16124    );
16125
16126    cx.lsp
16127        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16128            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16129                is_incomplete: true,
16130                item_defaults: None,
16131                items: vec![lsp::CompletionItem {
16132                    kind: Some(lsp::CompletionItemKind::FILE),
16133                    label: "AGL/".to_string(),
16134                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16135                        range: lsp::Range {
16136                            start: lsp::Position {
16137                                line: 8,
16138                                character: 10,
16139                            },
16140                            end: lsp::Position {
16141                                line: 8,
16142                                character: 11,
16143                            },
16144                        },
16145                        new_text: "AGL/".to_string(),
16146                    })),
16147                    sort_text: Some("40b67681AGL/".to_string()),
16148                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16149                    filter_text: Some("AGL/".to_string()),
16150                    insert_text: Some("AGL/".to_string()),
16151                    ..lsp::CompletionItem::default()
16152                }],
16153            })))
16154        });
16155    cx.update_editor(|editor, window, cx| {
16156        editor.show_completions(&ShowCompletions, window, cx);
16157    });
16158    cx.executor().run_until_parked();
16159    cx.update_editor(|editor, window, cx| {
16160        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16161    });
16162    cx.executor().run_until_parked();
16163    cx.assert_editor_state(
16164        r##"#ifndef BAR_H
16165#define BAR_H
16166
16167#include <stdbool.h>
16168
16169int fn_branch(bool do_branch1, bool do_branch2);
16170
16171#endif // BAR_H
16172#include "AGL/ˇ"##,
16173    );
16174
16175    cx.update_editor(|editor, window, cx| {
16176        editor.handle_input("\"", window, cx);
16177    });
16178    cx.executor().run_until_parked();
16179    cx.assert_editor_state(
16180        r##"#ifndef BAR_H
16181#define BAR_H
16182
16183#include <stdbool.h>
16184
16185int fn_branch(bool do_branch1, bool do_branch2);
16186
16187#endif // BAR_H
16188#include "AGL/"ˇ"##,
16189    );
16190}
16191
16192#[gpui::test]
16193async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16194    init_test(cx, |_| {});
16195
16196    let mut cx = EditorLspTestContext::new_rust(
16197        lsp::ServerCapabilities {
16198            completion_provider: Some(lsp::CompletionOptions {
16199                trigger_characters: Some(vec![".".to_string()]),
16200                resolve_provider: Some(true),
16201                ..Default::default()
16202            }),
16203            ..Default::default()
16204        },
16205        cx,
16206    )
16207    .await;
16208
16209    cx.set_state("fn main() { let a = 2ˇ; }");
16210    cx.simulate_keystroke(".");
16211    let completion_item = lsp::CompletionItem {
16212        label: "Some".into(),
16213        kind: Some(lsp::CompletionItemKind::SNIPPET),
16214        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16215        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16216            kind: lsp::MarkupKind::Markdown,
16217            value: "```rust\nSome(2)\n```".to_string(),
16218        })),
16219        deprecated: Some(false),
16220        sort_text: Some("Some".to_string()),
16221        filter_text: Some("Some".to_string()),
16222        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16223        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16224            range: lsp::Range {
16225                start: lsp::Position {
16226                    line: 0,
16227                    character: 22,
16228                },
16229                end: lsp::Position {
16230                    line: 0,
16231                    character: 22,
16232                },
16233            },
16234            new_text: "Some(2)".to_string(),
16235        })),
16236        additional_text_edits: Some(vec![lsp::TextEdit {
16237            range: lsp::Range {
16238                start: lsp::Position {
16239                    line: 0,
16240                    character: 20,
16241                },
16242                end: lsp::Position {
16243                    line: 0,
16244                    character: 22,
16245                },
16246            },
16247            new_text: "".to_string(),
16248        }]),
16249        ..Default::default()
16250    };
16251
16252    let closure_completion_item = completion_item.clone();
16253    let counter = Arc::new(AtomicUsize::new(0));
16254    let counter_clone = counter.clone();
16255    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16256        let task_completion_item = closure_completion_item.clone();
16257        counter_clone.fetch_add(1, atomic::Ordering::Release);
16258        async move {
16259            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16260                is_incomplete: true,
16261                item_defaults: None,
16262                items: vec![task_completion_item],
16263            })))
16264        }
16265    });
16266
16267    cx.condition(|editor, _| editor.context_menu_visible())
16268        .await;
16269    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16270    assert!(request.next().await.is_some());
16271    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16272
16273    cx.simulate_keystrokes("S o m");
16274    cx.condition(|editor, _| editor.context_menu_visible())
16275        .await;
16276    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16277    assert!(request.next().await.is_some());
16278    assert!(request.next().await.is_some());
16279    assert!(request.next().await.is_some());
16280    request.close();
16281    assert!(request.next().await.is_none());
16282    assert_eq!(
16283        counter.load(atomic::Ordering::Acquire),
16284        4,
16285        "With the completions menu open, only one LSP request should happen per input"
16286    );
16287}
16288
16289#[gpui::test]
16290async fn test_toggle_comment(cx: &mut TestAppContext) {
16291    init_test(cx, |_| {});
16292    let mut cx = EditorTestContext::new(cx).await;
16293    let language = Arc::new(Language::new(
16294        LanguageConfig {
16295            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16296            ..Default::default()
16297        },
16298        Some(tree_sitter_rust::LANGUAGE.into()),
16299    ));
16300    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16301
16302    // If multiple selections intersect a line, the line is only toggled once.
16303    cx.set_state(indoc! {"
16304        fn a() {
16305            «//b();
16306            ˇ»// «c();
16307            //ˇ»  d();
16308        }
16309    "});
16310
16311    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16312
16313    cx.assert_editor_state(indoc! {"
16314        fn a() {
16315            «b();
16316            ˇ»«c();
16317            ˇ» d();
16318        }
16319    "});
16320
16321    // The comment prefix is inserted at the same column for every line in a
16322    // selection.
16323    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16324
16325    cx.assert_editor_state(indoc! {"
16326        fn a() {
16327            // «b();
16328            ˇ»// «c();
16329            ˇ» // d();
16330        }
16331    "});
16332
16333    // If a selection ends at the beginning of a line, that line is not toggled.
16334    cx.set_selections_state(indoc! {"
16335        fn a() {
16336            // b();
16337            «// c();
16338        ˇ»     // d();
16339        }
16340    "});
16341
16342    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16343
16344    cx.assert_editor_state(indoc! {"
16345        fn a() {
16346            // b();
16347            «c();
16348        ˇ»     // d();
16349        }
16350    "});
16351
16352    // If a selection span a single line and is empty, the line is toggled.
16353    cx.set_state(indoc! {"
16354        fn a() {
16355            a();
16356            b();
16357        ˇ
16358        }
16359    "});
16360
16361    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16362
16363    cx.assert_editor_state(indoc! {"
16364        fn a() {
16365            a();
16366            b();
16367        //•ˇ
16368        }
16369    "});
16370
16371    // If a selection span multiple lines, empty lines are not toggled.
16372    cx.set_state(indoc! {"
16373        fn a() {
16374            «a();
16375
16376            c();ˇ»
16377        }
16378    "});
16379
16380    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16381
16382    cx.assert_editor_state(indoc! {"
16383        fn a() {
16384            // «a();
16385
16386            // c();ˇ»
16387        }
16388    "});
16389
16390    // If a selection includes multiple comment prefixes, all lines are uncommented.
16391    cx.set_state(indoc! {"
16392        fn a() {
16393            «// a();
16394            /// b();
16395            //! c();ˇ»
16396        }
16397    "});
16398
16399    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16400
16401    cx.assert_editor_state(indoc! {"
16402        fn a() {
16403            «a();
16404            b();
16405            c();ˇ»
16406        }
16407    "});
16408}
16409
16410#[gpui::test]
16411async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16412    init_test(cx, |_| {});
16413    let mut cx = EditorTestContext::new(cx).await;
16414    let language = Arc::new(Language::new(
16415        LanguageConfig {
16416            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16417            ..Default::default()
16418        },
16419        Some(tree_sitter_rust::LANGUAGE.into()),
16420    ));
16421    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16422
16423    let toggle_comments = &ToggleComments {
16424        advance_downwards: false,
16425        ignore_indent: true,
16426    };
16427
16428    // If multiple selections intersect a line, the line is only toggled once.
16429    cx.set_state(indoc! {"
16430        fn a() {
16431        //    «b();
16432        //    c();
16433        //    ˇ» d();
16434        }
16435    "});
16436
16437    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16438
16439    cx.assert_editor_state(indoc! {"
16440        fn a() {
16441            «b();
16442            c();
16443            ˇ» d();
16444        }
16445    "});
16446
16447    // The comment prefix is inserted at the beginning of each line
16448    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16449
16450    cx.assert_editor_state(indoc! {"
16451        fn a() {
16452        //    «b();
16453        //    c();
16454        //    ˇ» d();
16455        }
16456    "});
16457
16458    // If a selection ends at the beginning of a line, that line is not toggled.
16459    cx.set_selections_state(indoc! {"
16460        fn a() {
16461        //    b();
16462        //    «c();
16463        ˇ»//     d();
16464        }
16465    "});
16466
16467    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16468
16469    cx.assert_editor_state(indoc! {"
16470        fn a() {
16471        //    b();
16472            «c();
16473        ˇ»//     d();
16474        }
16475    "});
16476
16477    // If a selection span a single line and is empty, the line is toggled.
16478    cx.set_state(indoc! {"
16479        fn a() {
16480            a();
16481            b();
16482        ˇ
16483        }
16484    "});
16485
16486    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16487
16488    cx.assert_editor_state(indoc! {"
16489        fn a() {
16490            a();
16491            b();
16492        //ˇ
16493        }
16494    "});
16495
16496    // If a selection span multiple lines, empty lines are not toggled.
16497    cx.set_state(indoc! {"
16498        fn a() {
16499            «a();
16500
16501            c();ˇ»
16502        }
16503    "});
16504
16505    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16506
16507    cx.assert_editor_state(indoc! {"
16508        fn a() {
16509        //    «a();
16510
16511        //    c();ˇ»
16512        }
16513    "});
16514
16515    // If a selection includes multiple comment prefixes, all lines are uncommented.
16516    cx.set_state(indoc! {"
16517        fn a() {
16518        //    «a();
16519        ///    b();
16520        //!    c();ˇ»
16521        }
16522    "});
16523
16524    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16525
16526    cx.assert_editor_state(indoc! {"
16527        fn a() {
16528            «a();
16529            b();
16530            c();ˇ»
16531        }
16532    "});
16533}
16534
16535#[gpui::test]
16536async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16537    init_test(cx, |_| {});
16538
16539    let language = Arc::new(Language::new(
16540        LanguageConfig {
16541            line_comments: vec!["// ".into()],
16542            ..Default::default()
16543        },
16544        Some(tree_sitter_rust::LANGUAGE.into()),
16545    ));
16546
16547    let mut cx = EditorTestContext::new(cx).await;
16548
16549    cx.language_registry().add(language.clone());
16550    cx.update_buffer(|buffer, cx| {
16551        buffer.set_language(Some(language), cx);
16552    });
16553
16554    let toggle_comments = &ToggleComments {
16555        advance_downwards: true,
16556        ignore_indent: false,
16557    };
16558
16559    // Single cursor on one line -> advance
16560    // Cursor moves horizontally 3 characters as well on non-blank line
16561    cx.set_state(indoc!(
16562        "fn a() {
16563             ˇdog();
16564             cat();
16565        }"
16566    ));
16567    cx.update_editor(|editor, window, cx| {
16568        editor.toggle_comments(toggle_comments, window, cx);
16569    });
16570    cx.assert_editor_state(indoc!(
16571        "fn a() {
16572             // dog();
16573             catˇ();
16574        }"
16575    ));
16576
16577    // Single selection on one line -> don't advance
16578    cx.set_state(indoc!(
16579        "fn a() {
16580             «dog()ˇ»;
16581             cat();
16582        }"
16583    ));
16584    cx.update_editor(|editor, window, cx| {
16585        editor.toggle_comments(toggle_comments, window, cx);
16586    });
16587    cx.assert_editor_state(indoc!(
16588        "fn a() {
16589             // «dog()ˇ»;
16590             cat();
16591        }"
16592    ));
16593
16594    // Multiple cursors on one line -> advance
16595    cx.set_state(indoc!(
16596        "fn a() {
16597             ˇdˇog();
16598             cat();
16599        }"
16600    ));
16601    cx.update_editor(|editor, window, cx| {
16602        editor.toggle_comments(toggle_comments, window, cx);
16603    });
16604    cx.assert_editor_state(indoc!(
16605        "fn a() {
16606             // dog();
16607             catˇ(ˇ);
16608        }"
16609    ));
16610
16611    // Multiple cursors on one line, with selection -> don't advance
16612    cx.set_state(indoc!(
16613        "fn a() {
16614             ˇdˇog«()ˇ»;
16615             cat();
16616        }"
16617    ));
16618    cx.update_editor(|editor, window, cx| {
16619        editor.toggle_comments(toggle_comments, window, cx);
16620    });
16621    cx.assert_editor_state(indoc!(
16622        "fn a() {
16623             // ˇdˇog«()ˇ»;
16624             cat();
16625        }"
16626    ));
16627
16628    // Single cursor on one line -> advance
16629    // Cursor moves to column 0 on blank line
16630    cx.set_state(indoc!(
16631        "fn a() {
16632             ˇdog();
16633
16634             cat();
16635        }"
16636    ));
16637    cx.update_editor(|editor, window, cx| {
16638        editor.toggle_comments(toggle_comments, window, cx);
16639    });
16640    cx.assert_editor_state(indoc!(
16641        "fn a() {
16642             // dog();
16643        ˇ
16644             cat();
16645        }"
16646    ));
16647
16648    // Single cursor on one line -> advance
16649    // Cursor starts and ends at column 0
16650    cx.set_state(indoc!(
16651        "fn a() {
16652         ˇ    dog();
16653             cat();
16654        }"
16655    ));
16656    cx.update_editor(|editor, window, cx| {
16657        editor.toggle_comments(toggle_comments, window, cx);
16658    });
16659    cx.assert_editor_state(indoc!(
16660        "fn a() {
16661             // dog();
16662         ˇ    cat();
16663        }"
16664    ));
16665}
16666
16667#[gpui::test]
16668async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16669    init_test(cx, |_| {});
16670
16671    let mut cx = EditorTestContext::new(cx).await;
16672
16673    let html_language = Arc::new(
16674        Language::new(
16675            LanguageConfig {
16676                name: "HTML".into(),
16677                block_comment: Some(BlockCommentConfig {
16678                    start: "<!-- ".into(),
16679                    prefix: "".into(),
16680                    end: " -->".into(),
16681                    tab_size: 0,
16682                }),
16683                ..Default::default()
16684            },
16685            Some(tree_sitter_html::LANGUAGE.into()),
16686        )
16687        .with_injection_query(
16688            r#"
16689            (script_element
16690                (raw_text) @injection.content
16691                (#set! injection.language "javascript"))
16692            "#,
16693        )
16694        .unwrap(),
16695    );
16696
16697    let javascript_language = Arc::new(Language::new(
16698        LanguageConfig {
16699            name: "JavaScript".into(),
16700            line_comments: vec!["// ".into()],
16701            ..Default::default()
16702        },
16703        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16704    ));
16705
16706    cx.language_registry().add(html_language.clone());
16707    cx.language_registry().add(javascript_language);
16708    cx.update_buffer(|buffer, cx| {
16709        buffer.set_language(Some(html_language), cx);
16710    });
16711
16712    // Toggle comments for empty selections
16713    cx.set_state(
16714        &r#"
16715            <p>A</p>ˇ
16716            <p>B</p>ˇ
16717            <p>C</p>ˇ
16718        "#
16719        .unindent(),
16720    );
16721    cx.update_editor(|editor, window, cx| {
16722        editor.toggle_comments(&ToggleComments::default(), window, cx)
16723    });
16724    cx.assert_editor_state(
16725        &r#"
16726            <!-- <p>A</p>ˇ -->
16727            <!-- <p>B</p>ˇ -->
16728            <!-- <p>C</p>ˇ -->
16729        "#
16730        .unindent(),
16731    );
16732    cx.update_editor(|editor, window, cx| {
16733        editor.toggle_comments(&ToggleComments::default(), window, cx)
16734    });
16735    cx.assert_editor_state(
16736        &r#"
16737            <p>A</p>ˇ
16738            <p>B</p>ˇ
16739            <p>C</p>ˇ
16740        "#
16741        .unindent(),
16742    );
16743
16744    // Toggle comments for mixture of empty and non-empty selections, where
16745    // multiple selections occupy a given line.
16746    cx.set_state(
16747        &r#"
16748            <p>A«</p>
16749            <p>ˇ»B</p>ˇ
16750            <p>C«</p>
16751            <p>ˇ»D</p>ˇ
16752        "#
16753        .unindent(),
16754    );
16755
16756    cx.update_editor(|editor, window, cx| {
16757        editor.toggle_comments(&ToggleComments::default(), window, cx)
16758    });
16759    cx.assert_editor_state(
16760        &r#"
16761            <!-- <p>A«</p>
16762            <p>ˇ»B</p>ˇ -->
16763            <!-- <p>C«</p>
16764            <p>ˇ»D</p>ˇ -->
16765        "#
16766        .unindent(),
16767    );
16768    cx.update_editor(|editor, window, cx| {
16769        editor.toggle_comments(&ToggleComments::default(), window, cx)
16770    });
16771    cx.assert_editor_state(
16772        &r#"
16773            <p>A«</p>
16774            <p>ˇ»B</p>ˇ
16775            <p>C«</p>
16776            <p>ˇ»D</p>ˇ
16777        "#
16778        .unindent(),
16779    );
16780
16781    // Toggle comments when different languages are active for different
16782    // selections.
16783    cx.set_state(
16784        &r#"
16785            ˇ<script>
16786                ˇvar x = new Y();
16787            ˇ</script>
16788        "#
16789        .unindent(),
16790    );
16791    cx.executor().run_until_parked();
16792    cx.update_editor(|editor, window, cx| {
16793        editor.toggle_comments(&ToggleComments::default(), window, cx)
16794    });
16795    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16796    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16797    cx.assert_editor_state(
16798        &r#"
16799            <!-- ˇ<script> -->
16800                // ˇvar x = new Y();
16801            <!-- ˇ</script> -->
16802        "#
16803        .unindent(),
16804    );
16805}
16806
16807#[gpui::test]
16808fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16809    init_test(cx, |_| {});
16810
16811    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16812    let multibuffer = cx.new(|cx| {
16813        let mut multibuffer = MultiBuffer::new(ReadWrite);
16814        multibuffer.push_excerpts(
16815            buffer.clone(),
16816            [
16817                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16818                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16819            ],
16820            cx,
16821        );
16822        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16823        multibuffer
16824    });
16825
16826    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16827    editor.update_in(cx, |editor, window, cx| {
16828        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16829        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16830            s.select_ranges([
16831                Point::new(0, 0)..Point::new(0, 0),
16832                Point::new(1, 0)..Point::new(1, 0),
16833            ])
16834        });
16835
16836        editor.handle_input("X", window, cx);
16837        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16838        assert_eq!(
16839            editor.selections.ranges(&editor.display_snapshot(cx)),
16840            [
16841                Point::new(0, 1)..Point::new(0, 1),
16842                Point::new(1, 1)..Point::new(1, 1),
16843            ]
16844        );
16845
16846        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16848            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16849        });
16850        editor.backspace(&Default::default(), window, cx);
16851        assert_eq!(editor.text(cx), "Xa\nbbb");
16852        assert_eq!(
16853            editor.selections.ranges(&editor.display_snapshot(cx)),
16854            [Point::new(1, 0)..Point::new(1, 0)]
16855        );
16856
16857        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16858            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16859        });
16860        editor.backspace(&Default::default(), window, cx);
16861        assert_eq!(editor.text(cx), "X\nbb");
16862        assert_eq!(
16863            editor.selections.ranges(&editor.display_snapshot(cx)),
16864            [Point::new(0, 1)..Point::new(0, 1)]
16865        );
16866    });
16867}
16868
16869#[gpui::test]
16870fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16871    init_test(cx, |_| {});
16872
16873    let markers = vec![('[', ']').into(), ('(', ')').into()];
16874    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16875        indoc! {"
16876            [aaaa
16877            (bbbb]
16878            cccc)",
16879        },
16880        markers.clone(),
16881    );
16882    let excerpt_ranges = markers.into_iter().map(|marker| {
16883        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16884        ExcerptRange::new(context)
16885    });
16886    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16887    let multibuffer = cx.new(|cx| {
16888        let mut multibuffer = MultiBuffer::new(ReadWrite);
16889        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16890        multibuffer
16891    });
16892
16893    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16894    editor.update_in(cx, |editor, window, cx| {
16895        let (expected_text, selection_ranges) = marked_text_ranges(
16896            indoc! {"
16897                aaaa
16898                bˇbbb
16899                bˇbbˇb
16900                cccc"
16901            },
16902            true,
16903        );
16904        assert_eq!(editor.text(cx), expected_text);
16905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16906            s.select_ranges(
16907                selection_ranges
16908                    .iter()
16909                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16910            )
16911        });
16912
16913        editor.handle_input("X", window, cx);
16914
16915        let (expected_text, expected_selections) = marked_text_ranges(
16916            indoc! {"
16917                aaaa
16918                bXˇbbXb
16919                bXˇbbXˇb
16920                cccc"
16921            },
16922            false,
16923        );
16924        assert_eq!(editor.text(cx), expected_text);
16925        assert_eq!(
16926            editor.selections.ranges(&editor.display_snapshot(cx)),
16927            expected_selections
16928                .iter()
16929                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16930                .collect::<Vec<_>>()
16931        );
16932
16933        editor.newline(&Newline, window, cx);
16934        let (expected_text, expected_selections) = marked_text_ranges(
16935            indoc! {"
16936                aaaa
16937                bX
16938                ˇbbX
16939                b
16940                bX
16941                ˇbbX
16942                ˇb
16943                cccc"
16944            },
16945            false,
16946        );
16947        assert_eq!(editor.text(cx), expected_text);
16948        assert_eq!(
16949            editor.selections.ranges(&editor.display_snapshot(cx)),
16950            expected_selections
16951                .iter()
16952                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16953                .collect::<Vec<_>>()
16954        );
16955    });
16956}
16957
16958#[gpui::test]
16959fn test_refresh_selections(cx: &mut TestAppContext) {
16960    init_test(cx, |_| {});
16961
16962    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16963    let mut excerpt1_id = None;
16964    let multibuffer = cx.new(|cx| {
16965        let mut multibuffer = MultiBuffer::new(ReadWrite);
16966        excerpt1_id = multibuffer
16967            .push_excerpts(
16968                buffer.clone(),
16969                [
16970                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16971                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16972                ],
16973                cx,
16974            )
16975            .into_iter()
16976            .next();
16977        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16978        multibuffer
16979    });
16980
16981    let editor = cx.add_window(|window, cx| {
16982        let mut editor = build_editor(multibuffer.clone(), window, cx);
16983        let snapshot = editor.snapshot(window, cx);
16984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16985            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16986        });
16987        editor.begin_selection(
16988            Point::new(2, 1).to_display_point(&snapshot),
16989            true,
16990            1,
16991            window,
16992            cx,
16993        );
16994        assert_eq!(
16995            editor.selections.ranges(&editor.display_snapshot(cx)),
16996            [
16997                Point::new(1, 3)..Point::new(1, 3),
16998                Point::new(2, 1)..Point::new(2, 1),
16999            ]
17000        );
17001        editor
17002    });
17003
17004    // Refreshing selections is a no-op when excerpts haven't changed.
17005    _ = editor.update(cx, |editor, window, cx| {
17006        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17007        assert_eq!(
17008            editor.selections.ranges(&editor.display_snapshot(cx)),
17009            [
17010                Point::new(1, 3)..Point::new(1, 3),
17011                Point::new(2, 1)..Point::new(2, 1),
17012            ]
17013        );
17014    });
17015
17016    multibuffer.update(cx, |multibuffer, cx| {
17017        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17018    });
17019    _ = editor.update(cx, |editor, window, cx| {
17020        // Removing an excerpt causes the first selection to become degenerate.
17021        assert_eq!(
17022            editor.selections.ranges(&editor.display_snapshot(cx)),
17023            [
17024                Point::new(0, 0)..Point::new(0, 0),
17025                Point::new(0, 1)..Point::new(0, 1)
17026            ]
17027        );
17028
17029        // Refreshing selections will relocate the first selection to the original buffer
17030        // location.
17031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17032        assert_eq!(
17033            editor.selections.ranges(&editor.display_snapshot(cx)),
17034            [
17035                Point::new(0, 1)..Point::new(0, 1),
17036                Point::new(0, 3)..Point::new(0, 3)
17037            ]
17038        );
17039        assert!(editor.selections.pending_anchor().is_some());
17040    });
17041}
17042
17043#[gpui::test]
17044fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17045    init_test(cx, |_| {});
17046
17047    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17048    let mut excerpt1_id = None;
17049    let multibuffer = cx.new(|cx| {
17050        let mut multibuffer = MultiBuffer::new(ReadWrite);
17051        excerpt1_id = multibuffer
17052            .push_excerpts(
17053                buffer.clone(),
17054                [
17055                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17056                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17057                ],
17058                cx,
17059            )
17060            .into_iter()
17061            .next();
17062        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17063        multibuffer
17064    });
17065
17066    let editor = cx.add_window(|window, cx| {
17067        let mut editor = build_editor(multibuffer.clone(), window, cx);
17068        let snapshot = editor.snapshot(window, cx);
17069        editor.begin_selection(
17070            Point::new(1, 3).to_display_point(&snapshot),
17071            false,
17072            1,
17073            window,
17074            cx,
17075        );
17076        assert_eq!(
17077            editor.selections.ranges(&editor.display_snapshot(cx)),
17078            [Point::new(1, 3)..Point::new(1, 3)]
17079        );
17080        editor
17081    });
17082
17083    multibuffer.update(cx, |multibuffer, cx| {
17084        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17085    });
17086    _ = editor.update(cx, |editor, window, cx| {
17087        assert_eq!(
17088            editor.selections.ranges(&editor.display_snapshot(cx)),
17089            [Point::new(0, 0)..Point::new(0, 0)]
17090        );
17091
17092        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17094        assert_eq!(
17095            editor.selections.ranges(&editor.display_snapshot(cx)),
17096            [Point::new(0, 3)..Point::new(0, 3)]
17097        );
17098        assert!(editor.selections.pending_anchor().is_some());
17099    });
17100}
17101
17102#[gpui::test]
17103async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17104    init_test(cx, |_| {});
17105
17106    let language = Arc::new(
17107        Language::new(
17108            LanguageConfig {
17109                brackets: BracketPairConfig {
17110                    pairs: vec![
17111                        BracketPair {
17112                            start: "{".to_string(),
17113                            end: "}".to_string(),
17114                            close: true,
17115                            surround: true,
17116                            newline: true,
17117                        },
17118                        BracketPair {
17119                            start: "/* ".to_string(),
17120                            end: " */".to_string(),
17121                            close: true,
17122                            surround: true,
17123                            newline: true,
17124                        },
17125                    ],
17126                    ..Default::default()
17127                },
17128                ..Default::default()
17129            },
17130            Some(tree_sitter_rust::LANGUAGE.into()),
17131        )
17132        .with_indents_query("")
17133        .unwrap(),
17134    );
17135
17136    let text = concat!(
17137        "{   }\n",     //
17138        "  x\n",       //
17139        "  /*   */\n", //
17140        "x\n",         //
17141        "{{} }\n",     //
17142    );
17143
17144    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17145    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17146    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17147    editor
17148        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17149        .await;
17150
17151    editor.update_in(cx, |editor, window, cx| {
17152        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17153            s.select_display_ranges([
17154                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17155                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17156                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17157            ])
17158        });
17159        editor.newline(&Newline, window, cx);
17160
17161        assert_eq!(
17162            editor.buffer().read(cx).read(cx).text(),
17163            concat!(
17164                "{ \n",    // Suppress rustfmt
17165                "\n",      //
17166                "}\n",     //
17167                "  x\n",   //
17168                "  /* \n", //
17169                "  \n",    //
17170                "  */\n",  //
17171                "x\n",     //
17172                "{{} \n",  //
17173                "}\n",     //
17174            )
17175        );
17176    });
17177}
17178
17179#[gpui::test]
17180fn test_highlighted_ranges(cx: &mut TestAppContext) {
17181    init_test(cx, |_| {});
17182
17183    let editor = cx.add_window(|window, cx| {
17184        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17185        build_editor(buffer, window, cx)
17186    });
17187
17188    _ = editor.update(cx, |editor, window, cx| {
17189        struct Type1;
17190        struct Type2;
17191
17192        let buffer = editor.buffer.read(cx).snapshot(cx);
17193
17194        let anchor_range =
17195            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17196
17197        editor.highlight_background::<Type1>(
17198            &[
17199                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17200                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17201                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17202                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17203            ],
17204            |_, _| Hsla::red(),
17205            cx,
17206        );
17207        editor.highlight_background::<Type2>(
17208            &[
17209                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17210                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17211                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17212                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17213            ],
17214            |_, _| Hsla::green(),
17215            cx,
17216        );
17217
17218        let snapshot = editor.snapshot(window, cx);
17219        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17220            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17221            &snapshot,
17222            cx.theme(),
17223        );
17224        assert_eq!(
17225            highlighted_ranges,
17226            &[
17227                (
17228                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17229                    Hsla::green(),
17230                ),
17231                (
17232                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17233                    Hsla::red(),
17234                ),
17235                (
17236                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17237                    Hsla::green(),
17238                ),
17239                (
17240                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17241                    Hsla::red(),
17242                ),
17243            ]
17244        );
17245        assert_eq!(
17246            editor.sorted_background_highlights_in_range(
17247                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17248                &snapshot,
17249                cx.theme(),
17250            ),
17251            &[(
17252                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17253                Hsla::red(),
17254            )]
17255        );
17256    });
17257}
17258
17259#[gpui::test]
17260async fn test_following(cx: &mut TestAppContext) {
17261    init_test(cx, |_| {});
17262
17263    let fs = FakeFs::new(cx.executor());
17264    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17265
17266    let buffer = project.update(cx, |project, cx| {
17267        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17268        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17269    });
17270    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17271    let follower = cx.update(|cx| {
17272        cx.open_window(
17273            WindowOptions {
17274                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17275                    gpui::Point::new(px(0.), px(0.)),
17276                    gpui::Point::new(px(10.), px(80.)),
17277                ))),
17278                ..Default::default()
17279            },
17280            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17281        )
17282        .unwrap()
17283    });
17284
17285    let is_still_following = Rc::new(RefCell::new(true));
17286    let follower_edit_event_count = Rc::new(RefCell::new(0));
17287    let pending_update = Rc::new(RefCell::new(None));
17288    let leader_entity = leader.root(cx).unwrap();
17289    let follower_entity = follower.root(cx).unwrap();
17290    _ = follower.update(cx, {
17291        let update = pending_update.clone();
17292        let is_still_following = is_still_following.clone();
17293        let follower_edit_event_count = follower_edit_event_count.clone();
17294        |_, window, cx| {
17295            cx.subscribe_in(
17296                &leader_entity,
17297                window,
17298                move |_, leader, event, window, cx| {
17299                    leader.read(cx).add_event_to_update_proto(
17300                        event,
17301                        &mut update.borrow_mut(),
17302                        window,
17303                        cx,
17304                    );
17305                },
17306            )
17307            .detach();
17308
17309            cx.subscribe_in(
17310                &follower_entity,
17311                window,
17312                move |_, _, event: &EditorEvent, _window, _cx| {
17313                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17314                        *is_still_following.borrow_mut() = false;
17315                    }
17316
17317                    if let EditorEvent::BufferEdited = event {
17318                        *follower_edit_event_count.borrow_mut() += 1;
17319                    }
17320                },
17321            )
17322            .detach();
17323        }
17324    });
17325
17326    // Update the selections only
17327    _ = leader.update(cx, |leader, window, cx| {
17328        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17329            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17330        });
17331    });
17332    follower
17333        .update(cx, |follower, window, cx| {
17334            follower.apply_update_proto(
17335                &project,
17336                pending_update.borrow_mut().take().unwrap(),
17337                window,
17338                cx,
17339            )
17340        })
17341        .unwrap()
17342        .await
17343        .unwrap();
17344    _ = follower.update(cx, |follower, _, cx| {
17345        assert_eq!(
17346            follower.selections.ranges(&follower.display_snapshot(cx)),
17347            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17348        );
17349    });
17350    assert!(*is_still_following.borrow());
17351    assert_eq!(*follower_edit_event_count.borrow(), 0);
17352
17353    // Update the scroll position only
17354    _ = leader.update(cx, |leader, window, cx| {
17355        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17356    });
17357    follower
17358        .update(cx, |follower, window, cx| {
17359            follower.apply_update_proto(
17360                &project,
17361                pending_update.borrow_mut().take().unwrap(),
17362                window,
17363                cx,
17364            )
17365        })
17366        .unwrap()
17367        .await
17368        .unwrap();
17369    assert_eq!(
17370        follower
17371            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17372            .unwrap(),
17373        gpui::Point::new(1.5, 3.5)
17374    );
17375    assert!(*is_still_following.borrow());
17376    assert_eq!(*follower_edit_event_count.borrow(), 0);
17377
17378    // Update the selections and scroll position. The follower's scroll position is updated
17379    // via autoscroll, not via the leader's exact scroll position.
17380    _ = leader.update(cx, |leader, window, cx| {
17381        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17382            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17383        });
17384        leader.request_autoscroll(Autoscroll::newest(), cx);
17385        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17386    });
17387    follower
17388        .update(cx, |follower, window, cx| {
17389            follower.apply_update_proto(
17390                &project,
17391                pending_update.borrow_mut().take().unwrap(),
17392                window,
17393                cx,
17394            )
17395        })
17396        .unwrap()
17397        .await
17398        .unwrap();
17399    _ = follower.update(cx, |follower, _, cx| {
17400        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17401        assert_eq!(
17402            follower.selections.ranges(&follower.display_snapshot(cx)),
17403            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17404        );
17405    });
17406    assert!(*is_still_following.borrow());
17407
17408    // Creating a pending selection that precedes another selection
17409    _ = leader.update(cx, |leader, window, cx| {
17410        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17411            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17412        });
17413        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17414    });
17415    follower
17416        .update(cx, |follower, window, cx| {
17417            follower.apply_update_proto(
17418                &project,
17419                pending_update.borrow_mut().take().unwrap(),
17420                window,
17421                cx,
17422            )
17423        })
17424        .unwrap()
17425        .await
17426        .unwrap();
17427    _ = follower.update(cx, |follower, _, cx| {
17428        assert_eq!(
17429            follower.selections.ranges(&follower.display_snapshot(cx)),
17430            vec![
17431                MultiBufferOffset(0)..MultiBufferOffset(0),
17432                MultiBufferOffset(1)..MultiBufferOffset(1)
17433            ]
17434        );
17435    });
17436    assert!(*is_still_following.borrow());
17437
17438    // Extend the pending selection so that it surrounds another selection
17439    _ = leader.update(cx, |leader, window, cx| {
17440        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17441    });
17442    follower
17443        .update(cx, |follower, window, cx| {
17444            follower.apply_update_proto(
17445                &project,
17446                pending_update.borrow_mut().take().unwrap(),
17447                window,
17448                cx,
17449            )
17450        })
17451        .unwrap()
17452        .await
17453        .unwrap();
17454    _ = follower.update(cx, |follower, _, cx| {
17455        assert_eq!(
17456            follower.selections.ranges(&follower.display_snapshot(cx)),
17457            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17458        );
17459    });
17460
17461    // Scrolling locally breaks the follow
17462    _ = follower.update(cx, |follower, window, cx| {
17463        let top_anchor = follower
17464            .buffer()
17465            .read(cx)
17466            .read(cx)
17467            .anchor_after(MultiBufferOffset(0));
17468        follower.set_scroll_anchor(
17469            ScrollAnchor {
17470                anchor: top_anchor,
17471                offset: gpui::Point::new(0.0, 0.5),
17472            },
17473            window,
17474            cx,
17475        );
17476    });
17477    assert!(!(*is_still_following.borrow()));
17478}
17479
17480#[gpui::test]
17481async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17482    init_test(cx, |_| {});
17483
17484    let fs = FakeFs::new(cx.executor());
17485    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17486    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17487    let pane = workspace
17488        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17489        .unwrap();
17490
17491    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17492
17493    let leader = pane.update_in(cx, |_, window, cx| {
17494        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17495        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17496    });
17497
17498    // Start following the editor when it has no excerpts.
17499    let mut state_message =
17500        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17501    let workspace_entity = workspace.root(cx).unwrap();
17502    let follower_1 = cx
17503        .update_window(*workspace.deref(), |_, window, cx| {
17504            Editor::from_state_proto(
17505                workspace_entity,
17506                ViewId {
17507                    creator: CollaboratorId::PeerId(PeerId::default()),
17508                    id: 0,
17509                },
17510                &mut state_message,
17511                window,
17512                cx,
17513            )
17514        })
17515        .unwrap()
17516        .unwrap()
17517        .await
17518        .unwrap();
17519
17520    let update_message = Rc::new(RefCell::new(None));
17521    follower_1.update_in(cx, {
17522        let update = update_message.clone();
17523        |_, window, cx| {
17524            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17525                leader.read(cx).add_event_to_update_proto(
17526                    event,
17527                    &mut update.borrow_mut(),
17528                    window,
17529                    cx,
17530                );
17531            })
17532            .detach();
17533        }
17534    });
17535
17536    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17537        (
17538            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17539            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17540        )
17541    });
17542
17543    // Insert some excerpts.
17544    leader.update(cx, |leader, cx| {
17545        leader.buffer.update(cx, |multibuffer, cx| {
17546            multibuffer.set_excerpts_for_path(
17547                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17548                buffer_1.clone(),
17549                vec![
17550                    Point::row_range(0..3),
17551                    Point::row_range(1..6),
17552                    Point::row_range(12..15),
17553                ],
17554                0,
17555                cx,
17556            );
17557            multibuffer.set_excerpts_for_path(
17558                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17559                buffer_2.clone(),
17560                vec![Point::row_range(0..6), Point::row_range(8..12)],
17561                0,
17562                cx,
17563            );
17564        });
17565    });
17566
17567    // Apply the update of adding the excerpts.
17568    follower_1
17569        .update_in(cx, |follower, window, cx| {
17570            follower.apply_update_proto(
17571                &project,
17572                update_message.borrow().clone().unwrap(),
17573                window,
17574                cx,
17575            )
17576        })
17577        .await
17578        .unwrap();
17579    assert_eq!(
17580        follower_1.update(cx, |editor, cx| editor.text(cx)),
17581        leader.update(cx, |editor, cx| editor.text(cx))
17582    );
17583    update_message.borrow_mut().take();
17584
17585    // Start following separately after it already has excerpts.
17586    let mut state_message =
17587        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17588    let workspace_entity = workspace.root(cx).unwrap();
17589    let follower_2 = cx
17590        .update_window(*workspace.deref(), |_, window, cx| {
17591            Editor::from_state_proto(
17592                workspace_entity,
17593                ViewId {
17594                    creator: CollaboratorId::PeerId(PeerId::default()),
17595                    id: 0,
17596                },
17597                &mut state_message,
17598                window,
17599                cx,
17600            )
17601        })
17602        .unwrap()
17603        .unwrap()
17604        .await
17605        .unwrap();
17606    assert_eq!(
17607        follower_2.update(cx, |editor, cx| editor.text(cx)),
17608        leader.update(cx, |editor, cx| editor.text(cx))
17609    );
17610
17611    // Remove some excerpts.
17612    leader.update(cx, |leader, cx| {
17613        leader.buffer.update(cx, |multibuffer, cx| {
17614            let excerpt_ids = multibuffer.excerpt_ids();
17615            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17616            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17617        });
17618    });
17619
17620    // Apply the update of removing the excerpts.
17621    follower_1
17622        .update_in(cx, |follower, window, cx| {
17623            follower.apply_update_proto(
17624                &project,
17625                update_message.borrow().clone().unwrap(),
17626                window,
17627                cx,
17628            )
17629        })
17630        .await
17631        .unwrap();
17632    follower_2
17633        .update_in(cx, |follower, window, cx| {
17634            follower.apply_update_proto(
17635                &project,
17636                update_message.borrow().clone().unwrap(),
17637                window,
17638                cx,
17639            )
17640        })
17641        .await
17642        .unwrap();
17643    update_message.borrow_mut().take();
17644    assert_eq!(
17645        follower_1.update(cx, |editor, cx| editor.text(cx)),
17646        leader.update(cx, |editor, cx| editor.text(cx))
17647    );
17648}
17649
17650#[gpui::test]
17651async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17652    init_test(cx, |_| {});
17653
17654    let mut cx = EditorTestContext::new(cx).await;
17655    let lsp_store =
17656        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17657
17658    cx.set_state(indoc! {"
17659        ˇfn func(abc def: i32) -> u32 {
17660        }
17661    "});
17662
17663    cx.update(|_, cx| {
17664        lsp_store.update(cx, |lsp_store, cx| {
17665            lsp_store
17666                .update_diagnostics(
17667                    LanguageServerId(0),
17668                    lsp::PublishDiagnosticsParams {
17669                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17670                        version: None,
17671                        diagnostics: vec![
17672                            lsp::Diagnostic {
17673                                range: lsp::Range::new(
17674                                    lsp::Position::new(0, 11),
17675                                    lsp::Position::new(0, 12),
17676                                ),
17677                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17678                                ..Default::default()
17679                            },
17680                            lsp::Diagnostic {
17681                                range: lsp::Range::new(
17682                                    lsp::Position::new(0, 12),
17683                                    lsp::Position::new(0, 15),
17684                                ),
17685                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17686                                ..Default::default()
17687                            },
17688                            lsp::Diagnostic {
17689                                range: lsp::Range::new(
17690                                    lsp::Position::new(0, 25),
17691                                    lsp::Position::new(0, 28),
17692                                ),
17693                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17694                                ..Default::default()
17695                            },
17696                        ],
17697                    },
17698                    None,
17699                    DiagnosticSourceKind::Pushed,
17700                    &[],
17701                    cx,
17702                )
17703                .unwrap()
17704        });
17705    });
17706
17707    executor.run_until_parked();
17708
17709    cx.update_editor(|editor, window, cx| {
17710        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17711    });
17712
17713    cx.assert_editor_state(indoc! {"
17714        fn func(abc def: i32) -> ˇu32 {
17715        }
17716    "});
17717
17718    cx.update_editor(|editor, window, cx| {
17719        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17720    });
17721
17722    cx.assert_editor_state(indoc! {"
17723        fn func(abc ˇdef: i32) -> u32 {
17724        }
17725    "});
17726
17727    cx.update_editor(|editor, window, cx| {
17728        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17729    });
17730
17731    cx.assert_editor_state(indoc! {"
17732        fn func(abcˇ def: i32) -> u32 {
17733        }
17734    "});
17735
17736    cx.update_editor(|editor, window, cx| {
17737        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17738    });
17739
17740    cx.assert_editor_state(indoc! {"
17741        fn func(abc def: i32) -> ˇu32 {
17742        }
17743    "});
17744}
17745
17746#[gpui::test]
17747async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17748    init_test(cx, |_| {});
17749
17750    let mut cx = EditorTestContext::new(cx).await;
17751
17752    let diff_base = r#"
17753        use some::mod;
17754
17755        const A: u32 = 42;
17756
17757        fn main() {
17758            println!("hello");
17759
17760            println!("world");
17761        }
17762        "#
17763    .unindent();
17764
17765    // Edits are modified, removed, modified, added
17766    cx.set_state(
17767        &r#"
17768        use some::modified;
17769
17770        ˇ
17771        fn main() {
17772            println!("hello there");
17773
17774            println!("around the");
17775            println!("world");
17776        }
17777        "#
17778        .unindent(),
17779    );
17780
17781    cx.set_head_text(&diff_base);
17782    executor.run_until_parked();
17783
17784    cx.update_editor(|editor, window, cx| {
17785        //Wrap around the bottom of the buffer
17786        for _ in 0..3 {
17787            editor.go_to_next_hunk(&GoToHunk, window, cx);
17788        }
17789    });
17790
17791    cx.assert_editor_state(
17792        &r#"
17793        ˇuse some::modified;
17794
17795
17796        fn main() {
17797            println!("hello there");
17798
17799            println!("around the");
17800            println!("world");
17801        }
17802        "#
17803        .unindent(),
17804    );
17805
17806    cx.update_editor(|editor, window, cx| {
17807        //Wrap around the top of the buffer
17808        for _ in 0..2 {
17809            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17810        }
17811    });
17812
17813    cx.assert_editor_state(
17814        &r#"
17815        use some::modified;
17816
17817
17818        fn main() {
17819        ˇ    println!("hello there");
17820
17821            println!("around the");
17822            println!("world");
17823        }
17824        "#
17825        .unindent(),
17826    );
17827
17828    cx.update_editor(|editor, window, cx| {
17829        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17830    });
17831
17832    cx.assert_editor_state(
17833        &r#"
17834        use some::modified;
17835
17836        ˇ
17837        fn main() {
17838            println!("hello there");
17839
17840            println!("around the");
17841            println!("world");
17842        }
17843        "#
17844        .unindent(),
17845    );
17846
17847    cx.update_editor(|editor, window, cx| {
17848        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17849    });
17850
17851    cx.assert_editor_state(
17852        &r#"
17853        ˇuse some::modified;
17854
17855
17856        fn main() {
17857            println!("hello there");
17858
17859            println!("around the");
17860            println!("world");
17861        }
17862        "#
17863        .unindent(),
17864    );
17865
17866    cx.update_editor(|editor, window, cx| {
17867        for _ in 0..2 {
17868            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17869        }
17870    });
17871
17872    cx.assert_editor_state(
17873        &r#"
17874        use some::modified;
17875
17876
17877        fn main() {
17878        ˇ    println!("hello there");
17879
17880            println!("around the");
17881            println!("world");
17882        }
17883        "#
17884        .unindent(),
17885    );
17886
17887    cx.update_editor(|editor, window, cx| {
17888        editor.fold(&Fold, window, cx);
17889    });
17890
17891    cx.update_editor(|editor, window, cx| {
17892        editor.go_to_next_hunk(&GoToHunk, window, cx);
17893    });
17894
17895    cx.assert_editor_state(
17896        &r#"
17897        ˇuse some::modified;
17898
17899
17900        fn main() {
17901            println!("hello there");
17902
17903            println!("around the");
17904            println!("world");
17905        }
17906        "#
17907        .unindent(),
17908    );
17909}
17910
17911#[test]
17912fn test_split_words() {
17913    fn split(text: &str) -> Vec<&str> {
17914        split_words(text).collect()
17915    }
17916
17917    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17918    assert_eq!(split("hello_world"), &["hello_", "world"]);
17919    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17920    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17921    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17922    assert_eq!(split("helloworld"), &["helloworld"]);
17923
17924    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17925}
17926
17927#[test]
17928fn test_split_words_for_snippet_prefix() {
17929    fn split(text: &str) -> Vec<&str> {
17930        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17931    }
17932
17933    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17934    assert_eq!(split("hello_world"), &["hello_world"]);
17935    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17936    assert_eq!(split("Hello_World"), &["Hello_World"]);
17937    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17938    assert_eq!(split("helloworld"), &["helloworld"]);
17939    assert_eq!(
17940        split("this@is!@#$^many   . symbols"),
17941        &[
17942            "symbols",
17943            " symbols",
17944            ". symbols",
17945            " . symbols",
17946            "  . symbols",
17947            "   . symbols",
17948            "many   . symbols",
17949            "^many   . symbols",
17950            "$^many   . symbols",
17951            "#$^many   . symbols",
17952            "@#$^many   . symbols",
17953            "!@#$^many   . symbols",
17954            "is!@#$^many   . symbols",
17955            "@is!@#$^many   . symbols",
17956            "this@is!@#$^many   . symbols",
17957        ],
17958    );
17959    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17960}
17961
17962#[gpui::test]
17963async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17964    init_test(cx, |_| {});
17965
17966    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17967
17968    #[track_caller]
17969    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17970        let _state_context = cx.set_state(before);
17971        cx.run_until_parked();
17972        cx.update_editor(|editor, window, cx| {
17973            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17974        });
17975        cx.run_until_parked();
17976        cx.assert_editor_state(after);
17977    }
17978
17979    // Outside bracket jumps to outside of matching bracket
17980    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17981    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17982
17983    // Inside bracket jumps to inside of matching bracket
17984    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17985    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17986
17987    // When outside a bracket and inside, favor jumping to the inside bracket
17988    assert(
17989        "console.log('foo', [1, 2, 3]ˇ);",
17990        "console.log('foo', ˇ[1, 2, 3]);",
17991        &mut cx,
17992    );
17993    assert(
17994        "console.log(ˇ'foo', [1, 2, 3]);",
17995        "console.log('foo'ˇ, [1, 2, 3]);",
17996        &mut cx,
17997    );
17998
17999    // Bias forward if two options are equally likely
18000    assert(
18001        "let result = curried_fun()ˇ();",
18002        "let result = curried_fun()()ˇ;",
18003        &mut cx,
18004    );
18005
18006    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18007    assert(
18008        indoc! {"
18009            function test() {
18010                console.log('test')ˇ
18011            }"},
18012        indoc! {"
18013            function test() {
18014                console.logˇ('test')
18015            }"},
18016        &mut cx,
18017    );
18018}
18019
18020#[gpui::test]
18021async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18022    init_test(cx, |_| {});
18023    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18024    language_registry.add(markdown_lang());
18025    language_registry.add(rust_lang());
18026    let buffer = cx.new(|cx| {
18027        let mut buffer = language::Buffer::local(
18028            indoc! {"
18029            ```rs
18030            impl Worktree {
18031                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18032                }
18033            }
18034            ```
18035        "},
18036            cx,
18037        );
18038        buffer.set_language_registry(language_registry.clone());
18039        buffer.set_language(Some(markdown_lang()), cx);
18040        buffer
18041    });
18042    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18043    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18044    cx.executor().run_until_parked();
18045    _ = editor.update(cx, |editor, window, cx| {
18046        // Case 1: Test outer enclosing brackets
18047        select_ranges(
18048            editor,
18049            &indoc! {"
18050                ```rs
18051                impl Worktree {
18052                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18053                    }
1805418055                ```
18056            "},
18057            window,
18058            cx,
18059        );
18060        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18061        assert_text_with_selections(
18062            editor,
18063            &indoc! {"
18064                ```rs
18065                impl Worktree ˇ{
18066                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18067                    }
18068                }
18069                ```
18070            "},
18071            cx,
18072        );
18073        // Case 2: Test inner enclosing brackets
18074        select_ranges(
18075            editor,
18076            &indoc! {"
18077                ```rs
18078                impl Worktree {
18079                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1808018081                }
18082                ```
18083            "},
18084            window,
18085            cx,
18086        );
18087        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18088        assert_text_with_selections(
18089            editor,
18090            &indoc! {"
18091                ```rs
18092                impl Worktree {
18093                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18094                    }
18095                }
18096                ```
18097            "},
18098            cx,
18099        );
18100    });
18101}
18102
18103#[gpui::test]
18104async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18105    init_test(cx, |_| {});
18106
18107    let fs = FakeFs::new(cx.executor());
18108    fs.insert_tree(
18109        path!("/a"),
18110        json!({
18111            "main.rs": "fn main() { let a = 5; }",
18112            "other.rs": "// Test file",
18113        }),
18114    )
18115    .await;
18116    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18117
18118    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18119    language_registry.add(Arc::new(Language::new(
18120        LanguageConfig {
18121            name: "Rust".into(),
18122            matcher: LanguageMatcher {
18123                path_suffixes: vec!["rs".to_string()],
18124                ..Default::default()
18125            },
18126            brackets: BracketPairConfig {
18127                pairs: vec![BracketPair {
18128                    start: "{".to_string(),
18129                    end: "}".to_string(),
18130                    close: true,
18131                    surround: true,
18132                    newline: true,
18133                }],
18134                disabled_scopes_by_bracket_ix: Vec::new(),
18135            },
18136            ..Default::default()
18137        },
18138        Some(tree_sitter_rust::LANGUAGE.into()),
18139    )));
18140    let mut fake_servers = language_registry.register_fake_lsp(
18141        "Rust",
18142        FakeLspAdapter {
18143            capabilities: lsp::ServerCapabilities {
18144                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18145                    first_trigger_character: "{".to_string(),
18146                    more_trigger_character: None,
18147                }),
18148                ..Default::default()
18149            },
18150            ..Default::default()
18151        },
18152    );
18153
18154    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18155
18156    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18157
18158    let worktree_id = workspace
18159        .update(cx, |workspace, _, cx| {
18160            workspace.project().update(cx, |project, cx| {
18161                project.worktrees(cx).next().unwrap().read(cx).id()
18162            })
18163        })
18164        .unwrap();
18165
18166    let buffer = project
18167        .update(cx, |project, cx| {
18168            project.open_local_buffer(path!("/a/main.rs"), cx)
18169        })
18170        .await
18171        .unwrap();
18172    let editor_handle = workspace
18173        .update(cx, |workspace, window, cx| {
18174            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18175        })
18176        .unwrap()
18177        .await
18178        .unwrap()
18179        .downcast::<Editor>()
18180        .unwrap();
18181
18182    cx.executor().start_waiting();
18183    let fake_server = fake_servers.next().await.unwrap();
18184
18185    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18186        |params, _| async move {
18187            assert_eq!(
18188                params.text_document_position.text_document.uri,
18189                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18190            );
18191            assert_eq!(
18192                params.text_document_position.position,
18193                lsp::Position::new(0, 21),
18194            );
18195
18196            Ok(Some(vec![lsp::TextEdit {
18197                new_text: "]".to_string(),
18198                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18199            }]))
18200        },
18201    );
18202
18203    editor_handle.update_in(cx, |editor, window, cx| {
18204        window.focus(&editor.focus_handle(cx));
18205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18206            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18207        });
18208        editor.handle_input("{", window, cx);
18209    });
18210
18211    cx.executor().run_until_parked();
18212
18213    buffer.update(cx, |buffer, _| {
18214        assert_eq!(
18215            buffer.text(),
18216            "fn main() { let a = {5}; }",
18217            "No extra braces from on type formatting should appear in the buffer"
18218        )
18219    });
18220}
18221
18222#[gpui::test(iterations = 20, seeds(31))]
18223async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18224    init_test(cx, |_| {});
18225
18226    let mut cx = EditorLspTestContext::new_rust(
18227        lsp::ServerCapabilities {
18228            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18229                first_trigger_character: ".".to_string(),
18230                more_trigger_character: None,
18231            }),
18232            ..Default::default()
18233        },
18234        cx,
18235    )
18236    .await;
18237
18238    cx.update_buffer(|buffer, _| {
18239        // This causes autoindent to be async.
18240        buffer.set_sync_parse_timeout(Duration::ZERO)
18241    });
18242
18243    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18244    cx.simulate_keystroke("\n");
18245    cx.run_until_parked();
18246
18247    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18248    let mut request =
18249        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18250            let buffer_cloned = buffer_cloned.clone();
18251            async move {
18252                buffer_cloned.update(&mut cx, |buffer, _| {
18253                    assert_eq!(
18254                        buffer.text(),
18255                        "fn c() {\n    d()\n        .\n}\n",
18256                        "OnTypeFormatting should triggered after autoindent applied"
18257                    )
18258                })?;
18259
18260                Ok(Some(vec![]))
18261            }
18262        });
18263
18264    cx.simulate_keystroke(".");
18265    cx.run_until_parked();
18266
18267    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18268    assert!(request.next().await.is_some());
18269    request.close();
18270    assert!(request.next().await.is_none());
18271}
18272
18273#[gpui::test]
18274async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18275    init_test(cx, |_| {});
18276
18277    let fs = FakeFs::new(cx.executor());
18278    fs.insert_tree(
18279        path!("/a"),
18280        json!({
18281            "main.rs": "fn main() { let a = 5; }",
18282            "other.rs": "// Test file",
18283        }),
18284    )
18285    .await;
18286
18287    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18288
18289    let server_restarts = Arc::new(AtomicUsize::new(0));
18290    let closure_restarts = Arc::clone(&server_restarts);
18291    let language_server_name = "test language server";
18292    let language_name: LanguageName = "Rust".into();
18293
18294    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18295    language_registry.add(Arc::new(Language::new(
18296        LanguageConfig {
18297            name: language_name.clone(),
18298            matcher: LanguageMatcher {
18299                path_suffixes: vec!["rs".to_string()],
18300                ..Default::default()
18301            },
18302            ..Default::default()
18303        },
18304        Some(tree_sitter_rust::LANGUAGE.into()),
18305    )));
18306    let mut fake_servers = language_registry.register_fake_lsp(
18307        "Rust",
18308        FakeLspAdapter {
18309            name: language_server_name,
18310            initialization_options: Some(json!({
18311                "testOptionValue": true
18312            })),
18313            initializer: Some(Box::new(move |fake_server| {
18314                let task_restarts = Arc::clone(&closure_restarts);
18315                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18316                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18317                    futures::future::ready(Ok(()))
18318                });
18319            })),
18320            ..Default::default()
18321        },
18322    );
18323
18324    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18325    let _buffer = project
18326        .update(cx, |project, cx| {
18327            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18328        })
18329        .await
18330        .unwrap();
18331    let _fake_server = fake_servers.next().await.unwrap();
18332    update_test_language_settings(cx, |language_settings| {
18333        language_settings.languages.0.insert(
18334            language_name.clone().0,
18335            LanguageSettingsContent {
18336                tab_size: NonZeroU32::new(8),
18337                ..Default::default()
18338            },
18339        );
18340    });
18341    cx.executor().run_until_parked();
18342    assert_eq!(
18343        server_restarts.load(atomic::Ordering::Acquire),
18344        0,
18345        "Should not restart LSP server on an unrelated change"
18346    );
18347
18348    update_test_project_settings(cx, |project_settings| {
18349        project_settings.lsp.insert(
18350            "Some other server name".into(),
18351            LspSettings {
18352                binary: None,
18353                settings: None,
18354                initialization_options: Some(json!({
18355                    "some other init value": false
18356                })),
18357                enable_lsp_tasks: false,
18358                fetch: None,
18359            },
18360        );
18361    });
18362    cx.executor().run_until_parked();
18363    assert_eq!(
18364        server_restarts.load(atomic::Ordering::Acquire),
18365        0,
18366        "Should not restart LSP server on an unrelated LSP settings change"
18367    );
18368
18369    update_test_project_settings(cx, |project_settings| {
18370        project_settings.lsp.insert(
18371            language_server_name.into(),
18372            LspSettings {
18373                binary: None,
18374                settings: None,
18375                initialization_options: Some(json!({
18376                    "anotherInitValue": false
18377                })),
18378                enable_lsp_tasks: false,
18379                fetch: None,
18380            },
18381        );
18382    });
18383    cx.executor().run_until_parked();
18384    assert_eq!(
18385        server_restarts.load(atomic::Ordering::Acquire),
18386        1,
18387        "Should restart LSP server on a related LSP settings change"
18388    );
18389
18390    update_test_project_settings(cx, |project_settings| {
18391        project_settings.lsp.insert(
18392            language_server_name.into(),
18393            LspSettings {
18394                binary: None,
18395                settings: None,
18396                initialization_options: Some(json!({
18397                    "anotherInitValue": false
18398                })),
18399                enable_lsp_tasks: false,
18400                fetch: None,
18401            },
18402        );
18403    });
18404    cx.executor().run_until_parked();
18405    assert_eq!(
18406        server_restarts.load(atomic::Ordering::Acquire),
18407        1,
18408        "Should not restart LSP server on a related LSP settings change that is the same"
18409    );
18410
18411    update_test_project_settings(cx, |project_settings| {
18412        project_settings.lsp.insert(
18413            language_server_name.into(),
18414            LspSettings {
18415                binary: None,
18416                settings: None,
18417                initialization_options: None,
18418                enable_lsp_tasks: false,
18419                fetch: None,
18420            },
18421        );
18422    });
18423    cx.executor().run_until_parked();
18424    assert_eq!(
18425        server_restarts.load(atomic::Ordering::Acquire),
18426        2,
18427        "Should restart LSP server on another related LSP settings change"
18428    );
18429}
18430
18431#[gpui::test]
18432async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18433    init_test(cx, |_| {});
18434
18435    let mut cx = EditorLspTestContext::new_rust(
18436        lsp::ServerCapabilities {
18437            completion_provider: Some(lsp::CompletionOptions {
18438                trigger_characters: Some(vec![".".to_string()]),
18439                resolve_provider: Some(true),
18440                ..Default::default()
18441            }),
18442            ..Default::default()
18443        },
18444        cx,
18445    )
18446    .await;
18447
18448    cx.set_state("fn main() { let a = 2ˇ; }");
18449    cx.simulate_keystroke(".");
18450    let completion_item = lsp::CompletionItem {
18451        label: "some".into(),
18452        kind: Some(lsp::CompletionItemKind::SNIPPET),
18453        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18454        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18455            kind: lsp::MarkupKind::Markdown,
18456            value: "```rust\nSome(2)\n```".to_string(),
18457        })),
18458        deprecated: Some(false),
18459        sort_text: Some("fffffff2".to_string()),
18460        filter_text: Some("some".to_string()),
18461        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18462        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18463            range: lsp::Range {
18464                start: lsp::Position {
18465                    line: 0,
18466                    character: 22,
18467                },
18468                end: lsp::Position {
18469                    line: 0,
18470                    character: 22,
18471                },
18472            },
18473            new_text: "Some(2)".to_string(),
18474        })),
18475        additional_text_edits: Some(vec![lsp::TextEdit {
18476            range: lsp::Range {
18477                start: lsp::Position {
18478                    line: 0,
18479                    character: 20,
18480                },
18481                end: lsp::Position {
18482                    line: 0,
18483                    character: 22,
18484                },
18485            },
18486            new_text: "".to_string(),
18487        }]),
18488        ..Default::default()
18489    };
18490
18491    let closure_completion_item = completion_item.clone();
18492    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18493        let task_completion_item = closure_completion_item.clone();
18494        async move {
18495            Ok(Some(lsp::CompletionResponse::Array(vec![
18496                task_completion_item,
18497            ])))
18498        }
18499    });
18500
18501    request.next().await;
18502
18503    cx.condition(|editor, _| editor.context_menu_visible())
18504        .await;
18505    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18506        editor
18507            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18508            .unwrap()
18509    });
18510    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18511
18512    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18513        let task_completion_item = completion_item.clone();
18514        async move { Ok(task_completion_item) }
18515    })
18516    .next()
18517    .await
18518    .unwrap();
18519    apply_additional_edits.await.unwrap();
18520    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18521}
18522
18523#[gpui::test]
18524async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18525    init_test(cx, |_| {});
18526
18527    let mut cx = EditorLspTestContext::new_rust(
18528        lsp::ServerCapabilities {
18529            completion_provider: Some(lsp::CompletionOptions {
18530                trigger_characters: Some(vec![".".to_string()]),
18531                resolve_provider: Some(true),
18532                ..Default::default()
18533            }),
18534            ..Default::default()
18535        },
18536        cx,
18537    )
18538    .await;
18539
18540    cx.set_state("fn main() { let a = 2ˇ; }");
18541    cx.simulate_keystroke(".");
18542
18543    let item1 = lsp::CompletionItem {
18544        label: "method id()".to_string(),
18545        filter_text: Some("id".to_string()),
18546        detail: None,
18547        documentation: None,
18548        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18549            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18550            new_text: ".id".to_string(),
18551        })),
18552        ..lsp::CompletionItem::default()
18553    };
18554
18555    let item2 = lsp::CompletionItem {
18556        label: "other".to_string(),
18557        filter_text: Some("other".to_string()),
18558        detail: None,
18559        documentation: None,
18560        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18561            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18562            new_text: ".other".to_string(),
18563        })),
18564        ..lsp::CompletionItem::default()
18565    };
18566
18567    let item1 = item1.clone();
18568    cx.set_request_handler::<lsp::request::Completion, _, _>({
18569        let item1 = item1.clone();
18570        move |_, _, _| {
18571            let item1 = item1.clone();
18572            let item2 = item2.clone();
18573            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18574        }
18575    })
18576    .next()
18577    .await;
18578
18579    cx.condition(|editor, _| editor.context_menu_visible())
18580        .await;
18581    cx.update_editor(|editor, _, _| {
18582        let context_menu = editor.context_menu.borrow_mut();
18583        let context_menu = context_menu
18584            .as_ref()
18585            .expect("Should have the context menu deployed");
18586        match context_menu {
18587            CodeContextMenu::Completions(completions_menu) => {
18588                let completions = completions_menu.completions.borrow_mut();
18589                assert_eq!(
18590                    completions
18591                        .iter()
18592                        .map(|completion| &completion.label.text)
18593                        .collect::<Vec<_>>(),
18594                    vec!["method id()", "other"]
18595                )
18596            }
18597            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18598        }
18599    });
18600
18601    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18602        let item1 = item1.clone();
18603        move |_, item_to_resolve, _| {
18604            let item1 = item1.clone();
18605            async move {
18606                if item1 == item_to_resolve {
18607                    Ok(lsp::CompletionItem {
18608                        label: "method id()".to_string(),
18609                        filter_text: Some("id".to_string()),
18610                        detail: Some("Now resolved!".to_string()),
18611                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18612                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18613                            range: lsp::Range::new(
18614                                lsp::Position::new(0, 22),
18615                                lsp::Position::new(0, 22),
18616                            ),
18617                            new_text: ".id".to_string(),
18618                        })),
18619                        ..lsp::CompletionItem::default()
18620                    })
18621                } else {
18622                    Ok(item_to_resolve)
18623                }
18624            }
18625        }
18626    })
18627    .next()
18628    .await
18629    .unwrap();
18630    cx.run_until_parked();
18631
18632    cx.update_editor(|editor, window, cx| {
18633        editor.context_menu_next(&Default::default(), window, cx);
18634    });
18635
18636    cx.update_editor(|editor, _, _| {
18637        let context_menu = editor.context_menu.borrow_mut();
18638        let context_menu = context_menu
18639            .as_ref()
18640            .expect("Should have the context menu deployed");
18641        match context_menu {
18642            CodeContextMenu::Completions(completions_menu) => {
18643                let completions = completions_menu.completions.borrow_mut();
18644                assert_eq!(
18645                    completions
18646                        .iter()
18647                        .map(|completion| &completion.label.text)
18648                        .collect::<Vec<_>>(),
18649                    vec!["method id() Now resolved!", "other"],
18650                    "Should update first completion label, but not second as the filter text did not match."
18651                );
18652            }
18653            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18654        }
18655    });
18656}
18657
18658#[gpui::test]
18659async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18660    init_test(cx, |_| {});
18661    let mut cx = EditorLspTestContext::new_rust(
18662        lsp::ServerCapabilities {
18663            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18664            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18665            completion_provider: Some(lsp::CompletionOptions {
18666                resolve_provider: Some(true),
18667                ..Default::default()
18668            }),
18669            ..Default::default()
18670        },
18671        cx,
18672    )
18673    .await;
18674    cx.set_state(indoc! {"
18675        struct TestStruct {
18676            field: i32
18677        }
18678
18679        fn mainˇ() {
18680            let unused_var = 42;
18681            let test_struct = TestStruct { field: 42 };
18682        }
18683    "});
18684    let symbol_range = cx.lsp_range(indoc! {"
18685        struct TestStruct {
18686            field: i32
18687        }
18688
18689        «fn main»() {
18690            let unused_var = 42;
18691            let test_struct = TestStruct { field: 42 };
18692        }
18693    "});
18694    let mut hover_requests =
18695        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18696            Ok(Some(lsp::Hover {
18697                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18698                    kind: lsp::MarkupKind::Markdown,
18699                    value: "Function documentation".to_string(),
18700                }),
18701                range: Some(symbol_range),
18702            }))
18703        });
18704
18705    // Case 1: Test that code action menu hide hover popover
18706    cx.dispatch_action(Hover);
18707    hover_requests.next().await;
18708    cx.condition(|editor, _| editor.hover_state.visible()).await;
18709    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18710        move |_, _, _| async move {
18711            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18712                lsp::CodeAction {
18713                    title: "Remove unused variable".to_string(),
18714                    kind: Some(CodeActionKind::QUICKFIX),
18715                    edit: Some(lsp::WorkspaceEdit {
18716                        changes: Some(
18717                            [(
18718                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18719                                vec![lsp::TextEdit {
18720                                    range: lsp::Range::new(
18721                                        lsp::Position::new(5, 4),
18722                                        lsp::Position::new(5, 27),
18723                                    ),
18724                                    new_text: "".to_string(),
18725                                }],
18726                            )]
18727                            .into_iter()
18728                            .collect(),
18729                        ),
18730                        ..Default::default()
18731                    }),
18732                    ..Default::default()
18733                },
18734            )]))
18735        },
18736    );
18737    cx.update_editor(|editor, window, cx| {
18738        editor.toggle_code_actions(
18739            &ToggleCodeActions {
18740                deployed_from: None,
18741                quick_launch: false,
18742            },
18743            window,
18744            cx,
18745        );
18746    });
18747    code_action_requests.next().await;
18748    cx.run_until_parked();
18749    cx.condition(|editor, _| editor.context_menu_visible())
18750        .await;
18751    cx.update_editor(|editor, _, _| {
18752        assert!(
18753            !editor.hover_state.visible(),
18754            "Hover popover should be hidden when code action menu is shown"
18755        );
18756        // Hide code actions
18757        editor.context_menu.take();
18758    });
18759
18760    // Case 2: Test that code completions hide hover popover
18761    cx.dispatch_action(Hover);
18762    hover_requests.next().await;
18763    cx.condition(|editor, _| editor.hover_state.visible()).await;
18764    let counter = Arc::new(AtomicUsize::new(0));
18765    let mut completion_requests =
18766        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18767            let counter = counter.clone();
18768            async move {
18769                counter.fetch_add(1, atomic::Ordering::Release);
18770                Ok(Some(lsp::CompletionResponse::Array(vec![
18771                    lsp::CompletionItem {
18772                        label: "main".into(),
18773                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18774                        detail: Some("() -> ()".to_string()),
18775                        ..Default::default()
18776                    },
18777                    lsp::CompletionItem {
18778                        label: "TestStruct".into(),
18779                        kind: Some(lsp::CompletionItemKind::STRUCT),
18780                        detail: Some("struct TestStruct".to_string()),
18781                        ..Default::default()
18782                    },
18783                ])))
18784            }
18785        });
18786    cx.update_editor(|editor, window, cx| {
18787        editor.show_completions(&ShowCompletions, window, cx);
18788    });
18789    completion_requests.next().await;
18790    cx.condition(|editor, _| editor.context_menu_visible())
18791        .await;
18792    cx.update_editor(|editor, _, _| {
18793        assert!(
18794            !editor.hover_state.visible(),
18795            "Hover popover should be hidden when completion menu is shown"
18796        );
18797    });
18798}
18799
18800#[gpui::test]
18801async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18802    init_test(cx, |_| {});
18803
18804    let mut cx = EditorLspTestContext::new_rust(
18805        lsp::ServerCapabilities {
18806            completion_provider: Some(lsp::CompletionOptions {
18807                trigger_characters: Some(vec![".".to_string()]),
18808                resolve_provider: Some(true),
18809                ..Default::default()
18810            }),
18811            ..Default::default()
18812        },
18813        cx,
18814    )
18815    .await;
18816
18817    cx.set_state("fn main() { let a = 2ˇ; }");
18818    cx.simulate_keystroke(".");
18819
18820    let unresolved_item_1 = lsp::CompletionItem {
18821        label: "id".to_string(),
18822        filter_text: Some("id".to_string()),
18823        detail: None,
18824        documentation: None,
18825        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18826            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18827            new_text: ".id".to_string(),
18828        })),
18829        ..lsp::CompletionItem::default()
18830    };
18831    let resolved_item_1 = lsp::CompletionItem {
18832        additional_text_edits: Some(vec![lsp::TextEdit {
18833            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18834            new_text: "!!".to_string(),
18835        }]),
18836        ..unresolved_item_1.clone()
18837    };
18838    let unresolved_item_2 = lsp::CompletionItem {
18839        label: "other".to_string(),
18840        filter_text: Some("other".to_string()),
18841        detail: None,
18842        documentation: None,
18843        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18844            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18845            new_text: ".other".to_string(),
18846        })),
18847        ..lsp::CompletionItem::default()
18848    };
18849    let resolved_item_2 = lsp::CompletionItem {
18850        additional_text_edits: Some(vec![lsp::TextEdit {
18851            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18852            new_text: "??".to_string(),
18853        }]),
18854        ..unresolved_item_2.clone()
18855    };
18856
18857    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18858    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18859    cx.lsp
18860        .server
18861        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18862            let unresolved_item_1 = unresolved_item_1.clone();
18863            let resolved_item_1 = resolved_item_1.clone();
18864            let unresolved_item_2 = unresolved_item_2.clone();
18865            let resolved_item_2 = resolved_item_2.clone();
18866            let resolve_requests_1 = resolve_requests_1.clone();
18867            let resolve_requests_2 = resolve_requests_2.clone();
18868            move |unresolved_request, _| {
18869                let unresolved_item_1 = unresolved_item_1.clone();
18870                let resolved_item_1 = resolved_item_1.clone();
18871                let unresolved_item_2 = unresolved_item_2.clone();
18872                let resolved_item_2 = resolved_item_2.clone();
18873                let resolve_requests_1 = resolve_requests_1.clone();
18874                let resolve_requests_2 = resolve_requests_2.clone();
18875                async move {
18876                    if unresolved_request == unresolved_item_1 {
18877                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18878                        Ok(resolved_item_1.clone())
18879                    } else if unresolved_request == unresolved_item_2 {
18880                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18881                        Ok(resolved_item_2.clone())
18882                    } else {
18883                        panic!("Unexpected completion item {unresolved_request:?}")
18884                    }
18885                }
18886            }
18887        })
18888        .detach();
18889
18890    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18891        let unresolved_item_1 = unresolved_item_1.clone();
18892        let unresolved_item_2 = unresolved_item_2.clone();
18893        async move {
18894            Ok(Some(lsp::CompletionResponse::Array(vec![
18895                unresolved_item_1,
18896                unresolved_item_2,
18897            ])))
18898        }
18899    })
18900    .next()
18901    .await;
18902
18903    cx.condition(|editor, _| editor.context_menu_visible())
18904        .await;
18905    cx.update_editor(|editor, _, _| {
18906        let context_menu = editor.context_menu.borrow_mut();
18907        let context_menu = context_menu
18908            .as_ref()
18909            .expect("Should have the context menu deployed");
18910        match context_menu {
18911            CodeContextMenu::Completions(completions_menu) => {
18912                let completions = completions_menu.completions.borrow_mut();
18913                assert_eq!(
18914                    completions
18915                        .iter()
18916                        .map(|completion| &completion.label.text)
18917                        .collect::<Vec<_>>(),
18918                    vec!["id", "other"]
18919                )
18920            }
18921            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18922        }
18923    });
18924    cx.run_until_parked();
18925
18926    cx.update_editor(|editor, window, cx| {
18927        editor.context_menu_next(&ContextMenuNext, window, cx);
18928    });
18929    cx.run_until_parked();
18930    cx.update_editor(|editor, window, cx| {
18931        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18932    });
18933    cx.run_until_parked();
18934    cx.update_editor(|editor, window, cx| {
18935        editor.context_menu_next(&ContextMenuNext, window, cx);
18936    });
18937    cx.run_until_parked();
18938    cx.update_editor(|editor, window, cx| {
18939        editor
18940            .compose_completion(&ComposeCompletion::default(), window, cx)
18941            .expect("No task returned")
18942    })
18943    .await
18944    .expect("Completion failed");
18945    cx.run_until_parked();
18946
18947    cx.update_editor(|editor, _, cx| {
18948        assert_eq!(
18949            resolve_requests_1.load(atomic::Ordering::Acquire),
18950            1,
18951            "Should always resolve once despite multiple selections"
18952        );
18953        assert_eq!(
18954            resolve_requests_2.load(atomic::Ordering::Acquire),
18955            1,
18956            "Should always resolve once after multiple selections and applying the completion"
18957        );
18958        assert_eq!(
18959            editor.text(cx),
18960            "fn main() { let a = ??.other; }",
18961            "Should use resolved data when applying the completion"
18962        );
18963    });
18964}
18965
18966#[gpui::test]
18967async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18968    init_test(cx, |_| {});
18969
18970    let item_0 = lsp::CompletionItem {
18971        label: "abs".into(),
18972        insert_text: Some("abs".into()),
18973        data: Some(json!({ "very": "special"})),
18974        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18975        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18976            lsp::InsertReplaceEdit {
18977                new_text: "abs".to_string(),
18978                insert: lsp::Range::default(),
18979                replace: lsp::Range::default(),
18980            },
18981        )),
18982        ..lsp::CompletionItem::default()
18983    };
18984    let items = iter::once(item_0.clone())
18985        .chain((11..51).map(|i| lsp::CompletionItem {
18986            label: format!("item_{}", i),
18987            insert_text: Some(format!("item_{}", i)),
18988            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18989            ..lsp::CompletionItem::default()
18990        }))
18991        .collect::<Vec<_>>();
18992
18993    let default_commit_characters = vec!["?".to_string()];
18994    let default_data = json!({ "default": "data"});
18995    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18996    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18997    let default_edit_range = lsp::Range {
18998        start: lsp::Position {
18999            line: 0,
19000            character: 5,
19001        },
19002        end: lsp::Position {
19003            line: 0,
19004            character: 5,
19005        },
19006    };
19007
19008    let mut cx = EditorLspTestContext::new_rust(
19009        lsp::ServerCapabilities {
19010            completion_provider: Some(lsp::CompletionOptions {
19011                trigger_characters: Some(vec![".".to_string()]),
19012                resolve_provider: Some(true),
19013                ..Default::default()
19014            }),
19015            ..Default::default()
19016        },
19017        cx,
19018    )
19019    .await;
19020
19021    cx.set_state("fn main() { let a = 2ˇ; }");
19022    cx.simulate_keystroke(".");
19023
19024    let completion_data = default_data.clone();
19025    let completion_characters = default_commit_characters.clone();
19026    let completion_items = items.clone();
19027    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19028        let default_data = completion_data.clone();
19029        let default_commit_characters = completion_characters.clone();
19030        let items = completion_items.clone();
19031        async move {
19032            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19033                items,
19034                item_defaults: Some(lsp::CompletionListItemDefaults {
19035                    data: Some(default_data.clone()),
19036                    commit_characters: Some(default_commit_characters.clone()),
19037                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19038                        default_edit_range,
19039                    )),
19040                    insert_text_format: Some(default_insert_text_format),
19041                    insert_text_mode: Some(default_insert_text_mode),
19042                }),
19043                ..lsp::CompletionList::default()
19044            })))
19045        }
19046    })
19047    .next()
19048    .await;
19049
19050    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19051    cx.lsp
19052        .server
19053        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19054            let closure_resolved_items = resolved_items.clone();
19055            move |item_to_resolve, _| {
19056                let closure_resolved_items = closure_resolved_items.clone();
19057                async move {
19058                    closure_resolved_items.lock().push(item_to_resolve.clone());
19059                    Ok(item_to_resolve)
19060                }
19061            }
19062        })
19063        .detach();
19064
19065    cx.condition(|editor, _| editor.context_menu_visible())
19066        .await;
19067    cx.run_until_parked();
19068    cx.update_editor(|editor, _, _| {
19069        let menu = editor.context_menu.borrow_mut();
19070        match menu.as_ref().expect("should have the completions menu") {
19071            CodeContextMenu::Completions(completions_menu) => {
19072                assert_eq!(
19073                    completions_menu
19074                        .entries
19075                        .borrow()
19076                        .iter()
19077                        .map(|mat| mat.string.clone())
19078                        .collect::<Vec<String>>(),
19079                    items
19080                        .iter()
19081                        .map(|completion| completion.label.clone())
19082                        .collect::<Vec<String>>()
19083                );
19084            }
19085            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19086        }
19087    });
19088    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19089    // with 4 from the end.
19090    assert_eq!(
19091        *resolved_items.lock(),
19092        [&items[0..16], &items[items.len() - 4..items.len()]]
19093            .concat()
19094            .iter()
19095            .cloned()
19096            .map(|mut item| {
19097                if item.data.is_none() {
19098                    item.data = Some(default_data.clone());
19099                }
19100                item
19101            })
19102            .collect::<Vec<lsp::CompletionItem>>(),
19103        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19104    );
19105    resolved_items.lock().clear();
19106
19107    cx.update_editor(|editor, window, cx| {
19108        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19109    });
19110    cx.run_until_parked();
19111    // Completions that have already been resolved are skipped.
19112    assert_eq!(
19113        *resolved_items.lock(),
19114        items[items.len() - 17..items.len() - 4]
19115            .iter()
19116            .cloned()
19117            .map(|mut item| {
19118                if item.data.is_none() {
19119                    item.data = Some(default_data.clone());
19120                }
19121                item
19122            })
19123            .collect::<Vec<lsp::CompletionItem>>()
19124    );
19125    resolved_items.lock().clear();
19126}
19127
19128#[gpui::test]
19129async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19130    init_test(cx, |_| {});
19131
19132    let mut cx = EditorLspTestContext::new(
19133        Language::new(
19134            LanguageConfig {
19135                matcher: LanguageMatcher {
19136                    path_suffixes: vec!["jsx".into()],
19137                    ..Default::default()
19138                },
19139                overrides: [(
19140                    "element".into(),
19141                    LanguageConfigOverride {
19142                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19143                        ..Default::default()
19144                    },
19145                )]
19146                .into_iter()
19147                .collect(),
19148                ..Default::default()
19149            },
19150            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19151        )
19152        .with_override_query("(jsx_self_closing_element) @element")
19153        .unwrap(),
19154        lsp::ServerCapabilities {
19155            completion_provider: Some(lsp::CompletionOptions {
19156                trigger_characters: Some(vec![":".to_string()]),
19157                ..Default::default()
19158            }),
19159            ..Default::default()
19160        },
19161        cx,
19162    )
19163    .await;
19164
19165    cx.lsp
19166        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19167            Ok(Some(lsp::CompletionResponse::Array(vec![
19168                lsp::CompletionItem {
19169                    label: "bg-blue".into(),
19170                    ..Default::default()
19171                },
19172                lsp::CompletionItem {
19173                    label: "bg-red".into(),
19174                    ..Default::default()
19175                },
19176                lsp::CompletionItem {
19177                    label: "bg-yellow".into(),
19178                    ..Default::default()
19179                },
19180            ])))
19181        });
19182
19183    cx.set_state(r#"<p class="bgˇ" />"#);
19184
19185    // Trigger completion when typing a dash, because the dash is an extra
19186    // word character in the 'element' scope, which contains the cursor.
19187    cx.simulate_keystroke("-");
19188    cx.executor().run_until_parked();
19189    cx.update_editor(|editor, _, _| {
19190        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19191        {
19192            assert_eq!(
19193                completion_menu_entries(menu),
19194                &["bg-blue", "bg-red", "bg-yellow"]
19195            );
19196        } else {
19197            panic!("expected completion menu to be open");
19198        }
19199    });
19200
19201    cx.simulate_keystroke("l");
19202    cx.executor().run_until_parked();
19203    cx.update_editor(|editor, _, _| {
19204        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19205        {
19206            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19207        } else {
19208            panic!("expected completion menu to be open");
19209        }
19210    });
19211
19212    // When filtering completions, consider the character after the '-' to
19213    // be the start of a subword.
19214    cx.set_state(r#"<p class="yelˇ" />"#);
19215    cx.simulate_keystroke("l");
19216    cx.executor().run_until_parked();
19217    cx.update_editor(|editor, _, _| {
19218        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19219        {
19220            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19221        } else {
19222            panic!("expected completion menu to be open");
19223        }
19224    });
19225}
19226
19227fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19228    let entries = menu.entries.borrow();
19229    entries.iter().map(|mat| mat.string.clone()).collect()
19230}
19231
19232#[gpui::test]
19233async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19234    init_test(cx, |settings| {
19235        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19236    });
19237
19238    let fs = FakeFs::new(cx.executor());
19239    fs.insert_file(path!("/file.ts"), Default::default()).await;
19240
19241    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19242    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19243
19244    language_registry.add(Arc::new(Language::new(
19245        LanguageConfig {
19246            name: "TypeScript".into(),
19247            matcher: LanguageMatcher {
19248                path_suffixes: vec!["ts".to_string()],
19249                ..Default::default()
19250            },
19251            ..Default::default()
19252        },
19253        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19254    )));
19255    update_test_language_settings(cx, |settings| {
19256        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19257    });
19258
19259    let test_plugin = "test_plugin";
19260    let _ = language_registry.register_fake_lsp(
19261        "TypeScript",
19262        FakeLspAdapter {
19263            prettier_plugins: vec![test_plugin],
19264            ..Default::default()
19265        },
19266    );
19267
19268    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19269    let buffer = project
19270        .update(cx, |project, cx| {
19271            project.open_local_buffer(path!("/file.ts"), cx)
19272        })
19273        .await
19274        .unwrap();
19275
19276    let buffer_text = "one\ntwo\nthree\n";
19277    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19278    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19279    editor.update_in(cx, |editor, window, cx| {
19280        editor.set_text(buffer_text, window, cx)
19281    });
19282
19283    editor
19284        .update_in(cx, |editor, window, cx| {
19285            editor.perform_format(
19286                project.clone(),
19287                FormatTrigger::Manual,
19288                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19289                window,
19290                cx,
19291            )
19292        })
19293        .unwrap()
19294        .await;
19295    assert_eq!(
19296        editor.update(cx, |editor, cx| editor.text(cx)),
19297        buffer_text.to_string() + prettier_format_suffix,
19298        "Test prettier formatting was not applied to the original buffer text",
19299    );
19300
19301    update_test_language_settings(cx, |settings| {
19302        settings.defaults.formatter = Some(FormatterList::default())
19303    });
19304    let format = editor.update_in(cx, |editor, window, cx| {
19305        editor.perform_format(
19306            project.clone(),
19307            FormatTrigger::Manual,
19308            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19309            window,
19310            cx,
19311        )
19312    });
19313    format.await.unwrap();
19314    assert_eq!(
19315        editor.update(cx, |editor, cx| editor.text(cx)),
19316        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19317        "Autoformatting (via test prettier) was not applied to the original buffer text",
19318    );
19319}
19320
19321#[gpui::test]
19322async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19323    init_test(cx, |settings| {
19324        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19325    });
19326
19327    let fs = FakeFs::new(cx.executor());
19328    fs.insert_file(path!("/file.settings"), Default::default())
19329        .await;
19330
19331    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19332    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19333
19334    let ts_lang = Arc::new(Language::new(
19335        LanguageConfig {
19336            name: "TypeScript".into(),
19337            matcher: LanguageMatcher {
19338                path_suffixes: vec!["ts".to_string()],
19339                ..LanguageMatcher::default()
19340            },
19341            prettier_parser_name: Some("typescript".to_string()),
19342            ..LanguageConfig::default()
19343        },
19344        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19345    ));
19346
19347    language_registry.add(ts_lang.clone());
19348
19349    update_test_language_settings(cx, |settings| {
19350        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19351    });
19352
19353    let test_plugin = "test_plugin";
19354    let _ = language_registry.register_fake_lsp(
19355        "TypeScript",
19356        FakeLspAdapter {
19357            prettier_plugins: vec![test_plugin],
19358            ..Default::default()
19359        },
19360    );
19361
19362    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19363    let buffer = project
19364        .update(cx, |project, cx| {
19365            project.open_local_buffer(path!("/file.settings"), cx)
19366        })
19367        .await
19368        .unwrap();
19369
19370    project.update(cx, |project, cx| {
19371        project.set_language_for_buffer(&buffer, ts_lang, cx)
19372    });
19373
19374    let buffer_text = "one\ntwo\nthree\n";
19375    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19376    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19377    editor.update_in(cx, |editor, window, cx| {
19378        editor.set_text(buffer_text, window, cx)
19379    });
19380
19381    editor
19382        .update_in(cx, |editor, window, cx| {
19383            editor.perform_format(
19384                project.clone(),
19385                FormatTrigger::Manual,
19386                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19387                window,
19388                cx,
19389            )
19390        })
19391        .unwrap()
19392        .await;
19393    assert_eq!(
19394        editor.update(cx, |editor, cx| editor.text(cx)),
19395        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19396        "Test prettier formatting was not applied to the original buffer text",
19397    );
19398
19399    update_test_language_settings(cx, |settings| {
19400        settings.defaults.formatter = Some(FormatterList::default())
19401    });
19402    let format = editor.update_in(cx, |editor, window, cx| {
19403        editor.perform_format(
19404            project.clone(),
19405            FormatTrigger::Manual,
19406            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19407            window,
19408            cx,
19409        )
19410    });
19411    format.await.unwrap();
19412
19413    assert_eq!(
19414        editor.update(cx, |editor, cx| editor.text(cx)),
19415        buffer_text.to_string()
19416            + prettier_format_suffix
19417            + "\ntypescript\n"
19418            + prettier_format_suffix
19419            + "\ntypescript",
19420        "Autoformatting (via test prettier) was not applied to the original buffer text",
19421    );
19422}
19423
19424#[gpui::test]
19425async fn test_addition_reverts(cx: &mut TestAppContext) {
19426    init_test(cx, |_| {});
19427    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19428    let base_text = indoc! {r#"
19429        struct Row;
19430        struct Row1;
19431        struct Row2;
19432
19433        struct Row4;
19434        struct Row5;
19435        struct Row6;
19436
19437        struct Row8;
19438        struct Row9;
19439        struct Row10;"#};
19440
19441    // When addition hunks are not adjacent to carets, no hunk revert is performed
19442    assert_hunk_revert(
19443        indoc! {r#"struct Row;
19444                   struct Row1;
19445                   struct Row1.1;
19446                   struct Row1.2;
19447                   struct Row2;ˇ
19448
19449                   struct Row4;
19450                   struct Row5;
19451                   struct Row6;
19452
19453                   struct Row8;
19454                   ˇstruct Row9;
19455                   struct Row9.1;
19456                   struct Row9.2;
19457                   struct Row9.3;
19458                   struct Row10;"#},
19459        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19460        indoc! {r#"struct Row;
19461                   struct Row1;
19462                   struct Row1.1;
19463                   struct Row1.2;
19464                   struct Row2;ˇ
19465
19466                   struct Row4;
19467                   struct Row5;
19468                   struct Row6;
19469
19470                   struct Row8;
19471                   ˇstruct Row9;
19472                   struct Row9.1;
19473                   struct Row9.2;
19474                   struct Row9.3;
19475                   struct Row10;"#},
19476        base_text,
19477        &mut cx,
19478    );
19479    // Same for selections
19480    assert_hunk_revert(
19481        indoc! {r#"struct Row;
19482                   struct Row1;
19483                   struct Row2;
19484                   struct Row2.1;
19485                   struct Row2.2;
19486                   «ˇ
19487                   struct Row4;
19488                   struct» Row5;
19489                   «struct Row6;
19490                   ˇ»
19491                   struct Row9.1;
19492                   struct Row9.2;
19493                   struct Row9.3;
19494                   struct Row8;
19495                   struct Row9;
19496                   struct Row10;"#},
19497        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19498        indoc! {r#"struct Row;
19499                   struct Row1;
19500                   struct Row2;
19501                   struct Row2.1;
19502                   struct Row2.2;
19503                   «ˇ
19504                   struct Row4;
19505                   struct» Row5;
19506                   «struct Row6;
19507                   ˇ»
19508                   struct Row9.1;
19509                   struct Row9.2;
19510                   struct Row9.3;
19511                   struct Row8;
19512                   struct Row9;
19513                   struct Row10;"#},
19514        base_text,
19515        &mut cx,
19516    );
19517
19518    // When carets and selections intersect the addition hunks, those are reverted.
19519    // Adjacent carets got merged.
19520    assert_hunk_revert(
19521        indoc! {r#"struct Row;
19522                   ˇ// something on the top
19523                   struct Row1;
19524                   struct Row2;
19525                   struct Roˇw3.1;
19526                   struct Row2.2;
19527                   struct Row2.3;ˇ
19528
19529                   struct Row4;
19530                   struct ˇRow5.1;
19531                   struct Row5.2;
19532                   struct «Rowˇ»5.3;
19533                   struct Row5;
19534                   struct Row6;
19535                   ˇ
19536                   struct Row9.1;
19537                   struct «Rowˇ»9.2;
19538                   struct «ˇRow»9.3;
19539                   struct Row8;
19540                   struct Row9;
19541                   «ˇ// something on bottom»
19542                   struct Row10;"#},
19543        vec![
19544            DiffHunkStatusKind::Added,
19545            DiffHunkStatusKind::Added,
19546            DiffHunkStatusKind::Added,
19547            DiffHunkStatusKind::Added,
19548            DiffHunkStatusKind::Added,
19549        ],
19550        indoc! {r#"struct Row;
19551                   ˇstruct Row1;
19552                   struct Row2;
19553                   ˇ
19554                   struct Row4;
19555                   ˇstruct Row5;
19556                   struct Row6;
19557                   ˇ
19558                   ˇstruct Row8;
19559                   struct Row9;
19560                   ˇstruct Row10;"#},
19561        base_text,
19562        &mut cx,
19563    );
19564}
19565
19566#[gpui::test]
19567async fn test_modification_reverts(cx: &mut TestAppContext) {
19568    init_test(cx, |_| {});
19569    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19570    let base_text = indoc! {r#"
19571        struct Row;
19572        struct Row1;
19573        struct Row2;
19574
19575        struct Row4;
19576        struct Row5;
19577        struct Row6;
19578
19579        struct Row8;
19580        struct Row9;
19581        struct Row10;"#};
19582
19583    // Modification hunks behave the same as the addition ones.
19584    assert_hunk_revert(
19585        indoc! {r#"struct Row;
19586                   struct Row1;
19587                   struct Row33;
19588                   ˇ
19589                   struct Row4;
19590                   struct Row5;
19591                   struct Row6;
19592                   ˇ
19593                   struct Row99;
19594                   struct Row9;
19595                   struct Row10;"#},
19596        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19597        indoc! {r#"struct Row;
19598                   struct Row1;
19599                   struct Row33;
19600                   ˇ
19601                   struct Row4;
19602                   struct Row5;
19603                   struct Row6;
19604                   ˇ
19605                   struct Row99;
19606                   struct Row9;
19607                   struct Row10;"#},
19608        base_text,
19609        &mut cx,
19610    );
19611    assert_hunk_revert(
19612        indoc! {r#"struct Row;
19613                   struct Row1;
19614                   struct Row33;
19615                   «ˇ
19616                   struct Row4;
19617                   struct» Row5;
19618                   «struct Row6;
19619                   ˇ»
19620                   struct Row99;
19621                   struct Row9;
19622                   struct Row10;"#},
19623        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19624        indoc! {r#"struct Row;
19625                   struct Row1;
19626                   struct Row33;
19627                   «ˇ
19628                   struct Row4;
19629                   struct» Row5;
19630                   «struct Row6;
19631                   ˇ»
19632                   struct Row99;
19633                   struct Row9;
19634                   struct Row10;"#},
19635        base_text,
19636        &mut cx,
19637    );
19638
19639    assert_hunk_revert(
19640        indoc! {r#"ˇstruct Row1.1;
19641                   struct Row1;
19642                   «ˇstr»uct Row22;
19643
19644                   struct ˇRow44;
19645                   struct Row5;
19646                   struct «Rˇ»ow66;ˇ
19647
19648                   «struˇ»ct Row88;
19649                   struct Row9;
19650                   struct Row1011;ˇ"#},
19651        vec![
19652            DiffHunkStatusKind::Modified,
19653            DiffHunkStatusKind::Modified,
19654            DiffHunkStatusKind::Modified,
19655            DiffHunkStatusKind::Modified,
19656            DiffHunkStatusKind::Modified,
19657            DiffHunkStatusKind::Modified,
19658        ],
19659        indoc! {r#"struct Row;
19660                   ˇstruct Row1;
19661                   struct Row2;
19662                   ˇ
19663                   struct Row4;
19664                   ˇstruct Row5;
19665                   struct Row6;
19666                   ˇ
19667                   struct Row8;
19668                   ˇstruct Row9;
19669                   struct Row10;ˇ"#},
19670        base_text,
19671        &mut cx,
19672    );
19673}
19674
19675#[gpui::test]
19676async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19677    init_test(cx, |_| {});
19678    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19679    let base_text = indoc! {r#"
19680        one
19681
19682        two
19683        three
19684        "#};
19685
19686    cx.set_head_text(base_text);
19687    cx.set_state("\nˇ\n");
19688    cx.executor().run_until_parked();
19689    cx.update_editor(|editor, _window, cx| {
19690        editor.expand_selected_diff_hunks(cx);
19691    });
19692    cx.executor().run_until_parked();
19693    cx.update_editor(|editor, window, cx| {
19694        editor.backspace(&Default::default(), window, cx);
19695    });
19696    cx.run_until_parked();
19697    cx.assert_state_with_diff(
19698        indoc! {r#"
19699
19700        - two
19701        - threeˇ
19702        +
19703        "#}
19704        .to_string(),
19705    );
19706}
19707
19708#[gpui::test]
19709async fn test_deletion_reverts(cx: &mut TestAppContext) {
19710    init_test(cx, |_| {});
19711    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19712    let base_text = indoc! {r#"struct Row;
19713struct Row1;
19714struct Row2;
19715
19716struct Row4;
19717struct Row5;
19718struct Row6;
19719
19720struct Row8;
19721struct Row9;
19722struct Row10;"#};
19723
19724    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19725    assert_hunk_revert(
19726        indoc! {r#"struct Row;
19727                   struct Row2;
19728
19729                   ˇstruct Row4;
19730                   struct Row5;
19731                   struct Row6;
19732                   ˇ
19733                   struct Row8;
19734                   struct Row10;"#},
19735        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19736        indoc! {r#"struct Row;
19737                   struct Row2;
19738
19739                   ˇstruct Row4;
19740                   struct Row5;
19741                   struct Row6;
19742                   ˇ
19743                   struct Row8;
19744                   struct Row10;"#},
19745        base_text,
19746        &mut cx,
19747    );
19748    assert_hunk_revert(
19749        indoc! {r#"struct Row;
19750                   struct Row2;
19751
19752                   «ˇstruct Row4;
19753                   struct» Row5;
19754                   «struct Row6;
19755                   ˇ»
19756                   struct Row8;
19757                   struct Row10;"#},
19758        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19759        indoc! {r#"struct Row;
19760                   struct Row2;
19761
19762                   «ˇstruct Row4;
19763                   struct» Row5;
19764                   «struct Row6;
19765                   ˇ»
19766                   struct Row8;
19767                   struct Row10;"#},
19768        base_text,
19769        &mut cx,
19770    );
19771
19772    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19773    assert_hunk_revert(
19774        indoc! {r#"struct Row;
19775                   ˇstruct Row2;
19776
19777                   struct Row4;
19778                   struct Row5;
19779                   struct Row6;
19780
19781                   struct Row8;ˇ
19782                   struct Row10;"#},
19783        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19784        indoc! {r#"struct Row;
19785                   struct Row1;
19786                   ˇstruct Row2;
19787
19788                   struct Row4;
19789                   struct Row5;
19790                   struct Row6;
19791
19792                   struct Row8;ˇ
19793                   struct Row9;
19794                   struct Row10;"#},
19795        base_text,
19796        &mut cx,
19797    );
19798    assert_hunk_revert(
19799        indoc! {r#"struct Row;
19800                   struct Row2«ˇ;
19801                   struct Row4;
19802                   struct» Row5;
19803                   «struct Row6;
19804
19805                   struct Row8;ˇ»
19806                   struct Row10;"#},
19807        vec![
19808            DiffHunkStatusKind::Deleted,
19809            DiffHunkStatusKind::Deleted,
19810            DiffHunkStatusKind::Deleted,
19811        ],
19812        indoc! {r#"struct Row;
19813                   struct Row1;
19814                   struct Row2«ˇ;
19815
19816                   struct Row4;
19817                   struct» Row5;
19818                   «struct Row6;
19819
19820                   struct Row8;ˇ»
19821                   struct Row9;
19822                   struct Row10;"#},
19823        base_text,
19824        &mut cx,
19825    );
19826}
19827
19828#[gpui::test]
19829async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19830    init_test(cx, |_| {});
19831
19832    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19833    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19834    let base_text_3 =
19835        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19836
19837    let text_1 = edit_first_char_of_every_line(base_text_1);
19838    let text_2 = edit_first_char_of_every_line(base_text_2);
19839    let text_3 = edit_first_char_of_every_line(base_text_3);
19840
19841    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19842    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19843    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19844
19845    let multibuffer = cx.new(|cx| {
19846        let mut multibuffer = MultiBuffer::new(ReadWrite);
19847        multibuffer.push_excerpts(
19848            buffer_1.clone(),
19849            [
19850                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19851                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19852                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19853            ],
19854            cx,
19855        );
19856        multibuffer.push_excerpts(
19857            buffer_2.clone(),
19858            [
19859                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19860                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19861                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19862            ],
19863            cx,
19864        );
19865        multibuffer.push_excerpts(
19866            buffer_3.clone(),
19867            [
19868                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19869                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19870                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19871            ],
19872            cx,
19873        );
19874        multibuffer
19875    });
19876
19877    let fs = FakeFs::new(cx.executor());
19878    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19879    let (editor, cx) = cx
19880        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19881    editor.update_in(cx, |editor, _window, cx| {
19882        for (buffer, diff_base) in [
19883            (buffer_1.clone(), base_text_1),
19884            (buffer_2.clone(), base_text_2),
19885            (buffer_3.clone(), base_text_3),
19886        ] {
19887            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19888            editor
19889                .buffer
19890                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19891        }
19892    });
19893    cx.executor().run_until_parked();
19894
19895    editor.update_in(cx, |editor, window, cx| {
19896        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}");
19897        editor.select_all(&SelectAll, window, cx);
19898        editor.git_restore(&Default::default(), window, cx);
19899    });
19900    cx.executor().run_until_parked();
19901
19902    // When all ranges are selected, all buffer hunks are reverted.
19903    editor.update(cx, |editor, cx| {
19904        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");
19905    });
19906    buffer_1.update(cx, |buffer, _| {
19907        assert_eq!(buffer.text(), base_text_1);
19908    });
19909    buffer_2.update(cx, |buffer, _| {
19910        assert_eq!(buffer.text(), base_text_2);
19911    });
19912    buffer_3.update(cx, |buffer, _| {
19913        assert_eq!(buffer.text(), base_text_3);
19914    });
19915
19916    editor.update_in(cx, |editor, window, cx| {
19917        editor.undo(&Default::default(), window, cx);
19918    });
19919
19920    editor.update_in(cx, |editor, window, cx| {
19921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19922            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19923        });
19924        editor.git_restore(&Default::default(), window, cx);
19925    });
19926
19927    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19928    // but not affect buffer_2 and its related excerpts.
19929    editor.update(cx, |editor, cx| {
19930        assert_eq!(
19931            editor.text(cx),
19932            "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}"
19933        );
19934    });
19935    buffer_1.update(cx, |buffer, _| {
19936        assert_eq!(buffer.text(), base_text_1);
19937    });
19938    buffer_2.update(cx, |buffer, _| {
19939        assert_eq!(
19940            buffer.text(),
19941            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19942        );
19943    });
19944    buffer_3.update(cx, |buffer, _| {
19945        assert_eq!(
19946            buffer.text(),
19947            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19948        );
19949    });
19950
19951    fn edit_first_char_of_every_line(text: &str) -> String {
19952        text.split('\n')
19953            .map(|line| format!("X{}", &line[1..]))
19954            .collect::<Vec<_>>()
19955            .join("\n")
19956    }
19957}
19958
19959#[gpui::test]
19960async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19961    init_test(cx, |_| {});
19962
19963    let cols = 4;
19964    let rows = 10;
19965    let sample_text_1 = sample_text(rows, cols, 'a');
19966    assert_eq!(
19967        sample_text_1,
19968        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19969    );
19970    let sample_text_2 = sample_text(rows, cols, 'l');
19971    assert_eq!(
19972        sample_text_2,
19973        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19974    );
19975    let sample_text_3 = sample_text(rows, cols, 'v');
19976    assert_eq!(
19977        sample_text_3,
19978        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19979    );
19980
19981    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19982    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19983    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19984
19985    let multi_buffer = cx.new(|cx| {
19986        let mut multibuffer = MultiBuffer::new(ReadWrite);
19987        multibuffer.push_excerpts(
19988            buffer_1.clone(),
19989            [
19990                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19991                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19992                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19993            ],
19994            cx,
19995        );
19996        multibuffer.push_excerpts(
19997            buffer_2.clone(),
19998            [
19999                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20000                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20001                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20002            ],
20003            cx,
20004        );
20005        multibuffer.push_excerpts(
20006            buffer_3.clone(),
20007            [
20008                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20009                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20010                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20011            ],
20012            cx,
20013        );
20014        multibuffer
20015    });
20016
20017    let fs = FakeFs::new(cx.executor());
20018    fs.insert_tree(
20019        "/a",
20020        json!({
20021            "main.rs": sample_text_1,
20022            "other.rs": sample_text_2,
20023            "lib.rs": sample_text_3,
20024        }),
20025    )
20026    .await;
20027    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20028    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20029    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20030    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20031        Editor::new(
20032            EditorMode::full(),
20033            multi_buffer,
20034            Some(project.clone()),
20035            window,
20036            cx,
20037        )
20038    });
20039    let multibuffer_item_id = workspace
20040        .update(cx, |workspace, window, cx| {
20041            assert!(
20042                workspace.active_item(cx).is_none(),
20043                "active item should be None before the first item is added"
20044            );
20045            workspace.add_item_to_active_pane(
20046                Box::new(multi_buffer_editor.clone()),
20047                None,
20048                true,
20049                window,
20050                cx,
20051            );
20052            let active_item = workspace
20053                .active_item(cx)
20054                .expect("should have an active item after adding the multi buffer");
20055            assert_eq!(
20056                active_item.buffer_kind(cx),
20057                ItemBufferKind::Multibuffer,
20058                "A multi buffer was expected to active after adding"
20059            );
20060            active_item.item_id()
20061        })
20062        .unwrap();
20063    cx.executor().run_until_parked();
20064
20065    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20066        editor.change_selections(
20067            SelectionEffects::scroll(Autoscroll::Next),
20068            window,
20069            cx,
20070            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20071        );
20072        editor.open_excerpts(&OpenExcerpts, window, cx);
20073    });
20074    cx.executor().run_until_parked();
20075    let first_item_id = workspace
20076        .update(cx, |workspace, window, cx| {
20077            let active_item = workspace
20078                .active_item(cx)
20079                .expect("should have an active item after navigating into the 1st buffer");
20080            let first_item_id = active_item.item_id();
20081            assert_ne!(
20082                first_item_id, multibuffer_item_id,
20083                "Should navigate into the 1st buffer and activate it"
20084            );
20085            assert_eq!(
20086                active_item.buffer_kind(cx),
20087                ItemBufferKind::Singleton,
20088                "New active item should be a singleton buffer"
20089            );
20090            assert_eq!(
20091                active_item
20092                    .act_as::<Editor>(cx)
20093                    .expect("should have navigated into an editor for the 1st buffer")
20094                    .read(cx)
20095                    .text(cx),
20096                sample_text_1
20097            );
20098
20099            workspace
20100                .go_back(workspace.active_pane().downgrade(), window, cx)
20101                .detach_and_log_err(cx);
20102
20103            first_item_id
20104        })
20105        .unwrap();
20106    cx.executor().run_until_parked();
20107    workspace
20108        .update(cx, |workspace, _, cx| {
20109            let active_item = workspace
20110                .active_item(cx)
20111                .expect("should have an active item after navigating back");
20112            assert_eq!(
20113                active_item.item_id(),
20114                multibuffer_item_id,
20115                "Should navigate back to the multi buffer"
20116            );
20117            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20118        })
20119        .unwrap();
20120
20121    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20122        editor.change_selections(
20123            SelectionEffects::scroll(Autoscroll::Next),
20124            window,
20125            cx,
20126            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20127        );
20128        editor.open_excerpts(&OpenExcerpts, window, cx);
20129    });
20130    cx.executor().run_until_parked();
20131    let second_item_id = workspace
20132        .update(cx, |workspace, window, cx| {
20133            let active_item = workspace
20134                .active_item(cx)
20135                .expect("should have an active item after navigating into the 2nd buffer");
20136            let second_item_id = active_item.item_id();
20137            assert_ne!(
20138                second_item_id, multibuffer_item_id,
20139                "Should navigate away from the multibuffer"
20140            );
20141            assert_ne!(
20142                second_item_id, first_item_id,
20143                "Should navigate into the 2nd buffer and activate it"
20144            );
20145            assert_eq!(
20146                active_item.buffer_kind(cx),
20147                ItemBufferKind::Singleton,
20148                "New active item should be a singleton buffer"
20149            );
20150            assert_eq!(
20151                active_item
20152                    .act_as::<Editor>(cx)
20153                    .expect("should have navigated into an editor")
20154                    .read(cx)
20155                    .text(cx),
20156                sample_text_2
20157            );
20158
20159            workspace
20160                .go_back(workspace.active_pane().downgrade(), window, cx)
20161                .detach_and_log_err(cx);
20162
20163            second_item_id
20164        })
20165        .unwrap();
20166    cx.executor().run_until_parked();
20167    workspace
20168        .update(cx, |workspace, _, cx| {
20169            let active_item = workspace
20170                .active_item(cx)
20171                .expect("should have an active item after navigating back from the 2nd buffer");
20172            assert_eq!(
20173                active_item.item_id(),
20174                multibuffer_item_id,
20175                "Should navigate back from the 2nd buffer to the multi buffer"
20176            );
20177            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20178        })
20179        .unwrap();
20180
20181    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20182        editor.change_selections(
20183            SelectionEffects::scroll(Autoscroll::Next),
20184            window,
20185            cx,
20186            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20187        );
20188        editor.open_excerpts(&OpenExcerpts, window, cx);
20189    });
20190    cx.executor().run_until_parked();
20191    workspace
20192        .update(cx, |workspace, window, cx| {
20193            let active_item = workspace
20194                .active_item(cx)
20195                .expect("should have an active item after navigating into the 3rd buffer");
20196            let third_item_id = active_item.item_id();
20197            assert_ne!(
20198                third_item_id, multibuffer_item_id,
20199                "Should navigate into the 3rd buffer and activate it"
20200            );
20201            assert_ne!(third_item_id, first_item_id);
20202            assert_ne!(third_item_id, second_item_id);
20203            assert_eq!(
20204                active_item.buffer_kind(cx),
20205                ItemBufferKind::Singleton,
20206                "New active item should be a singleton buffer"
20207            );
20208            assert_eq!(
20209                active_item
20210                    .act_as::<Editor>(cx)
20211                    .expect("should have navigated into an editor")
20212                    .read(cx)
20213                    .text(cx),
20214                sample_text_3
20215            );
20216
20217            workspace
20218                .go_back(workspace.active_pane().downgrade(), window, cx)
20219                .detach_and_log_err(cx);
20220        })
20221        .unwrap();
20222    cx.executor().run_until_parked();
20223    workspace
20224        .update(cx, |workspace, _, cx| {
20225            let active_item = workspace
20226                .active_item(cx)
20227                .expect("should have an active item after navigating back from the 3rd buffer");
20228            assert_eq!(
20229                active_item.item_id(),
20230                multibuffer_item_id,
20231                "Should navigate back from the 3rd buffer to the multi buffer"
20232            );
20233            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20234        })
20235        .unwrap();
20236}
20237
20238#[gpui::test]
20239async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20240    init_test(cx, |_| {});
20241
20242    let mut cx = EditorTestContext::new(cx).await;
20243
20244    let diff_base = r#"
20245        use some::mod;
20246
20247        const A: u32 = 42;
20248
20249        fn main() {
20250            println!("hello");
20251
20252            println!("world");
20253        }
20254        "#
20255    .unindent();
20256
20257    cx.set_state(
20258        &r#"
20259        use some::modified;
20260
20261        ˇ
20262        fn main() {
20263            println!("hello there");
20264
20265            println!("around the");
20266            println!("world");
20267        }
20268        "#
20269        .unindent(),
20270    );
20271
20272    cx.set_head_text(&diff_base);
20273    executor.run_until_parked();
20274
20275    cx.update_editor(|editor, window, cx| {
20276        editor.go_to_next_hunk(&GoToHunk, window, cx);
20277        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20278    });
20279    executor.run_until_parked();
20280    cx.assert_state_with_diff(
20281        r#"
20282          use some::modified;
20283
20284
20285          fn main() {
20286        -     println!("hello");
20287        + ˇ    println!("hello there");
20288
20289              println!("around the");
20290              println!("world");
20291          }
20292        "#
20293        .unindent(),
20294    );
20295
20296    cx.update_editor(|editor, window, cx| {
20297        for _ in 0..2 {
20298            editor.go_to_next_hunk(&GoToHunk, window, cx);
20299            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20300        }
20301    });
20302    executor.run_until_parked();
20303    cx.assert_state_with_diff(
20304        r#"
20305        - use some::mod;
20306        + ˇuse some::modified;
20307
20308
20309          fn main() {
20310        -     println!("hello");
20311        +     println!("hello there");
20312
20313        +     println!("around the");
20314              println!("world");
20315          }
20316        "#
20317        .unindent(),
20318    );
20319
20320    cx.update_editor(|editor, window, cx| {
20321        editor.go_to_next_hunk(&GoToHunk, window, cx);
20322        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20323    });
20324    executor.run_until_parked();
20325    cx.assert_state_with_diff(
20326        r#"
20327        - use some::mod;
20328        + use some::modified;
20329
20330        - const A: u32 = 42;
20331          ˇ
20332          fn main() {
20333        -     println!("hello");
20334        +     println!("hello there");
20335
20336        +     println!("around the");
20337              println!("world");
20338          }
20339        "#
20340        .unindent(),
20341    );
20342
20343    cx.update_editor(|editor, window, cx| {
20344        editor.cancel(&Cancel, window, cx);
20345    });
20346
20347    cx.assert_state_with_diff(
20348        r#"
20349          use some::modified;
20350
20351          ˇ
20352          fn main() {
20353              println!("hello there");
20354
20355              println!("around the");
20356              println!("world");
20357          }
20358        "#
20359        .unindent(),
20360    );
20361}
20362
20363#[gpui::test]
20364async fn test_diff_base_change_with_expanded_diff_hunks(
20365    executor: BackgroundExecutor,
20366    cx: &mut TestAppContext,
20367) {
20368    init_test(cx, |_| {});
20369
20370    let mut cx = EditorTestContext::new(cx).await;
20371
20372    let diff_base = r#"
20373        use some::mod1;
20374        use some::mod2;
20375
20376        const A: u32 = 42;
20377        const B: u32 = 42;
20378        const C: u32 = 42;
20379
20380        fn main() {
20381            println!("hello");
20382
20383            println!("world");
20384        }
20385        "#
20386    .unindent();
20387
20388    cx.set_state(
20389        &r#"
20390        use some::mod2;
20391
20392        const A: u32 = 42;
20393        const C: u32 = 42;
20394
20395        fn main(ˇ) {
20396            //println!("hello");
20397
20398            println!("world");
20399            //
20400            //
20401        }
20402        "#
20403        .unindent(),
20404    );
20405
20406    cx.set_head_text(&diff_base);
20407    executor.run_until_parked();
20408
20409    cx.update_editor(|editor, window, cx| {
20410        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20411    });
20412    executor.run_until_parked();
20413    cx.assert_state_with_diff(
20414        r#"
20415        - use some::mod1;
20416          use some::mod2;
20417
20418          const A: u32 = 42;
20419        - const B: u32 = 42;
20420          const C: u32 = 42;
20421
20422          fn main(ˇ) {
20423        -     println!("hello");
20424        +     //println!("hello");
20425
20426              println!("world");
20427        +     //
20428        +     //
20429          }
20430        "#
20431        .unindent(),
20432    );
20433
20434    cx.set_head_text("new diff base!");
20435    executor.run_until_parked();
20436    cx.assert_state_with_diff(
20437        r#"
20438        - new diff base!
20439        + use some::mod2;
20440        +
20441        + const A: u32 = 42;
20442        + const C: u32 = 42;
20443        +
20444        + fn main(ˇ) {
20445        +     //println!("hello");
20446        +
20447        +     println!("world");
20448        +     //
20449        +     //
20450        + }
20451        "#
20452        .unindent(),
20453    );
20454}
20455
20456#[gpui::test]
20457async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20458    init_test(cx, |_| {});
20459
20460    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20461    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20462    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20463    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20464    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20465    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20466
20467    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20468    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20469    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20470
20471    let multi_buffer = cx.new(|cx| {
20472        let mut multibuffer = MultiBuffer::new(ReadWrite);
20473        multibuffer.push_excerpts(
20474            buffer_1.clone(),
20475            [
20476                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20477                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20478                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20479            ],
20480            cx,
20481        );
20482        multibuffer.push_excerpts(
20483            buffer_2.clone(),
20484            [
20485                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20486                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20487                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20488            ],
20489            cx,
20490        );
20491        multibuffer.push_excerpts(
20492            buffer_3.clone(),
20493            [
20494                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20495                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20496                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20497            ],
20498            cx,
20499        );
20500        multibuffer
20501    });
20502
20503    let editor =
20504        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20505    editor
20506        .update(cx, |editor, _window, cx| {
20507            for (buffer, diff_base) in [
20508                (buffer_1.clone(), file_1_old),
20509                (buffer_2.clone(), file_2_old),
20510                (buffer_3.clone(), file_3_old),
20511            ] {
20512                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20513                editor
20514                    .buffer
20515                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20516            }
20517        })
20518        .unwrap();
20519
20520    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20521    cx.run_until_parked();
20522
20523    cx.assert_editor_state(
20524        &"
20525            ˇaaa
20526            ccc
20527            ddd
20528
20529            ggg
20530            hhh
20531
20532
20533            lll
20534            mmm
20535            NNN
20536
20537            qqq
20538            rrr
20539
20540            uuu
20541            111
20542            222
20543            333
20544
20545            666
20546            777
20547
20548            000
20549            !!!"
20550        .unindent(),
20551    );
20552
20553    cx.update_editor(|editor, window, cx| {
20554        editor.select_all(&SelectAll, window, cx);
20555        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20556    });
20557    cx.executor().run_until_parked();
20558
20559    cx.assert_state_with_diff(
20560        "
20561            «aaa
20562          - bbb
20563            ccc
20564            ddd
20565
20566            ggg
20567            hhh
20568
20569
20570            lll
20571            mmm
20572          - nnn
20573          + NNN
20574
20575            qqq
20576            rrr
20577
20578            uuu
20579            111
20580            222
20581            333
20582
20583          + 666
20584            777
20585
20586            000
20587            !!!ˇ»"
20588            .unindent(),
20589    );
20590}
20591
20592#[gpui::test]
20593async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20594    init_test(cx, |_| {});
20595
20596    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20597    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20598
20599    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20600    let multi_buffer = cx.new(|cx| {
20601        let mut multibuffer = MultiBuffer::new(ReadWrite);
20602        multibuffer.push_excerpts(
20603            buffer.clone(),
20604            [
20605                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20606                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20607                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20608            ],
20609            cx,
20610        );
20611        multibuffer
20612    });
20613
20614    let editor =
20615        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20616    editor
20617        .update(cx, |editor, _window, cx| {
20618            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20619            editor
20620                .buffer
20621                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20622        })
20623        .unwrap();
20624
20625    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20626    cx.run_until_parked();
20627
20628    cx.update_editor(|editor, window, cx| {
20629        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20630    });
20631    cx.executor().run_until_parked();
20632
20633    // When the start of a hunk coincides with the start of its excerpt,
20634    // the hunk is expanded. When the start of a hunk is earlier than
20635    // the start of its excerpt, the hunk is not expanded.
20636    cx.assert_state_with_diff(
20637        "
20638            ˇaaa
20639          - bbb
20640          + BBB
20641
20642          - ddd
20643          - eee
20644          + DDD
20645          + EEE
20646            fff
20647
20648            iii
20649        "
20650        .unindent(),
20651    );
20652}
20653
20654#[gpui::test]
20655async fn test_edits_around_expanded_insertion_hunks(
20656    executor: BackgroundExecutor,
20657    cx: &mut TestAppContext,
20658) {
20659    init_test(cx, |_| {});
20660
20661    let mut cx = EditorTestContext::new(cx).await;
20662
20663    let diff_base = r#"
20664        use some::mod1;
20665        use some::mod2;
20666
20667        const A: u32 = 42;
20668
20669        fn main() {
20670            println!("hello");
20671
20672            println!("world");
20673        }
20674        "#
20675    .unindent();
20676    executor.run_until_parked();
20677    cx.set_state(
20678        &r#"
20679        use some::mod1;
20680        use some::mod2;
20681
20682        const A: u32 = 42;
20683        const B: u32 = 42;
20684        const C: u32 = 42;
20685        ˇ
20686
20687        fn main() {
20688            println!("hello");
20689
20690            println!("world");
20691        }
20692        "#
20693        .unindent(),
20694    );
20695
20696    cx.set_head_text(&diff_base);
20697    executor.run_until_parked();
20698
20699    cx.update_editor(|editor, window, cx| {
20700        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20701    });
20702    executor.run_until_parked();
20703
20704    cx.assert_state_with_diff(
20705        r#"
20706        use some::mod1;
20707        use some::mod2;
20708
20709        const A: u32 = 42;
20710      + const B: u32 = 42;
20711      + const C: u32 = 42;
20712      + ˇ
20713
20714        fn main() {
20715            println!("hello");
20716
20717            println!("world");
20718        }
20719      "#
20720        .unindent(),
20721    );
20722
20723    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20724    executor.run_until_parked();
20725
20726    cx.assert_state_with_diff(
20727        r#"
20728        use some::mod1;
20729        use some::mod2;
20730
20731        const A: u32 = 42;
20732      + const B: u32 = 42;
20733      + const C: u32 = 42;
20734      + const D: u32 = 42;
20735      + ˇ
20736
20737        fn main() {
20738            println!("hello");
20739
20740            println!("world");
20741        }
20742      "#
20743        .unindent(),
20744    );
20745
20746    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20747    executor.run_until_parked();
20748
20749    cx.assert_state_with_diff(
20750        r#"
20751        use some::mod1;
20752        use some::mod2;
20753
20754        const A: u32 = 42;
20755      + const B: u32 = 42;
20756      + const C: u32 = 42;
20757      + const D: u32 = 42;
20758      + const E: u32 = 42;
20759      + ˇ
20760
20761        fn main() {
20762            println!("hello");
20763
20764            println!("world");
20765        }
20766      "#
20767        .unindent(),
20768    );
20769
20770    cx.update_editor(|editor, window, cx| {
20771        editor.delete_line(&DeleteLine, window, cx);
20772    });
20773    executor.run_until_parked();
20774
20775    cx.assert_state_with_diff(
20776        r#"
20777        use some::mod1;
20778        use some::mod2;
20779
20780        const A: u32 = 42;
20781      + const B: u32 = 42;
20782      + const C: u32 = 42;
20783      + const D: u32 = 42;
20784      + const E: u32 = 42;
20785        ˇ
20786        fn main() {
20787            println!("hello");
20788
20789            println!("world");
20790        }
20791      "#
20792        .unindent(),
20793    );
20794
20795    cx.update_editor(|editor, window, cx| {
20796        editor.move_up(&MoveUp, window, cx);
20797        editor.delete_line(&DeleteLine, window, cx);
20798        editor.move_up(&MoveUp, window, cx);
20799        editor.delete_line(&DeleteLine, window, cx);
20800        editor.move_up(&MoveUp, window, cx);
20801        editor.delete_line(&DeleteLine, window, cx);
20802    });
20803    executor.run_until_parked();
20804    cx.assert_state_with_diff(
20805        r#"
20806        use some::mod1;
20807        use some::mod2;
20808
20809        const A: u32 = 42;
20810      + const B: u32 = 42;
20811        ˇ
20812        fn main() {
20813            println!("hello");
20814
20815            println!("world");
20816        }
20817      "#
20818        .unindent(),
20819    );
20820
20821    cx.update_editor(|editor, window, cx| {
20822        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20823        editor.delete_line(&DeleteLine, window, cx);
20824    });
20825    executor.run_until_parked();
20826    cx.assert_state_with_diff(
20827        r#"
20828        ˇ
20829        fn main() {
20830            println!("hello");
20831
20832            println!("world");
20833        }
20834      "#
20835        .unindent(),
20836    );
20837}
20838
20839#[gpui::test]
20840async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20841    init_test(cx, |_| {});
20842
20843    let mut cx = EditorTestContext::new(cx).await;
20844    cx.set_head_text(indoc! { "
20845        one
20846        two
20847        three
20848        four
20849        five
20850        "
20851    });
20852    cx.set_state(indoc! { "
20853        one
20854        ˇthree
20855        five
20856    "});
20857    cx.run_until_parked();
20858    cx.update_editor(|editor, window, cx| {
20859        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20860    });
20861    cx.assert_state_with_diff(
20862        indoc! { "
20863        one
20864      - two
20865        ˇthree
20866      - four
20867        five
20868    "}
20869        .to_string(),
20870    );
20871    cx.update_editor(|editor, window, cx| {
20872        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20873    });
20874
20875    cx.assert_state_with_diff(
20876        indoc! { "
20877        one
20878        ˇthree
20879        five
20880    "}
20881        .to_string(),
20882    );
20883
20884    cx.set_state(indoc! { "
20885        one
20886        ˇTWO
20887        three
20888        four
20889        five
20890    "});
20891    cx.run_until_parked();
20892    cx.update_editor(|editor, window, cx| {
20893        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20894    });
20895
20896    cx.assert_state_with_diff(
20897        indoc! { "
20898            one
20899          - two
20900          + ˇTWO
20901            three
20902            four
20903            five
20904        "}
20905        .to_string(),
20906    );
20907    cx.update_editor(|editor, window, cx| {
20908        editor.move_up(&Default::default(), window, cx);
20909        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20910    });
20911    cx.assert_state_with_diff(
20912        indoc! { "
20913            one
20914            ˇTWO
20915            three
20916            four
20917            five
20918        "}
20919        .to_string(),
20920    );
20921}
20922
20923#[gpui::test]
20924async fn test_edits_around_expanded_deletion_hunks(
20925    executor: BackgroundExecutor,
20926    cx: &mut TestAppContext,
20927) {
20928    init_test(cx, |_| {});
20929
20930    let mut cx = EditorTestContext::new(cx).await;
20931
20932    let diff_base = r#"
20933        use some::mod1;
20934        use some::mod2;
20935
20936        const A: u32 = 42;
20937        const B: u32 = 42;
20938        const C: u32 = 42;
20939
20940
20941        fn main() {
20942            println!("hello");
20943
20944            println!("world");
20945        }
20946    "#
20947    .unindent();
20948    executor.run_until_parked();
20949    cx.set_state(
20950        &r#"
20951        use some::mod1;
20952        use some::mod2;
20953
20954        ˇconst B: u32 = 42;
20955        const C: u32 = 42;
20956
20957
20958        fn main() {
20959            println!("hello");
20960
20961            println!("world");
20962        }
20963        "#
20964        .unindent(),
20965    );
20966
20967    cx.set_head_text(&diff_base);
20968    executor.run_until_parked();
20969
20970    cx.update_editor(|editor, window, cx| {
20971        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20972    });
20973    executor.run_until_parked();
20974
20975    cx.assert_state_with_diff(
20976        r#"
20977        use some::mod1;
20978        use some::mod2;
20979
20980      - const A: u32 = 42;
20981        ˇconst B: u32 = 42;
20982        const C: u32 = 42;
20983
20984
20985        fn main() {
20986            println!("hello");
20987
20988            println!("world");
20989        }
20990      "#
20991        .unindent(),
20992    );
20993
20994    cx.update_editor(|editor, window, cx| {
20995        editor.delete_line(&DeleteLine, window, cx);
20996    });
20997    executor.run_until_parked();
20998    cx.assert_state_with_diff(
20999        r#"
21000        use some::mod1;
21001        use some::mod2;
21002
21003      - const A: u32 = 42;
21004      - const B: u32 = 42;
21005        ˇconst C: u32 = 42;
21006
21007
21008        fn main() {
21009            println!("hello");
21010
21011            println!("world");
21012        }
21013      "#
21014        .unindent(),
21015    );
21016
21017    cx.update_editor(|editor, window, cx| {
21018        editor.delete_line(&DeleteLine, window, cx);
21019    });
21020    executor.run_until_parked();
21021    cx.assert_state_with_diff(
21022        r#"
21023        use some::mod1;
21024        use some::mod2;
21025
21026      - const A: u32 = 42;
21027      - const B: u32 = 42;
21028      - const C: u32 = 42;
21029        ˇ
21030
21031        fn main() {
21032            println!("hello");
21033
21034            println!("world");
21035        }
21036      "#
21037        .unindent(),
21038    );
21039
21040    cx.update_editor(|editor, window, cx| {
21041        editor.handle_input("replacement", window, cx);
21042    });
21043    executor.run_until_parked();
21044    cx.assert_state_with_diff(
21045        r#"
21046        use some::mod1;
21047        use some::mod2;
21048
21049      - const A: u32 = 42;
21050      - const B: u32 = 42;
21051      - const C: u32 = 42;
21052      -
21053      + replacementˇ
21054
21055        fn main() {
21056            println!("hello");
21057
21058            println!("world");
21059        }
21060      "#
21061        .unindent(),
21062    );
21063}
21064
21065#[gpui::test]
21066async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21067    init_test(cx, |_| {});
21068
21069    let mut cx = EditorTestContext::new(cx).await;
21070
21071    let base_text = r#"
21072        one
21073        two
21074        three
21075        four
21076        five
21077    "#
21078    .unindent();
21079    executor.run_until_parked();
21080    cx.set_state(
21081        &r#"
21082        one
21083        two
21084        fˇour
21085        five
21086        "#
21087        .unindent(),
21088    );
21089
21090    cx.set_head_text(&base_text);
21091    executor.run_until_parked();
21092
21093    cx.update_editor(|editor, window, cx| {
21094        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21095    });
21096    executor.run_until_parked();
21097
21098    cx.assert_state_with_diff(
21099        r#"
21100          one
21101          two
21102        - three
21103          fˇour
21104          five
21105        "#
21106        .unindent(),
21107    );
21108
21109    cx.update_editor(|editor, window, cx| {
21110        editor.backspace(&Backspace, window, cx);
21111        editor.backspace(&Backspace, window, cx);
21112    });
21113    executor.run_until_parked();
21114    cx.assert_state_with_diff(
21115        r#"
21116          one
21117          two
21118        - threeˇ
21119        - four
21120        + our
21121          five
21122        "#
21123        .unindent(),
21124    );
21125}
21126
21127#[gpui::test]
21128async fn test_edit_after_expanded_modification_hunk(
21129    executor: BackgroundExecutor,
21130    cx: &mut TestAppContext,
21131) {
21132    init_test(cx, |_| {});
21133
21134    let mut cx = EditorTestContext::new(cx).await;
21135
21136    let diff_base = r#"
21137        use some::mod1;
21138        use some::mod2;
21139
21140        const A: u32 = 42;
21141        const B: u32 = 42;
21142        const C: u32 = 42;
21143        const D: u32 = 42;
21144
21145
21146        fn main() {
21147            println!("hello");
21148
21149            println!("world");
21150        }"#
21151    .unindent();
21152
21153    cx.set_state(
21154        &r#"
21155        use some::mod1;
21156        use some::mod2;
21157
21158        const A: u32 = 42;
21159        const B: u32 = 42;
21160        const C: u32 = 43ˇ
21161        const D: u32 = 42;
21162
21163
21164        fn main() {
21165            println!("hello");
21166
21167            println!("world");
21168        }"#
21169        .unindent(),
21170    );
21171
21172    cx.set_head_text(&diff_base);
21173    executor.run_until_parked();
21174    cx.update_editor(|editor, window, cx| {
21175        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21176    });
21177    executor.run_until_parked();
21178
21179    cx.assert_state_with_diff(
21180        r#"
21181        use some::mod1;
21182        use some::mod2;
21183
21184        const A: u32 = 42;
21185        const B: u32 = 42;
21186      - const C: u32 = 42;
21187      + const C: u32 = 43ˇ
21188        const D: u32 = 42;
21189
21190
21191        fn main() {
21192            println!("hello");
21193
21194            println!("world");
21195        }"#
21196        .unindent(),
21197    );
21198
21199    cx.update_editor(|editor, window, cx| {
21200        editor.handle_input("\nnew_line\n", window, cx);
21201    });
21202    executor.run_until_parked();
21203
21204    cx.assert_state_with_diff(
21205        r#"
21206        use some::mod1;
21207        use some::mod2;
21208
21209        const A: u32 = 42;
21210        const B: u32 = 42;
21211      - const C: u32 = 42;
21212      + const C: u32 = 43
21213      + new_line
21214      + ˇ
21215        const D: u32 = 42;
21216
21217
21218        fn main() {
21219            println!("hello");
21220
21221            println!("world");
21222        }"#
21223        .unindent(),
21224    );
21225}
21226
21227#[gpui::test]
21228async fn test_stage_and_unstage_added_file_hunk(
21229    executor: BackgroundExecutor,
21230    cx: &mut TestAppContext,
21231) {
21232    init_test(cx, |_| {});
21233
21234    let mut cx = EditorTestContext::new(cx).await;
21235    cx.update_editor(|editor, _, cx| {
21236        editor.set_expand_all_diff_hunks(cx);
21237    });
21238
21239    let working_copy = r#"
21240            ˇfn main() {
21241                println!("hello, world!");
21242            }
21243        "#
21244    .unindent();
21245
21246    cx.set_state(&working_copy);
21247    executor.run_until_parked();
21248
21249    cx.assert_state_with_diff(
21250        r#"
21251            + ˇfn main() {
21252            +     println!("hello, world!");
21253            + }
21254        "#
21255        .unindent(),
21256    );
21257    cx.assert_index_text(None);
21258
21259    cx.update_editor(|editor, window, cx| {
21260        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21261    });
21262    executor.run_until_parked();
21263    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21264    cx.assert_state_with_diff(
21265        r#"
21266            + ˇfn main() {
21267            +     println!("hello, world!");
21268            + }
21269        "#
21270        .unindent(),
21271    );
21272
21273    cx.update_editor(|editor, window, cx| {
21274        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21275    });
21276    executor.run_until_parked();
21277    cx.assert_index_text(None);
21278}
21279
21280async fn setup_indent_guides_editor(
21281    text: &str,
21282    cx: &mut TestAppContext,
21283) -> (BufferId, EditorTestContext) {
21284    init_test(cx, |_| {});
21285
21286    let mut cx = EditorTestContext::new(cx).await;
21287
21288    let buffer_id = cx.update_editor(|editor, window, cx| {
21289        editor.set_text(text, window, cx);
21290        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21291
21292        buffer_ids[0]
21293    });
21294
21295    (buffer_id, cx)
21296}
21297
21298fn assert_indent_guides(
21299    range: Range<u32>,
21300    expected: Vec<IndentGuide>,
21301    active_indices: Option<Vec<usize>>,
21302    cx: &mut EditorTestContext,
21303) {
21304    let indent_guides = cx.update_editor(|editor, window, cx| {
21305        let snapshot = editor.snapshot(window, cx).display_snapshot;
21306        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21307            editor,
21308            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21309            true,
21310            &snapshot,
21311            cx,
21312        );
21313
21314        indent_guides.sort_by(|a, b| {
21315            a.depth.cmp(&b.depth).then(
21316                a.start_row
21317                    .cmp(&b.start_row)
21318                    .then(a.end_row.cmp(&b.end_row)),
21319            )
21320        });
21321        indent_guides
21322    });
21323
21324    if let Some(expected) = active_indices {
21325        let active_indices = cx.update_editor(|editor, window, cx| {
21326            let snapshot = editor.snapshot(window, cx).display_snapshot;
21327            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21328        });
21329
21330        assert_eq!(
21331            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21332            expected,
21333            "Active indent guide indices do not match"
21334        );
21335    }
21336
21337    assert_eq!(indent_guides, expected, "Indent guides do not match");
21338}
21339
21340fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21341    IndentGuide {
21342        buffer_id,
21343        start_row: MultiBufferRow(start_row),
21344        end_row: MultiBufferRow(end_row),
21345        depth,
21346        tab_size: 4,
21347        settings: IndentGuideSettings {
21348            enabled: true,
21349            line_width: 1,
21350            active_line_width: 1,
21351            coloring: IndentGuideColoring::default(),
21352            background_coloring: IndentGuideBackgroundColoring::default(),
21353        },
21354    }
21355}
21356
21357#[gpui::test]
21358async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21359    let (buffer_id, mut cx) = setup_indent_guides_editor(
21360        &"
21361        fn main() {
21362            let a = 1;
21363        }"
21364        .unindent(),
21365        cx,
21366    )
21367    .await;
21368
21369    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21370}
21371
21372#[gpui::test]
21373async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21374    let (buffer_id, mut cx) = setup_indent_guides_editor(
21375        &"
21376        fn main() {
21377            let a = 1;
21378            let b = 2;
21379        }"
21380        .unindent(),
21381        cx,
21382    )
21383    .await;
21384
21385    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21386}
21387
21388#[gpui::test]
21389async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21390    let (buffer_id, mut cx) = setup_indent_guides_editor(
21391        &"
21392        fn main() {
21393            let a = 1;
21394            if a == 3 {
21395                let b = 2;
21396            } else {
21397                let c = 3;
21398            }
21399        }"
21400        .unindent(),
21401        cx,
21402    )
21403    .await;
21404
21405    assert_indent_guides(
21406        0..8,
21407        vec![
21408            indent_guide(buffer_id, 1, 6, 0),
21409            indent_guide(buffer_id, 3, 3, 1),
21410            indent_guide(buffer_id, 5, 5, 1),
21411        ],
21412        None,
21413        &mut cx,
21414    );
21415}
21416
21417#[gpui::test]
21418async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21419    let (buffer_id, mut cx) = setup_indent_guides_editor(
21420        &"
21421        fn main() {
21422            let a = 1;
21423                let b = 2;
21424            let c = 3;
21425        }"
21426        .unindent(),
21427        cx,
21428    )
21429    .await;
21430
21431    assert_indent_guides(
21432        0..5,
21433        vec![
21434            indent_guide(buffer_id, 1, 3, 0),
21435            indent_guide(buffer_id, 2, 2, 1),
21436        ],
21437        None,
21438        &mut cx,
21439    );
21440}
21441
21442#[gpui::test]
21443async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21444    let (buffer_id, mut cx) = setup_indent_guides_editor(
21445        &"
21446        fn main() {
21447            let a = 1;
21448
21449            let c = 3;
21450        }"
21451        .unindent(),
21452        cx,
21453    )
21454    .await;
21455
21456    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21457}
21458
21459#[gpui::test]
21460async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21461    let (buffer_id, mut cx) = setup_indent_guides_editor(
21462        &"
21463        fn main() {
21464            let a = 1;
21465
21466            let c = 3;
21467
21468            if a == 3 {
21469                let b = 2;
21470            } else {
21471                let c = 3;
21472            }
21473        }"
21474        .unindent(),
21475        cx,
21476    )
21477    .await;
21478
21479    assert_indent_guides(
21480        0..11,
21481        vec![
21482            indent_guide(buffer_id, 1, 9, 0),
21483            indent_guide(buffer_id, 6, 6, 1),
21484            indent_guide(buffer_id, 8, 8, 1),
21485        ],
21486        None,
21487        &mut cx,
21488    );
21489}
21490
21491#[gpui::test]
21492async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21493    let (buffer_id, mut cx) = setup_indent_guides_editor(
21494        &"
21495        fn main() {
21496            let a = 1;
21497
21498            let c = 3;
21499
21500            if a == 3 {
21501                let b = 2;
21502            } else {
21503                let c = 3;
21504            }
21505        }"
21506        .unindent(),
21507        cx,
21508    )
21509    .await;
21510
21511    assert_indent_guides(
21512        1..11,
21513        vec![
21514            indent_guide(buffer_id, 1, 9, 0),
21515            indent_guide(buffer_id, 6, 6, 1),
21516            indent_guide(buffer_id, 8, 8, 1),
21517        ],
21518        None,
21519        &mut cx,
21520    );
21521}
21522
21523#[gpui::test]
21524async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21525    let (buffer_id, mut cx) = setup_indent_guides_editor(
21526        &"
21527        fn main() {
21528            let a = 1;
21529
21530            let c = 3;
21531
21532            if a == 3 {
21533                let b = 2;
21534            } else {
21535                let c = 3;
21536            }
21537        }"
21538        .unindent(),
21539        cx,
21540    )
21541    .await;
21542
21543    assert_indent_guides(
21544        1..10,
21545        vec![
21546            indent_guide(buffer_id, 1, 9, 0),
21547            indent_guide(buffer_id, 6, 6, 1),
21548            indent_guide(buffer_id, 8, 8, 1),
21549        ],
21550        None,
21551        &mut cx,
21552    );
21553}
21554
21555#[gpui::test]
21556async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21557    let (buffer_id, mut cx) = setup_indent_guides_editor(
21558        &"
21559        fn main() {
21560            if a {
21561                b(
21562                    c,
21563                    d,
21564                )
21565            } else {
21566                e(
21567                    f
21568                )
21569            }
21570        }"
21571        .unindent(),
21572        cx,
21573    )
21574    .await;
21575
21576    assert_indent_guides(
21577        0..11,
21578        vec![
21579            indent_guide(buffer_id, 1, 10, 0),
21580            indent_guide(buffer_id, 2, 5, 1),
21581            indent_guide(buffer_id, 7, 9, 1),
21582            indent_guide(buffer_id, 3, 4, 2),
21583            indent_guide(buffer_id, 8, 8, 2),
21584        ],
21585        None,
21586        &mut cx,
21587    );
21588
21589    cx.update_editor(|editor, window, cx| {
21590        editor.fold_at(MultiBufferRow(2), window, cx);
21591        assert_eq!(
21592            editor.display_text(cx),
21593            "
21594            fn main() {
21595                if a {
21596                    b(⋯
21597                    )
21598                } else {
21599                    e(
21600                        f
21601                    )
21602                }
21603            }"
21604            .unindent()
21605        );
21606    });
21607
21608    assert_indent_guides(
21609        0..11,
21610        vec![
21611            indent_guide(buffer_id, 1, 10, 0),
21612            indent_guide(buffer_id, 2, 5, 1),
21613            indent_guide(buffer_id, 7, 9, 1),
21614            indent_guide(buffer_id, 8, 8, 2),
21615        ],
21616        None,
21617        &mut cx,
21618    );
21619}
21620
21621#[gpui::test]
21622async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21623    let (buffer_id, mut cx) = setup_indent_guides_editor(
21624        &"
21625        block1
21626            block2
21627                block3
21628                    block4
21629            block2
21630        block1
21631        block1"
21632            .unindent(),
21633        cx,
21634    )
21635    .await;
21636
21637    assert_indent_guides(
21638        1..10,
21639        vec![
21640            indent_guide(buffer_id, 1, 4, 0),
21641            indent_guide(buffer_id, 2, 3, 1),
21642            indent_guide(buffer_id, 3, 3, 2),
21643        ],
21644        None,
21645        &mut cx,
21646    );
21647}
21648
21649#[gpui::test]
21650async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21651    let (buffer_id, mut cx) = setup_indent_guides_editor(
21652        &"
21653        block1
21654            block2
21655                block3
21656
21657        block1
21658        block1"
21659            .unindent(),
21660        cx,
21661    )
21662    .await;
21663
21664    assert_indent_guides(
21665        0..6,
21666        vec![
21667            indent_guide(buffer_id, 1, 2, 0),
21668            indent_guide(buffer_id, 2, 2, 1),
21669        ],
21670        None,
21671        &mut cx,
21672    );
21673}
21674
21675#[gpui::test]
21676async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21677    let (buffer_id, mut cx) = setup_indent_guides_editor(
21678        &"
21679        function component() {
21680        \treturn (
21681        \t\t\t
21682        \t\t<div>
21683        \t\t\t<abc></abc>
21684        \t\t</div>
21685        \t)
21686        }"
21687        .unindent(),
21688        cx,
21689    )
21690    .await;
21691
21692    assert_indent_guides(
21693        0..8,
21694        vec![
21695            indent_guide(buffer_id, 1, 6, 0),
21696            indent_guide(buffer_id, 2, 5, 1),
21697            indent_guide(buffer_id, 4, 4, 2),
21698        ],
21699        None,
21700        &mut cx,
21701    );
21702}
21703
21704#[gpui::test]
21705async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21706    let (buffer_id, mut cx) = setup_indent_guides_editor(
21707        &"
21708        function component() {
21709        \treturn (
21710        \t
21711        \t\t<div>
21712        \t\t\t<abc></abc>
21713        \t\t</div>
21714        \t)
21715        }"
21716        .unindent(),
21717        cx,
21718    )
21719    .await;
21720
21721    assert_indent_guides(
21722        0..8,
21723        vec![
21724            indent_guide(buffer_id, 1, 6, 0),
21725            indent_guide(buffer_id, 2, 5, 1),
21726            indent_guide(buffer_id, 4, 4, 2),
21727        ],
21728        None,
21729        &mut cx,
21730    );
21731}
21732
21733#[gpui::test]
21734async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21735    let (buffer_id, mut cx) = setup_indent_guides_editor(
21736        &"
21737        block1
21738
21739
21740
21741            block2
21742        "
21743        .unindent(),
21744        cx,
21745    )
21746    .await;
21747
21748    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21749}
21750
21751#[gpui::test]
21752async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21753    let (buffer_id, mut cx) = setup_indent_guides_editor(
21754        &"
21755        def a:
21756        \tb = 3
21757        \tif True:
21758        \t\tc = 4
21759        \t\td = 5
21760        \tprint(b)
21761        "
21762        .unindent(),
21763        cx,
21764    )
21765    .await;
21766
21767    assert_indent_guides(
21768        0..6,
21769        vec![
21770            indent_guide(buffer_id, 1, 5, 0),
21771            indent_guide(buffer_id, 3, 4, 1),
21772        ],
21773        None,
21774        &mut cx,
21775    );
21776}
21777
21778#[gpui::test]
21779async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21780    let (buffer_id, mut cx) = setup_indent_guides_editor(
21781        &"
21782    fn main() {
21783        let a = 1;
21784    }"
21785        .unindent(),
21786        cx,
21787    )
21788    .await;
21789
21790    cx.update_editor(|editor, window, cx| {
21791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21792            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21793        });
21794    });
21795
21796    assert_indent_guides(
21797        0..3,
21798        vec![indent_guide(buffer_id, 1, 1, 0)],
21799        Some(vec![0]),
21800        &mut cx,
21801    );
21802}
21803
21804#[gpui::test]
21805async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21806    let (buffer_id, mut cx) = setup_indent_guides_editor(
21807        &"
21808    fn main() {
21809        if 1 == 2 {
21810            let a = 1;
21811        }
21812    }"
21813        .unindent(),
21814        cx,
21815    )
21816    .await;
21817
21818    cx.update_editor(|editor, window, cx| {
21819        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21820            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21821        });
21822    });
21823
21824    assert_indent_guides(
21825        0..4,
21826        vec![
21827            indent_guide(buffer_id, 1, 3, 0),
21828            indent_guide(buffer_id, 2, 2, 1),
21829        ],
21830        Some(vec![1]),
21831        &mut cx,
21832    );
21833
21834    cx.update_editor(|editor, window, cx| {
21835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21836            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21837        });
21838    });
21839
21840    assert_indent_guides(
21841        0..4,
21842        vec![
21843            indent_guide(buffer_id, 1, 3, 0),
21844            indent_guide(buffer_id, 2, 2, 1),
21845        ],
21846        Some(vec![1]),
21847        &mut cx,
21848    );
21849
21850    cx.update_editor(|editor, window, cx| {
21851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21852            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21853        });
21854    });
21855
21856    assert_indent_guides(
21857        0..4,
21858        vec![
21859            indent_guide(buffer_id, 1, 3, 0),
21860            indent_guide(buffer_id, 2, 2, 1),
21861        ],
21862        Some(vec![0]),
21863        &mut cx,
21864    );
21865}
21866
21867#[gpui::test]
21868async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21869    let (buffer_id, mut cx) = setup_indent_guides_editor(
21870        &"
21871    fn main() {
21872        let a = 1;
21873
21874        let b = 2;
21875    }"
21876        .unindent(),
21877        cx,
21878    )
21879    .await;
21880
21881    cx.update_editor(|editor, window, cx| {
21882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21883            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21884        });
21885    });
21886
21887    assert_indent_guides(
21888        0..5,
21889        vec![indent_guide(buffer_id, 1, 3, 0)],
21890        Some(vec![0]),
21891        &mut cx,
21892    );
21893}
21894
21895#[gpui::test]
21896async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21897    let (buffer_id, mut cx) = setup_indent_guides_editor(
21898        &"
21899    def m:
21900        a = 1
21901        pass"
21902            .unindent(),
21903        cx,
21904    )
21905    .await;
21906
21907    cx.update_editor(|editor, window, cx| {
21908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21909            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21910        });
21911    });
21912
21913    assert_indent_guides(
21914        0..3,
21915        vec![indent_guide(buffer_id, 1, 2, 0)],
21916        Some(vec![0]),
21917        &mut cx,
21918    );
21919}
21920
21921#[gpui::test]
21922async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21923    init_test(cx, |_| {});
21924    let mut cx = EditorTestContext::new(cx).await;
21925    let text = indoc! {
21926        "
21927        impl A {
21928            fn b() {
21929                0;
21930                3;
21931                5;
21932                6;
21933                7;
21934            }
21935        }
21936        "
21937    };
21938    let base_text = indoc! {
21939        "
21940        impl A {
21941            fn b() {
21942                0;
21943                1;
21944                2;
21945                3;
21946                4;
21947            }
21948            fn c() {
21949                5;
21950                6;
21951                7;
21952            }
21953        }
21954        "
21955    };
21956
21957    cx.update_editor(|editor, window, cx| {
21958        editor.set_text(text, window, cx);
21959
21960        editor.buffer().update(cx, |multibuffer, cx| {
21961            let buffer = multibuffer.as_singleton().unwrap();
21962            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21963
21964            multibuffer.set_all_diff_hunks_expanded(cx);
21965            multibuffer.add_diff(diff, cx);
21966
21967            buffer.read(cx).remote_id()
21968        })
21969    });
21970    cx.run_until_parked();
21971
21972    cx.assert_state_with_diff(
21973        indoc! { "
21974          impl A {
21975              fn b() {
21976                  0;
21977        -         1;
21978        -         2;
21979                  3;
21980        -         4;
21981        -     }
21982        -     fn c() {
21983                  5;
21984                  6;
21985                  7;
21986              }
21987          }
21988          ˇ"
21989        }
21990        .to_string(),
21991    );
21992
21993    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21994        editor
21995            .snapshot(window, cx)
21996            .buffer_snapshot()
21997            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21998            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21999            .collect::<Vec<_>>()
22000    });
22001    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22002    assert_eq!(
22003        actual_guides,
22004        vec![
22005            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22006            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22007            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22008        ]
22009    );
22010}
22011
22012#[gpui::test]
22013async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22014    init_test(cx, |_| {});
22015    let mut cx = EditorTestContext::new(cx).await;
22016
22017    let diff_base = r#"
22018        a
22019        b
22020        c
22021        "#
22022    .unindent();
22023
22024    cx.set_state(
22025        &r#"
22026        ˇA
22027        b
22028        C
22029        "#
22030        .unindent(),
22031    );
22032    cx.set_head_text(&diff_base);
22033    cx.update_editor(|editor, window, cx| {
22034        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22035    });
22036    executor.run_until_parked();
22037
22038    let both_hunks_expanded = r#"
22039        - a
22040        + ˇA
22041          b
22042        - c
22043        + C
22044        "#
22045    .unindent();
22046
22047    cx.assert_state_with_diff(both_hunks_expanded.clone());
22048
22049    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22050        let snapshot = editor.snapshot(window, cx);
22051        let hunks = editor
22052            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22053            .collect::<Vec<_>>();
22054        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22055        hunks
22056            .into_iter()
22057            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22058            .collect::<Vec<_>>()
22059    });
22060    assert_eq!(hunk_ranges.len(), 2);
22061
22062    cx.update_editor(|editor, _, cx| {
22063        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22064    });
22065    executor.run_until_parked();
22066
22067    let second_hunk_expanded = r#"
22068          ˇA
22069          b
22070        - c
22071        + C
22072        "#
22073    .unindent();
22074
22075    cx.assert_state_with_diff(second_hunk_expanded);
22076
22077    cx.update_editor(|editor, _, cx| {
22078        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22079    });
22080    executor.run_until_parked();
22081
22082    cx.assert_state_with_diff(both_hunks_expanded.clone());
22083
22084    cx.update_editor(|editor, _, cx| {
22085        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22086    });
22087    executor.run_until_parked();
22088
22089    let first_hunk_expanded = r#"
22090        - a
22091        + ˇA
22092          b
22093          C
22094        "#
22095    .unindent();
22096
22097    cx.assert_state_with_diff(first_hunk_expanded);
22098
22099    cx.update_editor(|editor, _, cx| {
22100        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22101    });
22102    executor.run_until_parked();
22103
22104    cx.assert_state_with_diff(both_hunks_expanded);
22105
22106    cx.set_state(
22107        &r#"
22108        ˇA
22109        b
22110        "#
22111        .unindent(),
22112    );
22113    cx.run_until_parked();
22114
22115    // TODO this cursor position seems bad
22116    cx.assert_state_with_diff(
22117        r#"
22118        - ˇa
22119        + A
22120          b
22121        "#
22122        .unindent(),
22123    );
22124
22125    cx.update_editor(|editor, window, cx| {
22126        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22127    });
22128
22129    cx.assert_state_with_diff(
22130        r#"
22131            - ˇa
22132            + A
22133              b
22134            - c
22135            "#
22136        .unindent(),
22137    );
22138
22139    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22140        let snapshot = editor.snapshot(window, cx);
22141        let hunks = editor
22142            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22143            .collect::<Vec<_>>();
22144        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22145        hunks
22146            .into_iter()
22147            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22148            .collect::<Vec<_>>()
22149    });
22150    assert_eq!(hunk_ranges.len(), 2);
22151
22152    cx.update_editor(|editor, _, cx| {
22153        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22154    });
22155    executor.run_until_parked();
22156
22157    cx.assert_state_with_diff(
22158        r#"
22159        - ˇa
22160        + A
22161          b
22162        "#
22163        .unindent(),
22164    );
22165}
22166
22167#[gpui::test]
22168async fn test_toggle_deletion_hunk_at_start_of_file(
22169    executor: BackgroundExecutor,
22170    cx: &mut TestAppContext,
22171) {
22172    init_test(cx, |_| {});
22173    let mut cx = EditorTestContext::new(cx).await;
22174
22175    let diff_base = r#"
22176        a
22177        b
22178        c
22179        "#
22180    .unindent();
22181
22182    cx.set_state(
22183        &r#"
22184        ˇb
22185        c
22186        "#
22187        .unindent(),
22188    );
22189    cx.set_head_text(&diff_base);
22190    cx.update_editor(|editor, window, cx| {
22191        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22192    });
22193    executor.run_until_parked();
22194
22195    let hunk_expanded = r#"
22196        - a
22197          ˇb
22198          c
22199        "#
22200    .unindent();
22201
22202    cx.assert_state_with_diff(hunk_expanded.clone());
22203
22204    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22205        let snapshot = editor.snapshot(window, cx);
22206        let hunks = editor
22207            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22208            .collect::<Vec<_>>();
22209        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22210        hunks
22211            .into_iter()
22212            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22213            .collect::<Vec<_>>()
22214    });
22215    assert_eq!(hunk_ranges.len(), 1);
22216
22217    cx.update_editor(|editor, _, cx| {
22218        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22219    });
22220    executor.run_until_parked();
22221
22222    let hunk_collapsed = r#"
22223          ˇb
22224          c
22225        "#
22226    .unindent();
22227
22228    cx.assert_state_with_diff(hunk_collapsed);
22229
22230    cx.update_editor(|editor, _, cx| {
22231        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22232    });
22233    executor.run_until_parked();
22234
22235    cx.assert_state_with_diff(hunk_expanded);
22236}
22237
22238#[gpui::test]
22239async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22240    executor: BackgroundExecutor,
22241    cx: &mut TestAppContext,
22242) {
22243    init_test(cx, |_| {});
22244    let mut cx = EditorTestContext::new(cx).await;
22245
22246    cx.set_state("ˇnew\nsecond\nthird\n");
22247    cx.set_head_text("old\nsecond\nthird\n");
22248    cx.update_editor(|editor, window, cx| {
22249        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22250    });
22251    executor.run_until_parked();
22252    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22253
22254    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22255    cx.update_editor(|editor, window, cx| {
22256        let snapshot = editor.snapshot(window, cx);
22257        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22258        let hunks = editor
22259            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22260            .collect::<Vec<_>>();
22261        assert_eq!(hunks.len(), 1);
22262        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22263        editor.toggle_single_diff_hunk(hunk_range, cx)
22264    });
22265    executor.run_until_parked();
22266    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22267
22268    // Keep the editor scrolled to the top so the full hunk remains visible.
22269    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22270}
22271
22272#[gpui::test]
22273async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22274    init_test(cx, |_| {});
22275
22276    let fs = FakeFs::new(cx.executor());
22277    fs.insert_tree(
22278        path!("/test"),
22279        json!({
22280            ".git": {},
22281            "file-1": "ONE\n",
22282            "file-2": "TWO\n",
22283            "file-3": "THREE\n",
22284        }),
22285    )
22286    .await;
22287
22288    fs.set_head_for_repo(
22289        path!("/test/.git").as_ref(),
22290        &[
22291            ("file-1", "one\n".into()),
22292            ("file-2", "two\n".into()),
22293            ("file-3", "three\n".into()),
22294        ],
22295        "deadbeef",
22296    );
22297
22298    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22299    let mut buffers = vec![];
22300    for i in 1..=3 {
22301        let buffer = project
22302            .update(cx, |project, cx| {
22303                let path = format!(path!("/test/file-{}"), i);
22304                project.open_local_buffer(path, cx)
22305            })
22306            .await
22307            .unwrap();
22308        buffers.push(buffer);
22309    }
22310
22311    let multibuffer = cx.new(|cx| {
22312        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22313        multibuffer.set_all_diff_hunks_expanded(cx);
22314        for buffer in &buffers {
22315            let snapshot = buffer.read(cx).snapshot();
22316            multibuffer.set_excerpts_for_path(
22317                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22318                buffer.clone(),
22319                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22320                2,
22321                cx,
22322            );
22323        }
22324        multibuffer
22325    });
22326
22327    let editor = cx.add_window(|window, cx| {
22328        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22329    });
22330    cx.run_until_parked();
22331
22332    let snapshot = editor
22333        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22334        .unwrap();
22335    let hunks = snapshot
22336        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22337        .map(|hunk| match hunk {
22338            DisplayDiffHunk::Unfolded {
22339                display_row_range, ..
22340            } => display_row_range,
22341            DisplayDiffHunk::Folded { .. } => unreachable!(),
22342        })
22343        .collect::<Vec<_>>();
22344    assert_eq!(
22345        hunks,
22346        [
22347            DisplayRow(2)..DisplayRow(4),
22348            DisplayRow(7)..DisplayRow(9),
22349            DisplayRow(12)..DisplayRow(14),
22350        ]
22351    );
22352}
22353
22354#[gpui::test]
22355async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22356    init_test(cx, |_| {});
22357
22358    let mut cx = EditorTestContext::new(cx).await;
22359    cx.set_head_text(indoc! { "
22360        one
22361        two
22362        three
22363        four
22364        five
22365        "
22366    });
22367    cx.set_index_text(indoc! { "
22368        one
22369        two
22370        three
22371        four
22372        five
22373        "
22374    });
22375    cx.set_state(indoc! {"
22376        one
22377        TWO
22378        ˇTHREE
22379        FOUR
22380        five
22381    "});
22382    cx.run_until_parked();
22383    cx.update_editor(|editor, window, cx| {
22384        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22385    });
22386    cx.run_until_parked();
22387    cx.assert_index_text(Some(indoc! {"
22388        one
22389        TWO
22390        THREE
22391        FOUR
22392        five
22393    "}));
22394    cx.set_state(indoc! { "
22395        one
22396        TWO
22397        ˇTHREE-HUNDRED
22398        FOUR
22399        five
22400    "});
22401    cx.run_until_parked();
22402    cx.update_editor(|editor, window, cx| {
22403        let snapshot = editor.snapshot(window, cx);
22404        let hunks = editor
22405            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22406            .collect::<Vec<_>>();
22407        assert_eq!(hunks.len(), 1);
22408        assert_eq!(
22409            hunks[0].status(),
22410            DiffHunkStatus {
22411                kind: DiffHunkStatusKind::Modified,
22412                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22413            }
22414        );
22415
22416        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22417    });
22418    cx.run_until_parked();
22419    cx.assert_index_text(Some(indoc! {"
22420        one
22421        TWO
22422        THREE-HUNDRED
22423        FOUR
22424        five
22425    "}));
22426}
22427
22428#[gpui::test]
22429fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22430    init_test(cx, |_| {});
22431
22432    let editor = cx.add_window(|window, cx| {
22433        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22434        build_editor(buffer, window, cx)
22435    });
22436
22437    let render_args = Arc::new(Mutex::new(None));
22438    let snapshot = editor
22439        .update(cx, |editor, window, cx| {
22440            let snapshot = editor.buffer().read(cx).snapshot(cx);
22441            let range =
22442                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22443
22444            struct RenderArgs {
22445                row: MultiBufferRow,
22446                folded: bool,
22447                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22448            }
22449
22450            let crease = Crease::inline(
22451                range,
22452                FoldPlaceholder::test(),
22453                {
22454                    let toggle_callback = render_args.clone();
22455                    move |row, folded, callback, _window, _cx| {
22456                        *toggle_callback.lock() = Some(RenderArgs {
22457                            row,
22458                            folded,
22459                            callback,
22460                        });
22461                        div()
22462                    }
22463                },
22464                |_row, _folded, _window, _cx| div(),
22465            );
22466
22467            editor.insert_creases(Some(crease), cx);
22468            let snapshot = editor.snapshot(window, cx);
22469            let _div =
22470                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22471            snapshot
22472        })
22473        .unwrap();
22474
22475    let render_args = render_args.lock().take().unwrap();
22476    assert_eq!(render_args.row, MultiBufferRow(1));
22477    assert!(!render_args.folded);
22478    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22479
22480    cx.update_window(*editor, |_, window, cx| {
22481        (render_args.callback)(true, window, cx)
22482    })
22483    .unwrap();
22484    let snapshot = editor
22485        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22486        .unwrap();
22487    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22488
22489    cx.update_window(*editor, |_, window, cx| {
22490        (render_args.callback)(false, window, cx)
22491    })
22492    .unwrap();
22493    let snapshot = editor
22494        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22495        .unwrap();
22496    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22497}
22498
22499#[gpui::test]
22500async fn test_input_text(cx: &mut TestAppContext) {
22501    init_test(cx, |_| {});
22502    let mut cx = EditorTestContext::new(cx).await;
22503
22504    cx.set_state(
22505        &r#"ˇone
22506        two
22507
22508        three
22509        fourˇ
22510        five
22511
22512        siˇx"#
22513            .unindent(),
22514    );
22515
22516    cx.dispatch_action(HandleInput(String::new()));
22517    cx.assert_editor_state(
22518        &r#"ˇone
22519        two
22520
22521        three
22522        fourˇ
22523        five
22524
22525        siˇx"#
22526            .unindent(),
22527    );
22528
22529    cx.dispatch_action(HandleInput("AAAA".to_string()));
22530    cx.assert_editor_state(
22531        &r#"AAAAˇone
22532        two
22533
22534        three
22535        fourAAAAˇ
22536        five
22537
22538        siAAAAˇx"#
22539            .unindent(),
22540    );
22541}
22542
22543#[gpui::test]
22544async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22545    init_test(cx, |_| {});
22546
22547    let mut cx = EditorTestContext::new(cx).await;
22548    cx.set_state(
22549        r#"let foo = 1;
22550let foo = 2;
22551let foo = 3;
22552let fooˇ = 4;
22553let foo = 5;
22554let foo = 6;
22555let foo = 7;
22556let foo = 8;
22557let foo = 9;
22558let foo = 10;
22559let foo = 11;
22560let foo = 12;
22561let foo = 13;
22562let foo = 14;
22563let foo = 15;"#,
22564    );
22565
22566    cx.update_editor(|e, window, cx| {
22567        assert_eq!(
22568            e.next_scroll_position,
22569            NextScrollCursorCenterTopBottom::Center,
22570            "Default next scroll direction is center",
22571        );
22572
22573        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22574        assert_eq!(
22575            e.next_scroll_position,
22576            NextScrollCursorCenterTopBottom::Top,
22577            "After center, next scroll direction should be top",
22578        );
22579
22580        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22581        assert_eq!(
22582            e.next_scroll_position,
22583            NextScrollCursorCenterTopBottom::Bottom,
22584            "After top, next scroll direction should be bottom",
22585        );
22586
22587        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22588        assert_eq!(
22589            e.next_scroll_position,
22590            NextScrollCursorCenterTopBottom::Center,
22591            "After bottom, scrolling should start over",
22592        );
22593
22594        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22595        assert_eq!(
22596            e.next_scroll_position,
22597            NextScrollCursorCenterTopBottom::Top,
22598            "Scrolling continues if retriggered fast enough"
22599        );
22600    });
22601
22602    cx.executor()
22603        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22604    cx.executor().run_until_parked();
22605    cx.update_editor(|e, _, _| {
22606        assert_eq!(
22607            e.next_scroll_position,
22608            NextScrollCursorCenterTopBottom::Center,
22609            "If scrolling is not triggered fast enough, it should reset"
22610        );
22611    });
22612}
22613
22614#[gpui::test]
22615async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22616    init_test(cx, |_| {});
22617    let mut cx = EditorLspTestContext::new_rust(
22618        lsp::ServerCapabilities {
22619            definition_provider: Some(lsp::OneOf::Left(true)),
22620            references_provider: Some(lsp::OneOf::Left(true)),
22621            ..lsp::ServerCapabilities::default()
22622        },
22623        cx,
22624    )
22625    .await;
22626
22627    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22628        let go_to_definition = cx
22629            .lsp
22630            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22631                move |params, _| async move {
22632                    if empty_go_to_definition {
22633                        Ok(None)
22634                    } else {
22635                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22636                            uri: params.text_document_position_params.text_document.uri,
22637                            range: lsp::Range::new(
22638                                lsp::Position::new(4, 3),
22639                                lsp::Position::new(4, 6),
22640                            ),
22641                        })))
22642                    }
22643                },
22644            );
22645        let references = cx
22646            .lsp
22647            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22648                Ok(Some(vec![lsp::Location {
22649                    uri: params.text_document_position.text_document.uri,
22650                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22651                }]))
22652            });
22653        (go_to_definition, references)
22654    };
22655
22656    cx.set_state(
22657        &r#"fn one() {
22658            let mut a = ˇtwo();
22659        }
22660
22661        fn two() {}"#
22662            .unindent(),
22663    );
22664    set_up_lsp_handlers(false, &mut cx);
22665    let navigated = cx
22666        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22667        .await
22668        .expect("Failed to navigate to definition");
22669    assert_eq!(
22670        navigated,
22671        Navigated::Yes,
22672        "Should have navigated to definition from the GetDefinition response"
22673    );
22674    cx.assert_editor_state(
22675        &r#"fn one() {
22676            let mut a = two();
22677        }
22678
22679        fn «twoˇ»() {}"#
22680            .unindent(),
22681    );
22682
22683    let editors = cx.update_workspace(|workspace, _, cx| {
22684        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22685    });
22686    cx.update_editor(|_, _, test_editor_cx| {
22687        assert_eq!(
22688            editors.len(),
22689            1,
22690            "Initially, only one, test, editor should be open in the workspace"
22691        );
22692        assert_eq!(
22693            test_editor_cx.entity(),
22694            editors.last().expect("Asserted len is 1").clone()
22695        );
22696    });
22697
22698    set_up_lsp_handlers(true, &mut cx);
22699    let navigated = cx
22700        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22701        .await
22702        .expect("Failed to navigate to lookup references");
22703    assert_eq!(
22704        navigated,
22705        Navigated::Yes,
22706        "Should have navigated to references as a fallback after empty GoToDefinition response"
22707    );
22708    // We should not change the selections in the existing file,
22709    // if opening another milti buffer with the references
22710    cx.assert_editor_state(
22711        &r#"fn one() {
22712            let mut a = two();
22713        }
22714
22715        fn «twoˇ»() {}"#
22716            .unindent(),
22717    );
22718    let editors = cx.update_workspace(|workspace, _, cx| {
22719        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22720    });
22721    cx.update_editor(|_, _, test_editor_cx| {
22722        assert_eq!(
22723            editors.len(),
22724            2,
22725            "After falling back to references search, we open a new editor with the results"
22726        );
22727        let references_fallback_text = editors
22728            .into_iter()
22729            .find(|new_editor| *new_editor != test_editor_cx.entity())
22730            .expect("Should have one non-test editor now")
22731            .read(test_editor_cx)
22732            .text(test_editor_cx);
22733        assert_eq!(
22734            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22735            "Should use the range from the references response and not the GoToDefinition one"
22736        );
22737    });
22738}
22739
22740#[gpui::test]
22741async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22742    init_test(cx, |_| {});
22743    cx.update(|cx| {
22744        let mut editor_settings = EditorSettings::get_global(cx).clone();
22745        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22746        EditorSettings::override_global(editor_settings, cx);
22747    });
22748    let mut cx = EditorLspTestContext::new_rust(
22749        lsp::ServerCapabilities {
22750            definition_provider: Some(lsp::OneOf::Left(true)),
22751            references_provider: Some(lsp::OneOf::Left(true)),
22752            ..lsp::ServerCapabilities::default()
22753        },
22754        cx,
22755    )
22756    .await;
22757    let original_state = r#"fn one() {
22758        let mut a = ˇtwo();
22759    }
22760
22761    fn two() {}"#
22762        .unindent();
22763    cx.set_state(&original_state);
22764
22765    let mut go_to_definition = cx
22766        .lsp
22767        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22768            move |_, _| async move { Ok(None) },
22769        );
22770    let _references = cx
22771        .lsp
22772        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22773            panic!("Should not call for references with no go to definition fallback")
22774        });
22775
22776    let navigated = cx
22777        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22778        .await
22779        .expect("Failed to navigate to lookup references");
22780    go_to_definition
22781        .next()
22782        .await
22783        .expect("Should have called the go_to_definition handler");
22784
22785    assert_eq!(
22786        navigated,
22787        Navigated::No,
22788        "Should have navigated to references as a fallback after empty GoToDefinition response"
22789    );
22790    cx.assert_editor_state(&original_state);
22791    let editors = cx.update_workspace(|workspace, _, cx| {
22792        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22793    });
22794    cx.update_editor(|_, _, _| {
22795        assert_eq!(
22796            editors.len(),
22797            1,
22798            "After unsuccessful fallback, no other editor should have been opened"
22799        );
22800    });
22801}
22802
22803#[gpui::test]
22804async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22805    init_test(cx, |_| {});
22806    let mut cx = EditorLspTestContext::new_rust(
22807        lsp::ServerCapabilities {
22808            references_provider: Some(lsp::OneOf::Left(true)),
22809            ..lsp::ServerCapabilities::default()
22810        },
22811        cx,
22812    )
22813    .await;
22814
22815    cx.set_state(
22816        &r#"
22817        fn one() {
22818            let mut a = two();
22819        }
22820
22821        fn ˇtwo() {}"#
22822            .unindent(),
22823    );
22824    cx.lsp
22825        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22826            Ok(Some(vec![
22827                lsp::Location {
22828                    uri: params.text_document_position.text_document.uri.clone(),
22829                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22830                },
22831                lsp::Location {
22832                    uri: params.text_document_position.text_document.uri,
22833                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22834                },
22835            ]))
22836        });
22837    let navigated = cx
22838        .update_editor(|editor, window, cx| {
22839            editor.find_all_references(&FindAllReferences::default(), window, cx)
22840        })
22841        .unwrap()
22842        .await
22843        .expect("Failed to navigate to references");
22844    assert_eq!(
22845        navigated,
22846        Navigated::Yes,
22847        "Should have navigated to references from the FindAllReferences response"
22848    );
22849    cx.assert_editor_state(
22850        &r#"fn one() {
22851            let mut a = two();
22852        }
22853
22854        fn ˇtwo() {}"#
22855            .unindent(),
22856    );
22857
22858    let editors = cx.update_workspace(|workspace, _, cx| {
22859        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22860    });
22861    cx.update_editor(|_, _, _| {
22862        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22863    });
22864
22865    cx.set_state(
22866        &r#"fn one() {
22867            let mut a = ˇtwo();
22868        }
22869
22870        fn two() {}"#
22871            .unindent(),
22872    );
22873    let navigated = cx
22874        .update_editor(|editor, window, cx| {
22875            editor.find_all_references(&FindAllReferences::default(), window, cx)
22876        })
22877        .unwrap()
22878        .await
22879        .expect("Failed to navigate to references");
22880    assert_eq!(
22881        navigated,
22882        Navigated::Yes,
22883        "Should have navigated to references from the FindAllReferences response"
22884    );
22885    cx.assert_editor_state(
22886        &r#"fn one() {
22887            let mut a = ˇtwo();
22888        }
22889
22890        fn two() {}"#
22891            .unindent(),
22892    );
22893    let editors = cx.update_workspace(|workspace, _, cx| {
22894        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22895    });
22896    cx.update_editor(|_, _, _| {
22897        assert_eq!(
22898            editors.len(),
22899            2,
22900            "should have re-used the previous multibuffer"
22901        );
22902    });
22903
22904    cx.set_state(
22905        &r#"fn one() {
22906            let mut a = ˇtwo();
22907        }
22908        fn three() {}
22909        fn two() {}"#
22910            .unindent(),
22911    );
22912    cx.lsp
22913        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22914            Ok(Some(vec![
22915                lsp::Location {
22916                    uri: params.text_document_position.text_document.uri.clone(),
22917                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22918                },
22919                lsp::Location {
22920                    uri: params.text_document_position.text_document.uri,
22921                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22922                },
22923            ]))
22924        });
22925    let navigated = cx
22926        .update_editor(|editor, window, cx| {
22927            editor.find_all_references(&FindAllReferences::default(), window, cx)
22928        })
22929        .unwrap()
22930        .await
22931        .expect("Failed to navigate to references");
22932    assert_eq!(
22933        navigated,
22934        Navigated::Yes,
22935        "Should have navigated to references from the FindAllReferences response"
22936    );
22937    cx.assert_editor_state(
22938        &r#"fn one() {
22939                let mut a = ˇtwo();
22940            }
22941            fn three() {}
22942            fn two() {}"#
22943            .unindent(),
22944    );
22945    let editors = cx.update_workspace(|workspace, _, cx| {
22946        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22947    });
22948    cx.update_editor(|_, _, _| {
22949        assert_eq!(
22950            editors.len(),
22951            3,
22952            "should have used a new multibuffer as offsets changed"
22953        );
22954    });
22955}
22956#[gpui::test]
22957async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22958    init_test(cx, |_| {});
22959
22960    let language = Arc::new(Language::new(
22961        LanguageConfig::default(),
22962        Some(tree_sitter_rust::LANGUAGE.into()),
22963    ));
22964
22965    let text = r#"
22966        #[cfg(test)]
22967        mod tests() {
22968            #[test]
22969            fn runnable_1() {
22970                let a = 1;
22971            }
22972
22973            #[test]
22974            fn runnable_2() {
22975                let a = 1;
22976                let b = 2;
22977            }
22978        }
22979    "#
22980    .unindent();
22981
22982    let fs = FakeFs::new(cx.executor());
22983    fs.insert_file("/file.rs", Default::default()).await;
22984
22985    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22986    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22987    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22988    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22989    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22990
22991    let editor = cx.new_window_entity(|window, cx| {
22992        Editor::new(
22993            EditorMode::full(),
22994            multi_buffer,
22995            Some(project.clone()),
22996            window,
22997            cx,
22998        )
22999    });
23000
23001    editor.update_in(cx, |editor, window, cx| {
23002        let snapshot = editor.buffer().read(cx).snapshot(cx);
23003        editor.tasks.insert(
23004            (buffer.read(cx).remote_id(), 3),
23005            RunnableTasks {
23006                templates: vec![],
23007                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23008                column: 0,
23009                extra_variables: HashMap::default(),
23010                context_range: BufferOffset(43)..BufferOffset(85),
23011            },
23012        );
23013        editor.tasks.insert(
23014            (buffer.read(cx).remote_id(), 8),
23015            RunnableTasks {
23016                templates: vec![],
23017                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23018                column: 0,
23019                extra_variables: HashMap::default(),
23020                context_range: BufferOffset(86)..BufferOffset(191),
23021            },
23022        );
23023
23024        // Test finding task when cursor is inside function body
23025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23026            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23027        });
23028        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23029        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23030
23031        // Test finding task when cursor is on function name
23032        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23033            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23034        });
23035        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23036        assert_eq!(row, 8, "Should find task when cursor is on function name");
23037    });
23038}
23039
23040#[gpui::test]
23041async fn test_folding_buffers(cx: &mut TestAppContext) {
23042    init_test(cx, |_| {});
23043
23044    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23045    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23046    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23047
23048    let fs = FakeFs::new(cx.executor());
23049    fs.insert_tree(
23050        path!("/a"),
23051        json!({
23052            "first.rs": sample_text_1,
23053            "second.rs": sample_text_2,
23054            "third.rs": sample_text_3,
23055        }),
23056    )
23057    .await;
23058    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23059    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23060    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23061    let worktree = project.update(cx, |project, cx| {
23062        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23063        assert_eq!(worktrees.len(), 1);
23064        worktrees.pop().unwrap()
23065    });
23066    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23067
23068    let buffer_1 = project
23069        .update(cx, |project, cx| {
23070            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23071        })
23072        .await
23073        .unwrap();
23074    let buffer_2 = project
23075        .update(cx, |project, cx| {
23076            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23077        })
23078        .await
23079        .unwrap();
23080    let buffer_3 = project
23081        .update(cx, |project, cx| {
23082            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23083        })
23084        .await
23085        .unwrap();
23086
23087    let multi_buffer = cx.new(|cx| {
23088        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23089        multi_buffer.push_excerpts(
23090            buffer_1.clone(),
23091            [
23092                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23093                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23094                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23095            ],
23096            cx,
23097        );
23098        multi_buffer.push_excerpts(
23099            buffer_2.clone(),
23100            [
23101                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23102                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23103                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23104            ],
23105            cx,
23106        );
23107        multi_buffer.push_excerpts(
23108            buffer_3.clone(),
23109            [
23110                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23111                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23112                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23113            ],
23114            cx,
23115        );
23116        multi_buffer
23117    });
23118    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23119        Editor::new(
23120            EditorMode::full(),
23121            multi_buffer.clone(),
23122            Some(project.clone()),
23123            window,
23124            cx,
23125        )
23126    });
23127
23128    assert_eq!(
23129        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23130        "\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",
23131    );
23132
23133    multi_buffer_editor.update(cx, |editor, cx| {
23134        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23135    });
23136    assert_eq!(
23137        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23138        "\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",
23139        "After folding the first buffer, its text should not be displayed"
23140    );
23141
23142    multi_buffer_editor.update(cx, |editor, cx| {
23143        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23144    });
23145    assert_eq!(
23146        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23147        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23148        "After folding the second buffer, its text should not be displayed"
23149    );
23150
23151    multi_buffer_editor.update(cx, |editor, cx| {
23152        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23153    });
23154    assert_eq!(
23155        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23156        "\n\n\n\n\n",
23157        "After folding the third buffer, its text should not be displayed"
23158    );
23159
23160    // Emulate selection inside the fold logic, that should work
23161    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23162        editor
23163            .snapshot(window, cx)
23164            .next_line_boundary(Point::new(0, 4));
23165    });
23166
23167    multi_buffer_editor.update(cx, |editor, cx| {
23168        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23169    });
23170    assert_eq!(
23171        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23172        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23173        "After unfolding the second buffer, its text should be displayed"
23174    );
23175
23176    // Typing inside of buffer 1 causes that buffer to be unfolded.
23177    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23178        assert_eq!(
23179            multi_buffer
23180                .read(cx)
23181                .snapshot(cx)
23182                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23183                .collect::<String>(),
23184            "bbbb"
23185        );
23186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23187            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23188        });
23189        editor.handle_input("B", window, cx);
23190    });
23191
23192    assert_eq!(
23193        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23194        "\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",
23195        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23196    );
23197
23198    multi_buffer_editor.update(cx, |editor, cx| {
23199        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23200    });
23201    assert_eq!(
23202        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23203        "\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",
23204        "After unfolding the all buffers, all original text should be displayed"
23205    );
23206}
23207
23208#[gpui::test]
23209async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23210    init_test(cx, |_| {});
23211
23212    let sample_text_1 = "1111\n2222\n3333".to_string();
23213    let sample_text_2 = "4444\n5555\n6666".to_string();
23214    let sample_text_3 = "7777\n8888\n9999".to_string();
23215
23216    let fs = FakeFs::new(cx.executor());
23217    fs.insert_tree(
23218        path!("/a"),
23219        json!({
23220            "first.rs": sample_text_1,
23221            "second.rs": sample_text_2,
23222            "third.rs": sample_text_3,
23223        }),
23224    )
23225    .await;
23226    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23227    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23228    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23229    let worktree = project.update(cx, |project, cx| {
23230        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23231        assert_eq!(worktrees.len(), 1);
23232        worktrees.pop().unwrap()
23233    });
23234    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23235
23236    let buffer_1 = project
23237        .update(cx, |project, cx| {
23238            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23239        })
23240        .await
23241        .unwrap();
23242    let buffer_2 = project
23243        .update(cx, |project, cx| {
23244            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23245        })
23246        .await
23247        .unwrap();
23248    let buffer_3 = project
23249        .update(cx, |project, cx| {
23250            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23251        })
23252        .await
23253        .unwrap();
23254
23255    let multi_buffer = cx.new(|cx| {
23256        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23257        multi_buffer.push_excerpts(
23258            buffer_1.clone(),
23259            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23260            cx,
23261        );
23262        multi_buffer.push_excerpts(
23263            buffer_2.clone(),
23264            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23265            cx,
23266        );
23267        multi_buffer.push_excerpts(
23268            buffer_3.clone(),
23269            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23270            cx,
23271        );
23272        multi_buffer
23273    });
23274
23275    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23276        Editor::new(
23277            EditorMode::full(),
23278            multi_buffer,
23279            Some(project.clone()),
23280            window,
23281            cx,
23282        )
23283    });
23284
23285    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23286    assert_eq!(
23287        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23288        full_text,
23289    );
23290
23291    multi_buffer_editor.update(cx, |editor, cx| {
23292        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23293    });
23294    assert_eq!(
23295        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23296        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23297        "After folding the first buffer, its text should not be displayed"
23298    );
23299
23300    multi_buffer_editor.update(cx, |editor, cx| {
23301        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23302    });
23303
23304    assert_eq!(
23305        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23306        "\n\n\n\n\n\n7777\n8888\n9999",
23307        "After folding the second buffer, its text should not be displayed"
23308    );
23309
23310    multi_buffer_editor.update(cx, |editor, cx| {
23311        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23312    });
23313    assert_eq!(
23314        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23315        "\n\n\n\n\n",
23316        "After folding the third buffer, its text should not be displayed"
23317    );
23318
23319    multi_buffer_editor.update(cx, |editor, cx| {
23320        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23321    });
23322    assert_eq!(
23323        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23324        "\n\n\n\n4444\n5555\n6666\n\n",
23325        "After unfolding the second buffer, its text should be displayed"
23326    );
23327
23328    multi_buffer_editor.update(cx, |editor, cx| {
23329        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23330    });
23331    assert_eq!(
23332        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23333        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23334        "After unfolding the first buffer, its text should be displayed"
23335    );
23336
23337    multi_buffer_editor.update(cx, |editor, cx| {
23338        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23339    });
23340    assert_eq!(
23341        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23342        full_text,
23343        "After unfolding all buffers, all original text should be displayed"
23344    );
23345}
23346
23347#[gpui::test]
23348async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23349    init_test(cx, |_| {});
23350
23351    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23352
23353    let fs = FakeFs::new(cx.executor());
23354    fs.insert_tree(
23355        path!("/a"),
23356        json!({
23357            "main.rs": sample_text,
23358        }),
23359    )
23360    .await;
23361    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23362    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23363    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23364    let worktree = project.update(cx, |project, cx| {
23365        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23366        assert_eq!(worktrees.len(), 1);
23367        worktrees.pop().unwrap()
23368    });
23369    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23370
23371    let buffer_1 = project
23372        .update(cx, |project, cx| {
23373            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23374        })
23375        .await
23376        .unwrap();
23377
23378    let multi_buffer = cx.new(|cx| {
23379        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23380        multi_buffer.push_excerpts(
23381            buffer_1.clone(),
23382            [ExcerptRange::new(
23383                Point::new(0, 0)
23384                    ..Point::new(
23385                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23386                        0,
23387                    ),
23388            )],
23389            cx,
23390        );
23391        multi_buffer
23392    });
23393    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23394        Editor::new(
23395            EditorMode::full(),
23396            multi_buffer,
23397            Some(project.clone()),
23398            window,
23399            cx,
23400        )
23401    });
23402
23403    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23404    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23405        enum TestHighlight {}
23406        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23407        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23408        editor.highlight_text::<TestHighlight>(
23409            vec![highlight_range.clone()],
23410            HighlightStyle::color(Hsla::green()),
23411            cx,
23412        );
23413        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23414            s.select_ranges(Some(highlight_range))
23415        });
23416    });
23417
23418    let full_text = format!("\n\n{sample_text}");
23419    assert_eq!(
23420        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23421        full_text,
23422    );
23423}
23424
23425#[gpui::test]
23426async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23427    init_test(cx, |_| {});
23428    cx.update(|cx| {
23429        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23430            "keymaps/default-linux.json",
23431            cx,
23432        )
23433        .unwrap();
23434        cx.bind_keys(default_key_bindings);
23435    });
23436
23437    let (editor, cx) = cx.add_window_view(|window, cx| {
23438        let multi_buffer = MultiBuffer::build_multi(
23439            [
23440                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23441                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23442                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23443                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23444            ],
23445            cx,
23446        );
23447        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23448
23449        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23450        // fold all but the second buffer, so that we test navigating between two
23451        // adjacent folded buffers, as well as folded buffers at the start and
23452        // end the multibuffer
23453        editor.fold_buffer(buffer_ids[0], cx);
23454        editor.fold_buffer(buffer_ids[2], cx);
23455        editor.fold_buffer(buffer_ids[3], cx);
23456
23457        editor
23458    });
23459    cx.simulate_resize(size(px(1000.), px(1000.)));
23460
23461    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23462    cx.assert_excerpts_with_selections(indoc! {"
23463        [EXCERPT]
23464        ˇ[FOLDED]
23465        [EXCERPT]
23466        a1
23467        b1
23468        [EXCERPT]
23469        [FOLDED]
23470        [EXCERPT]
23471        [FOLDED]
23472        "
23473    });
23474    cx.simulate_keystroke("down");
23475    cx.assert_excerpts_with_selections(indoc! {"
23476        [EXCERPT]
23477        [FOLDED]
23478        [EXCERPT]
23479        ˇa1
23480        b1
23481        [EXCERPT]
23482        [FOLDED]
23483        [EXCERPT]
23484        [FOLDED]
23485        "
23486    });
23487    cx.simulate_keystroke("down");
23488    cx.assert_excerpts_with_selections(indoc! {"
23489        [EXCERPT]
23490        [FOLDED]
23491        [EXCERPT]
23492        a1
23493        ˇb1
23494        [EXCERPT]
23495        [FOLDED]
23496        [EXCERPT]
23497        [FOLDED]
23498        "
23499    });
23500    cx.simulate_keystroke("down");
23501    cx.assert_excerpts_with_selections(indoc! {"
23502        [EXCERPT]
23503        [FOLDED]
23504        [EXCERPT]
23505        a1
23506        b1
23507        ˇ[EXCERPT]
23508        [FOLDED]
23509        [EXCERPT]
23510        [FOLDED]
23511        "
23512    });
23513    cx.simulate_keystroke("down");
23514    cx.assert_excerpts_with_selections(indoc! {"
23515        [EXCERPT]
23516        [FOLDED]
23517        [EXCERPT]
23518        a1
23519        b1
23520        [EXCERPT]
23521        ˇ[FOLDED]
23522        [EXCERPT]
23523        [FOLDED]
23524        "
23525    });
23526    for _ in 0..5 {
23527        cx.simulate_keystroke("down");
23528        cx.assert_excerpts_with_selections(indoc! {"
23529            [EXCERPT]
23530            [FOLDED]
23531            [EXCERPT]
23532            a1
23533            b1
23534            [EXCERPT]
23535            [FOLDED]
23536            [EXCERPT]
23537            ˇ[FOLDED]
23538            "
23539        });
23540    }
23541
23542    cx.simulate_keystroke("up");
23543    cx.assert_excerpts_with_selections(indoc! {"
23544        [EXCERPT]
23545        [FOLDED]
23546        [EXCERPT]
23547        a1
23548        b1
23549        [EXCERPT]
23550        ˇ[FOLDED]
23551        [EXCERPT]
23552        [FOLDED]
23553        "
23554    });
23555    cx.simulate_keystroke("up");
23556    cx.assert_excerpts_with_selections(indoc! {"
23557        [EXCERPT]
23558        [FOLDED]
23559        [EXCERPT]
23560        a1
23561        b1
23562        ˇ[EXCERPT]
23563        [FOLDED]
23564        [EXCERPT]
23565        [FOLDED]
23566        "
23567    });
23568    cx.simulate_keystroke("up");
23569    cx.assert_excerpts_with_selections(indoc! {"
23570        [EXCERPT]
23571        [FOLDED]
23572        [EXCERPT]
23573        a1
23574        ˇb1
23575        [EXCERPT]
23576        [FOLDED]
23577        [EXCERPT]
23578        [FOLDED]
23579        "
23580    });
23581    cx.simulate_keystroke("up");
23582    cx.assert_excerpts_with_selections(indoc! {"
23583        [EXCERPT]
23584        [FOLDED]
23585        [EXCERPT]
23586        ˇa1
23587        b1
23588        [EXCERPT]
23589        [FOLDED]
23590        [EXCERPT]
23591        [FOLDED]
23592        "
23593    });
23594    for _ in 0..5 {
23595        cx.simulate_keystroke("up");
23596        cx.assert_excerpts_with_selections(indoc! {"
23597            [EXCERPT]
23598            ˇ[FOLDED]
23599            [EXCERPT]
23600            a1
23601            b1
23602            [EXCERPT]
23603            [FOLDED]
23604            [EXCERPT]
23605            [FOLDED]
23606            "
23607        });
23608    }
23609}
23610
23611#[gpui::test]
23612async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23613    init_test(cx, |_| {});
23614
23615    // Simple insertion
23616    assert_highlighted_edits(
23617        "Hello, world!",
23618        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23619        true,
23620        cx,
23621        |highlighted_edits, cx| {
23622            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23623            assert_eq!(highlighted_edits.highlights.len(), 1);
23624            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23625            assert_eq!(
23626                highlighted_edits.highlights[0].1.background_color,
23627                Some(cx.theme().status().created_background)
23628            );
23629        },
23630    )
23631    .await;
23632
23633    // Replacement
23634    assert_highlighted_edits(
23635        "This is a test.",
23636        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23637        false,
23638        cx,
23639        |highlighted_edits, cx| {
23640            assert_eq!(highlighted_edits.text, "That is a test.");
23641            assert_eq!(highlighted_edits.highlights.len(), 1);
23642            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23643            assert_eq!(
23644                highlighted_edits.highlights[0].1.background_color,
23645                Some(cx.theme().status().created_background)
23646            );
23647        },
23648    )
23649    .await;
23650
23651    // Multiple edits
23652    assert_highlighted_edits(
23653        "Hello, world!",
23654        vec![
23655            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23656            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23657        ],
23658        false,
23659        cx,
23660        |highlighted_edits, cx| {
23661            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23662            assert_eq!(highlighted_edits.highlights.len(), 2);
23663            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23664            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23665            assert_eq!(
23666                highlighted_edits.highlights[0].1.background_color,
23667                Some(cx.theme().status().created_background)
23668            );
23669            assert_eq!(
23670                highlighted_edits.highlights[1].1.background_color,
23671                Some(cx.theme().status().created_background)
23672            );
23673        },
23674    )
23675    .await;
23676
23677    // Multiple lines with edits
23678    assert_highlighted_edits(
23679        "First line\nSecond line\nThird line\nFourth line",
23680        vec![
23681            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23682            (
23683                Point::new(2, 0)..Point::new(2, 10),
23684                "New third line".to_string(),
23685            ),
23686            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23687        ],
23688        false,
23689        cx,
23690        |highlighted_edits, cx| {
23691            assert_eq!(
23692                highlighted_edits.text,
23693                "Second modified\nNew third line\nFourth updated line"
23694            );
23695            assert_eq!(highlighted_edits.highlights.len(), 3);
23696            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23697            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23698            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23699            for highlight in &highlighted_edits.highlights {
23700                assert_eq!(
23701                    highlight.1.background_color,
23702                    Some(cx.theme().status().created_background)
23703                );
23704            }
23705        },
23706    )
23707    .await;
23708}
23709
23710#[gpui::test]
23711async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23712    init_test(cx, |_| {});
23713
23714    // Deletion
23715    assert_highlighted_edits(
23716        "Hello, world!",
23717        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23718        true,
23719        cx,
23720        |highlighted_edits, cx| {
23721            assert_eq!(highlighted_edits.text, "Hello, world!");
23722            assert_eq!(highlighted_edits.highlights.len(), 1);
23723            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23724            assert_eq!(
23725                highlighted_edits.highlights[0].1.background_color,
23726                Some(cx.theme().status().deleted_background)
23727            );
23728        },
23729    )
23730    .await;
23731
23732    // Insertion
23733    assert_highlighted_edits(
23734        "Hello, world!",
23735        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23736        true,
23737        cx,
23738        |highlighted_edits, cx| {
23739            assert_eq!(highlighted_edits.highlights.len(), 1);
23740            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23741            assert_eq!(
23742                highlighted_edits.highlights[0].1.background_color,
23743                Some(cx.theme().status().created_background)
23744            );
23745        },
23746    )
23747    .await;
23748}
23749
23750async fn assert_highlighted_edits(
23751    text: &str,
23752    edits: Vec<(Range<Point>, String)>,
23753    include_deletions: bool,
23754    cx: &mut TestAppContext,
23755    assertion_fn: impl Fn(HighlightedText, &App),
23756) {
23757    let window = cx.add_window(|window, cx| {
23758        let buffer = MultiBuffer::build_simple(text, cx);
23759        Editor::new(EditorMode::full(), buffer, None, window, cx)
23760    });
23761    let cx = &mut VisualTestContext::from_window(*window, cx);
23762
23763    let (buffer, snapshot) = window
23764        .update(cx, |editor, _window, cx| {
23765            (
23766                editor.buffer().clone(),
23767                editor.buffer().read(cx).snapshot(cx),
23768            )
23769        })
23770        .unwrap();
23771
23772    let edits = edits
23773        .into_iter()
23774        .map(|(range, edit)| {
23775            (
23776                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23777                edit,
23778            )
23779        })
23780        .collect::<Vec<_>>();
23781
23782    let text_anchor_edits = edits
23783        .clone()
23784        .into_iter()
23785        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23786        .collect::<Vec<_>>();
23787
23788    let edit_preview = window
23789        .update(cx, |_, _window, cx| {
23790            buffer
23791                .read(cx)
23792                .as_singleton()
23793                .unwrap()
23794                .read(cx)
23795                .preview_edits(text_anchor_edits.into(), cx)
23796        })
23797        .unwrap()
23798        .await;
23799
23800    cx.update(|_window, cx| {
23801        let highlighted_edits = edit_prediction_edit_text(
23802            snapshot.as_singleton().unwrap().2,
23803            &edits,
23804            &edit_preview,
23805            include_deletions,
23806            cx,
23807        );
23808        assertion_fn(highlighted_edits, cx)
23809    });
23810}
23811
23812#[track_caller]
23813fn assert_breakpoint(
23814    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23815    path: &Arc<Path>,
23816    expected: Vec<(u32, Breakpoint)>,
23817) {
23818    if expected.is_empty() {
23819        assert!(!breakpoints.contains_key(path), "{}", path.display());
23820    } else {
23821        let mut breakpoint = breakpoints
23822            .get(path)
23823            .unwrap()
23824            .iter()
23825            .map(|breakpoint| {
23826                (
23827                    breakpoint.row,
23828                    Breakpoint {
23829                        message: breakpoint.message.clone(),
23830                        state: breakpoint.state,
23831                        condition: breakpoint.condition.clone(),
23832                        hit_condition: breakpoint.hit_condition.clone(),
23833                    },
23834                )
23835            })
23836            .collect::<Vec<_>>();
23837
23838        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23839
23840        assert_eq!(expected, breakpoint);
23841    }
23842}
23843
23844fn add_log_breakpoint_at_cursor(
23845    editor: &mut Editor,
23846    log_message: &str,
23847    window: &mut Window,
23848    cx: &mut Context<Editor>,
23849) {
23850    let (anchor, bp) = editor
23851        .breakpoints_at_cursors(window, cx)
23852        .first()
23853        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23854        .unwrap_or_else(|| {
23855            let snapshot = editor.snapshot(window, cx);
23856            let cursor_position: Point =
23857                editor.selections.newest(&snapshot.display_snapshot).head();
23858
23859            let breakpoint_position = snapshot
23860                .buffer_snapshot()
23861                .anchor_before(Point::new(cursor_position.row, 0));
23862
23863            (breakpoint_position, Breakpoint::new_log(log_message))
23864        });
23865
23866    editor.edit_breakpoint_at_anchor(
23867        anchor,
23868        bp,
23869        BreakpointEditAction::EditLogMessage(log_message.into()),
23870        cx,
23871    );
23872}
23873
23874#[gpui::test]
23875async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23876    init_test(cx, |_| {});
23877
23878    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23879    let fs = FakeFs::new(cx.executor());
23880    fs.insert_tree(
23881        path!("/a"),
23882        json!({
23883            "main.rs": sample_text,
23884        }),
23885    )
23886    .await;
23887    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23888    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23889    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23890
23891    let fs = FakeFs::new(cx.executor());
23892    fs.insert_tree(
23893        path!("/a"),
23894        json!({
23895            "main.rs": sample_text,
23896        }),
23897    )
23898    .await;
23899    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23900    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23901    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23902    let worktree_id = workspace
23903        .update(cx, |workspace, _window, cx| {
23904            workspace.project().update(cx, |project, cx| {
23905                project.worktrees(cx).next().unwrap().read(cx).id()
23906            })
23907        })
23908        .unwrap();
23909
23910    let buffer = project
23911        .update(cx, |project, cx| {
23912            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23913        })
23914        .await
23915        .unwrap();
23916
23917    let (editor, cx) = cx.add_window_view(|window, cx| {
23918        Editor::new(
23919            EditorMode::full(),
23920            MultiBuffer::build_from_buffer(buffer, cx),
23921            Some(project.clone()),
23922            window,
23923            cx,
23924        )
23925    });
23926
23927    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23928    let abs_path = project.read_with(cx, |project, cx| {
23929        project
23930            .absolute_path(&project_path, cx)
23931            .map(Arc::from)
23932            .unwrap()
23933    });
23934
23935    // assert we can add breakpoint on the first line
23936    editor.update_in(cx, |editor, window, cx| {
23937        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23938        editor.move_to_end(&MoveToEnd, window, cx);
23939        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23940    });
23941
23942    let breakpoints = editor.update(cx, |editor, cx| {
23943        editor
23944            .breakpoint_store()
23945            .as_ref()
23946            .unwrap()
23947            .read(cx)
23948            .all_source_breakpoints(cx)
23949    });
23950
23951    assert_eq!(1, breakpoints.len());
23952    assert_breakpoint(
23953        &breakpoints,
23954        &abs_path,
23955        vec![
23956            (0, Breakpoint::new_standard()),
23957            (3, Breakpoint::new_standard()),
23958        ],
23959    );
23960
23961    editor.update_in(cx, |editor, window, cx| {
23962        editor.move_to_beginning(&MoveToBeginning, window, cx);
23963        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23964    });
23965
23966    let breakpoints = editor.update(cx, |editor, cx| {
23967        editor
23968            .breakpoint_store()
23969            .as_ref()
23970            .unwrap()
23971            .read(cx)
23972            .all_source_breakpoints(cx)
23973    });
23974
23975    assert_eq!(1, breakpoints.len());
23976    assert_breakpoint(
23977        &breakpoints,
23978        &abs_path,
23979        vec![(3, Breakpoint::new_standard())],
23980    );
23981
23982    editor.update_in(cx, |editor, window, cx| {
23983        editor.move_to_end(&MoveToEnd, window, cx);
23984        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23985    });
23986
23987    let breakpoints = editor.update(cx, |editor, cx| {
23988        editor
23989            .breakpoint_store()
23990            .as_ref()
23991            .unwrap()
23992            .read(cx)
23993            .all_source_breakpoints(cx)
23994    });
23995
23996    assert_eq!(0, breakpoints.len());
23997    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23998}
23999
24000#[gpui::test]
24001async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24002    init_test(cx, |_| {});
24003
24004    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24005
24006    let fs = FakeFs::new(cx.executor());
24007    fs.insert_tree(
24008        path!("/a"),
24009        json!({
24010            "main.rs": sample_text,
24011        }),
24012    )
24013    .await;
24014    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24015    let (workspace, cx) =
24016        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24017
24018    let worktree_id = workspace.update(cx, |workspace, cx| {
24019        workspace.project().update(cx, |project, cx| {
24020            project.worktrees(cx).next().unwrap().read(cx).id()
24021        })
24022    });
24023
24024    let buffer = project
24025        .update(cx, |project, cx| {
24026            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24027        })
24028        .await
24029        .unwrap();
24030
24031    let (editor, cx) = cx.add_window_view(|window, cx| {
24032        Editor::new(
24033            EditorMode::full(),
24034            MultiBuffer::build_from_buffer(buffer, cx),
24035            Some(project.clone()),
24036            window,
24037            cx,
24038        )
24039    });
24040
24041    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24042    let abs_path = project.read_with(cx, |project, cx| {
24043        project
24044            .absolute_path(&project_path, cx)
24045            .map(Arc::from)
24046            .unwrap()
24047    });
24048
24049    editor.update_in(cx, |editor, window, cx| {
24050        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24051    });
24052
24053    let breakpoints = editor.update(cx, |editor, cx| {
24054        editor
24055            .breakpoint_store()
24056            .as_ref()
24057            .unwrap()
24058            .read(cx)
24059            .all_source_breakpoints(cx)
24060    });
24061
24062    assert_breakpoint(
24063        &breakpoints,
24064        &abs_path,
24065        vec![(0, Breakpoint::new_log("hello world"))],
24066    );
24067
24068    // Removing a log message from a log breakpoint should remove it
24069    editor.update_in(cx, |editor, window, cx| {
24070        add_log_breakpoint_at_cursor(editor, "", window, cx);
24071    });
24072
24073    let breakpoints = editor.update(cx, |editor, cx| {
24074        editor
24075            .breakpoint_store()
24076            .as_ref()
24077            .unwrap()
24078            .read(cx)
24079            .all_source_breakpoints(cx)
24080    });
24081
24082    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24083
24084    editor.update_in(cx, |editor, window, cx| {
24085        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24086        editor.move_to_end(&MoveToEnd, window, cx);
24087        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24088        // Not adding a log message to a standard breakpoint shouldn't remove it
24089        add_log_breakpoint_at_cursor(editor, "", window, cx);
24090    });
24091
24092    let breakpoints = editor.update(cx, |editor, cx| {
24093        editor
24094            .breakpoint_store()
24095            .as_ref()
24096            .unwrap()
24097            .read(cx)
24098            .all_source_breakpoints(cx)
24099    });
24100
24101    assert_breakpoint(
24102        &breakpoints,
24103        &abs_path,
24104        vec![
24105            (0, Breakpoint::new_standard()),
24106            (3, Breakpoint::new_standard()),
24107        ],
24108    );
24109
24110    editor.update_in(cx, |editor, window, cx| {
24111        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24112    });
24113
24114    let breakpoints = editor.update(cx, |editor, cx| {
24115        editor
24116            .breakpoint_store()
24117            .as_ref()
24118            .unwrap()
24119            .read(cx)
24120            .all_source_breakpoints(cx)
24121    });
24122
24123    assert_breakpoint(
24124        &breakpoints,
24125        &abs_path,
24126        vec![
24127            (0, Breakpoint::new_standard()),
24128            (3, Breakpoint::new_log("hello world")),
24129        ],
24130    );
24131
24132    editor.update_in(cx, |editor, window, cx| {
24133        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24134    });
24135
24136    let breakpoints = editor.update(cx, |editor, cx| {
24137        editor
24138            .breakpoint_store()
24139            .as_ref()
24140            .unwrap()
24141            .read(cx)
24142            .all_source_breakpoints(cx)
24143    });
24144
24145    assert_breakpoint(
24146        &breakpoints,
24147        &abs_path,
24148        vec![
24149            (0, Breakpoint::new_standard()),
24150            (3, Breakpoint::new_log("hello Earth!!")),
24151        ],
24152    );
24153}
24154
24155/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24156/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24157/// or when breakpoints were placed out of order. This tests for a regression too
24158#[gpui::test]
24159async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24160    init_test(cx, |_| {});
24161
24162    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24163    let fs = FakeFs::new(cx.executor());
24164    fs.insert_tree(
24165        path!("/a"),
24166        json!({
24167            "main.rs": sample_text,
24168        }),
24169    )
24170    .await;
24171    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24172    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24173    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24174
24175    let fs = FakeFs::new(cx.executor());
24176    fs.insert_tree(
24177        path!("/a"),
24178        json!({
24179            "main.rs": sample_text,
24180        }),
24181    )
24182    .await;
24183    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24184    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24185    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24186    let worktree_id = workspace
24187        .update(cx, |workspace, _window, cx| {
24188            workspace.project().update(cx, |project, cx| {
24189                project.worktrees(cx).next().unwrap().read(cx).id()
24190            })
24191        })
24192        .unwrap();
24193
24194    let buffer = project
24195        .update(cx, |project, cx| {
24196            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24197        })
24198        .await
24199        .unwrap();
24200
24201    let (editor, cx) = cx.add_window_view(|window, cx| {
24202        Editor::new(
24203            EditorMode::full(),
24204            MultiBuffer::build_from_buffer(buffer, cx),
24205            Some(project.clone()),
24206            window,
24207            cx,
24208        )
24209    });
24210
24211    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24212    let abs_path = project.read_with(cx, |project, cx| {
24213        project
24214            .absolute_path(&project_path, cx)
24215            .map(Arc::from)
24216            .unwrap()
24217    });
24218
24219    // assert we can add breakpoint on the first line
24220    editor.update_in(cx, |editor, window, cx| {
24221        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24222        editor.move_to_end(&MoveToEnd, window, cx);
24223        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24224        editor.move_up(&MoveUp, window, cx);
24225        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24226    });
24227
24228    let breakpoints = editor.update(cx, |editor, cx| {
24229        editor
24230            .breakpoint_store()
24231            .as_ref()
24232            .unwrap()
24233            .read(cx)
24234            .all_source_breakpoints(cx)
24235    });
24236
24237    assert_eq!(1, breakpoints.len());
24238    assert_breakpoint(
24239        &breakpoints,
24240        &abs_path,
24241        vec![
24242            (0, Breakpoint::new_standard()),
24243            (2, Breakpoint::new_standard()),
24244            (3, Breakpoint::new_standard()),
24245        ],
24246    );
24247
24248    editor.update_in(cx, |editor, window, cx| {
24249        editor.move_to_beginning(&MoveToBeginning, window, cx);
24250        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24251        editor.move_to_end(&MoveToEnd, window, cx);
24252        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24253        // Disabling a breakpoint that doesn't exist should do nothing
24254        editor.move_up(&MoveUp, window, cx);
24255        editor.move_up(&MoveUp, window, cx);
24256        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24257    });
24258
24259    let breakpoints = editor.update(cx, |editor, cx| {
24260        editor
24261            .breakpoint_store()
24262            .as_ref()
24263            .unwrap()
24264            .read(cx)
24265            .all_source_breakpoints(cx)
24266    });
24267
24268    let disable_breakpoint = {
24269        let mut bp = Breakpoint::new_standard();
24270        bp.state = BreakpointState::Disabled;
24271        bp
24272    };
24273
24274    assert_eq!(1, breakpoints.len());
24275    assert_breakpoint(
24276        &breakpoints,
24277        &abs_path,
24278        vec![
24279            (0, disable_breakpoint.clone()),
24280            (2, Breakpoint::new_standard()),
24281            (3, disable_breakpoint.clone()),
24282        ],
24283    );
24284
24285    editor.update_in(cx, |editor, window, cx| {
24286        editor.move_to_beginning(&MoveToBeginning, window, cx);
24287        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24288        editor.move_to_end(&MoveToEnd, window, cx);
24289        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24290        editor.move_up(&MoveUp, window, cx);
24291        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24292    });
24293
24294    let breakpoints = editor.update(cx, |editor, cx| {
24295        editor
24296            .breakpoint_store()
24297            .as_ref()
24298            .unwrap()
24299            .read(cx)
24300            .all_source_breakpoints(cx)
24301    });
24302
24303    assert_eq!(1, breakpoints.len());
24304    assert_breakpoint(
24305        &breakpoints,
24306        &abs_path,
24307        vec![
24308            (0, Breakpoint::new_standard()),
24309            (2, disable_breakpoint),
24310            (3, Breakpoint::new_standard()),
24311        ],
24312    );
24313}
24314
24315#[gpui::test]
24316async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24317    init_test(cx, |_| {});
24318    let capabilities = lsp::ServerCapabilities {
24319        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24320            prepare_provider: Some(true),
24321            work_done_progress_options: Default::default(),
24322        })),
24323        ..Default::default()
24324    };
24325    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24326
24327    cx.set_state(indoc! {"
24328        struct Fˇoo {}
24329    "});
24330
24331    cx.update_editor(|editor, _, cx| {
24332        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24333        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24334        editor.highlight_background::<DocumentHighlightRead>(
24335            &[highlight_range],
24336            |_, theme| theme.colors().editor_document_highlight_read_background,
24337            cx,
24338        );
24339    });
24340
24341    let mut prepare_rename_handler = cx
24342        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24343            move |_, _, _| async move {
24344                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24345                    start: lsp::Position {
24346                        line: 0,
24347                        character: 7,
24348                    },
24349                    end: lsp::Position {
24350                        line: 0,
24351                        character: 10,
24352                    },
24353                })))
24354            },
24355        );
24356    let prepare_rename_task = cx
24357        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24358        .expect("Prepare rename was not started");
24359    prepare_rename_handler.next().await.unwrap();
24360    prepare_rename_task.await.expect("Prepare rename failed");
24361
24362    let mut rename_handler =
24363        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24364            let edit = lsp::TextEdit {
24365                range: lsp::Range {
24366                    start: lsp::Position {
24367                        line: 0,
24368                        character: 7,
24369                    },
24370                    end: lsp::Position {
24371                        line: 0,
24372                        character: 10,
24373                    },
24374                },
24375                new_text: "FooRenamed".to_string(),
24376            };
24377            Ok(Some(lsp::WorkspaceEdit::new(
24378                // Specify the same edit twice
24379                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24380            )))
24381        });
24382    let rename_task = cx
24383        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24384        .expect("Confirm rename was not started");
24385    rename_handler.next().await.unwrap();
24386    rename_task.await.expect("Confirm rename failed");
24387    cx.run_until_parked();
24388
24389    // Despite two edits, only one is actually applied as those are identical
24390    cx.assert_editor_state(indoc! {"
24391        struct FooRenamedˇ {}
24392    "});
24393}
24394
24395#[gpui::test]
24396async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24397    init_test(cx, |_| {});
24398    // These capabilities indicate that the server does not support prepare rename.
24399    let capabilities = lsp::ServerCapabilities {
24400        rename_provider: Some(lsp::OneOf::Left(true)),
24401        ..Default::default()
24402    };
24403    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24404
24405    cx.set_state(indoc! {"
24406        struct Fˇoo {}
24407    "});
24408
24409    cx.update_editor(|editor, _window, cx| {
24410        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24411        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24412        editor.highlight_background::<DocumentHighlightRead>(
24413            &[highlight_range],
24414            |_, theme| theme.colors().editor_document_highlight_read_background,
24415            cx,
24416        );
24417    });
24418
24419    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24420        .expect("Prepare rename was not started")
24421        .await
24422        .expect("Prepare rename failed");
24423
24424    let mut rename_handler =
24425        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24426            let edit = lsp::TextEdit {
24427                range: lsp::Range {
24428                    start: lsp::Position {
24429                        line: 0,
24430                        character: 7,
24431                    },
24432                    end: lsp::Position {
24433                        line: 0,
24434                        character: 10,
24435                    },
24436                },
24437                new_text: "FooRenamed".to_string(),
24438            };
24439            Ok(Some(lsp::WorkspaceEdit::new(
24440                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24441            )))
24442        });
24443    let rename_task = cx
24444        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24445        .expect("Confirm rename was not started");
24446    rename_handler.next().await.unwrap();
24447    rename_task.await.expect("Confirm rename failed");
24448    cx.run_until_parked();
24449
24450    // Correct range is renamed, as `surrounding_word` is used to find it.
24451    cx.assert_editor_state(indoc! {"
24452        struct FooRenamedˇ {}
24453    "});
24454}
24455
24456#[gpui::test]
24457async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24458    init_test(cx, |_| {});
24459    let mut cx = EditorTestContext::new(cx).await;
24460
24461    let language = Arc::new(
24462        Language::new(
24463            LanguageConfig::default(),
24464            Some(tree_sitter_html::LANGUAGE.into()),
24465        )
24466        .with_brackets_query(
24467            r#"
24468            ("<" @open "/>" @close)
24469            ("</" @open ">" @close)
24470            ("<" @open ">" @close)
24471            ("\"" @open "\"" @close)
24472            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24473        "#,
24474        )
24475        .unwrap(),
24476    );
24477    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24478
24479    cx.set_state(indoc! {"
24480        <span>ˇ</span>
24481    "});
24482    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24483    cx.assert_editor_state(indoc! {"
24484        <span>
24485        ˇ
24486        </span>
24487    "});
24488
24489    cx.set_state(indoc! {"
24490        <span><span></span>ˇ</span>
24491    "});
24492    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24493    cx.assert_editor_state(indoc! {"
24494        <span><span></span>
24495        ˇ</span>
24496    "});
24497
24498    cx.set_state(indoc! {"
24499        <span>ˇ
24500        </span>
24501    "});
24502    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24503    cx.assert_editor_state(indoc! {"
24504        <span>
24505        ˇ
24506        </span>
24507    "});
24508}
24509
24510#[gpui::test(iterations = 10)]
24511async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24512    init_test(cx, |_| {});
24513
24514    let fs = FakeFs::new(cx.executor());
24515    fs.insert_tree(
24516        path!("/dir"),
24517        json!({
24518            "a.ts": "a",
24519        }),
24520    )
24521    .await;
24522
24523    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24524    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24525    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24526
24527    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24528    language_registry.add(Arc::new(Language::new(
24529        LanguageConfig {
24530            name: "TypeScript".into(),
24531            matcher: LanguageMatcher {
24532                path_suffixes: vec!["ts".to_string()],
24533                ..Default::default()
24534            },
24535            ..Default::default()
24536        },
24537        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24538    )));
24539    let mut fake_language_servers = language_registry.register_fake_lsp(
24540        "TypeScript",
24541        FakeLspAdapter {
24542            capabilities: lsp::ServerCapabilities {
24543                code_lens_provider: Some(lsp::CodeLensOptions {
24544                    resolve_provider: Some(true),
24545                }),
24546                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24547                    commands: vec!["_the/command".to_string()],
24548                    ..lsp::ExecuteCommandOptions::default()
24549                }),
24550                ..lsp::ServerCapabilities::default()
24551            },
24552            ..FakeLspAdapter::default()
24553        },
24554    );
24555
24556    let editor = workspace
24557        .update(cx, |workspace, window, cx| {
24558            workspace.open_abs_path(
24559                PathBuf::from(path!("/dir/a.ts")),
24560                OpenOptions::default(),
24561                window,
24562                cx,
24563            )
24564        })
24565        .unwrap()
24566        .await
24567        .unwrap()
24568        .downcast::<Editor>()
24569        .unwrap();
24570    cx.executor().run_until_parked();
24571
24572    let fake_server = fake_language_servers.next().await.unwrap();
24573
24574    let buffer = editor.update(cx, |editor, cx| {
24575        editor
24576            .buffer()
24577            .read(cx)
24578            .as_singleton()
24579            .expect("have opened a single file by path")
24580    });
24581
24582    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24583    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24584    drop(buffer_snapshot);
24585    let actions = cx
24586        .update_window(*workspace, |_, window, cx| {
24587            project.code_actions(&buffer, anchor..anchor, window, cx)
24588        })
24589        .unwrap();
24590
24591    fake_server
24592        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24593            Ok(Some(vec![
24594                lsp::CodeLens {
24595                    range: lsp::Range::default(),
24596                    command: Some(lsp::Command {
24597                        title: "Code lens command".to_owned(),
24598                        command: "_the/command".to_owned(),
24599                        arguments: None,
24600                    }),
24601                    data: None,
24602                },
24603                lsp::CodeLens {
24604                    range: lsp::Range::default(),
24605                    command: Some(lsp::Command {
24606                        title: "Command not in capabilities".to_owned(),
24607                        command: "not in capabilities".to_owned(),
24608                        arguments: None,
24609                    }),
24610                    data: None,
24611                },
24612                lsp::CodeLens {
24613                    range: lsp::Range {
24614                        start: lsp::Position {
24615                            line: 1,
24616                            character: 1,
24617                        },
24618                        end: lsp::Position {
24619                            line: 1,
24620                            character: 1,
24621                        },
24622                    },
24623                    command: Some(lsp::Command {
24624                        title: "Command not in range".to_owned(),
24625                        command: "_the/command".to_owned(),
24626                        arguments: None,
24627                    }),
24628                    data: None,
24629                },
24630            ]))
24631        })
24632        .next()
24633        .await;
24634
24635    let actions = actions.await.unwrap();
24636    assert_eq!(
24637        actions.len(),
24638        1,
24639        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24640    );
24641    let action = actions[0].clone();
24642    let apply = project.update(cx, |project, cx| {
24643        project.apply_code_action(buffer.clone(), action, true, cx)
24644    });
24645
24646    // Resolving the code action does not populate its edits. In absence of
24647    // edits, we must execute the given command.
24648    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24649        |mut lens, _| async move {
24650            let lens_command = lens.command.as_mut().expect("should have a command");
24651            assert_eq!(lens_command.title, "Code lens command");
24652            lens_command.arguments = Some(vec![json!("the-argument")]);
24653            Ok(lens)
24654        },
24655    );
24656
24657    // While executing the command, the language server sends the editor
24658    // a `workspaceEdit` request.
24659    fake_server
24660        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24661            let fake = fake_server.clone();
24662            move |params, _| {
24663                assert_eq!(params.command, "_the/command");
24664                let fake = fake.clone();
24665                async move {
24666                    fake.server
24667                        .request::<lsp::request::ApplyWorkspaceEdit>(
24668                            lsp::ApplyWorkspaceEditParams {
24669                                label: None,
24670                                edit: lsp::WorkspaceEdit {
24671                                    changes: Some(
24672                                        [(
24673                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24674                                            vec![lsp::TextEdit {
24675                                                range: lsp::Range::new(
24676                                                    lsp::Position::new(0, 0),
24677                                                    lsp::Position::new(0, 0),
24678                                                ),
24679                                                new_text: "X".into(),
24680                                            }],
24681                                        )]
24682                                        .into_iter()
24683                                        .collect(),
24684                                    ),
24685                                    ..lsp::WorkspaceEdit::default()
24686                                },
24687                            },
24688                        )
24689                        .await
24690                        .into_response()
24691                        .unwrap();
24692                    Ok(Some(json!(null)))
24693                }
24694            }
24695        })
24696        .next()
24697        .await;
24698
24699    // Applying the code lens command returns a project transaction containing the edits
24700    // sent by the language server in its `workspaceEdit` request.
24701    let transaction = apply.await.unwrap();
24702    assert!(transaction.0.contains_key(&buffer));
24703    buffer.update(cx, |buffer, cx| {
24704        assert_eq!(buffer.text(), "Xa");
24705        buffer.undo(cx);
24706        assert_eq!(buffer.text(), "a");
24707    });
24708
24709    let actions_after_edits = cx
24710        .update_window(*workspace, |_, window, cx| {
24711            project.code_actions(&buffer, anchor..anchor, window, cx)
24712        })
24713        .unwrap()
24714        .await
24715        .unwrap();
24716    assert_eq!(
24717        actions, actions_after_edits,
24718        "For the same selection, same code lens actions should be returned"
24719    );
24720
24721    let _responses =
24722        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24723            panic!("No more code lens requests are expected");
24724        });
24725    editor.update_in(cx, |editor, window, cx| {
24726        editor.select_all(&SelectAll, window, cx);
24727    });
24728    cx.executor().run_until_parked();
24729    let new_actions = cx
24730        .update_window(*workspace, |_, window, cx| {
24731            project.code_actions(&buffer, anchor..anchor, window, cx)
24732        })
24733        .unwrap()
24734        .await
24735        .unwrap();
24736    assert_eq!(
24737        actions, new_actions,
24738        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24739    );
24740}
24741
24742#[gpui::test]
24743async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24744    init_test(cx, |_| {});
24745
24746    let fs = FakeFs::new(cx.executor());
24747    let main_text = r#"fn main() {
24748println!("1");
24749println!("2");
24750println!("3");
24751println!("4");
24752println!("5");
24753}"#;
24754    let lib_text = "mod foo {}";
24755    fs.insert_tree(
24756        path!("/a"),
24757        json!({
24758            "lib.rs": lib_text,
24759            "main.rs": main_text,
24760        }),
24761    )
24762    .await;
24763
24764    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24765    let (workspace, cx) =
24766        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24767    let worktree_id = workspace.update(cx, |workspace, cx| {
24768        workspace.project().update(cx, |project, cx| {
24769            project.worktrees(cx).next().unwrap().read(cx).id()
24770        })
24771    });
24772
24773    let expected_ranges = vec![
24774        Point::new(0, 0)..Point::new(0, 0),
24775        Point::new(1, 0)..Point::new(1, 1),
24776        Point::new(2, 0)..Point::new(2, 2),
24777        Point::new(3, 0)..Point::new(3, 3),
24778    ];
24779
24780    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24781    let editor_1 = workspace
24782        .update_in(cx, |workspace, window, cx| {
24783            workspace.open_path(
24784                (worktree_id, rel_path("main.rs")),
24785                Some(pane_1.downgrade()),
24786                true,
24787                window,
24788                cx,
24789            )
24790        })
24791        .unwrap()
24792        .await
24793        .downcast::<Editor>()
24794        .unwrap();
24795    pane_1.update(cx, |pane, cx| {
24796        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24797        open_editor.update(cx, |editor, cx| {
24798            assert_eq!(
24799                editor.display_text(cx),
24800                main_text,
24801                "Original main.rs text on initial open",
24802            );
24803            assert_eq!(
24804                editor
24805                    .selections
24806                    .all::<Point>(&editor.display_snapshot(cx))
24807                    .into_iter()
24808                    .map(|s| s.range())
24809                    .collect::<Vec<_>>(),
24810                vec![Point::zero()..Point::zero()],
24811                "Default selections on initial open",
24812            );
24813        })
24814    });
24815    editor_1.update_in(cx, |editor, window, cx| {
24816        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24817            s.select_ranges(expected_ranges.clone());
24818        });
24819    });
24820
24821    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24822        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24823    });
24824    let editor_2 = workspace
24825        .update_in(cx, |workspace, window, cx| {
24826            workspace.open_path(
24827                (worktree_id, rel_path("main.rs")),
24828                Some(pane_2.downgrade()),
24829                true,
24830                window,
24831                cx,
24832            )
24833        })
24834        .unwrap()
24835        .await
24836        .downcast::<Editor>()
24837        .unwrap();
24838    pane_2.update(cx, |pane, cx| {
24839        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24840        open_editor.update(cx, |editor, cx| {
24841            assert_eq!(
24842                editor.display_text(cx),
24843                main_text,
24844                "Original main.rs text on initial open in another panel",
24845            );
24846            assert_eq!(
24847                editor
24848                    .selections
24849                    .all::<Point>(&editor.display_snapshot(cx))
24850                    .into_iter()
24851                    .map(|s| s.range())
24852                    .collect::<Vec<_>>(),
24853                vec![Point::zero()..Point::zero()],
24854                "Default selections on initial open in another panel",
24855            );
24856        })
24857    });
24858
24859    editor_2.update_in(cx, |editor, window, cx| {
24860        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24861    });
24862
24863    let _other_editor_1 = workspace
24864        .update_in(cx, |workspace, window, cx| {
24865            workspace.open_path(
24866                (worktree_id, rel_path("lib.rs")),
24867                Some(pane_1.downgrade()),
24868                true,
24869                window,
24870                cx,
24871            )
24872        })
24873        .unwrap()
24874        .await
24875        .downcast::<Editor>()
24876        .unwrap();
24877    pane_1
24878        .update_in(cx, |pane, window, cx| {
24879            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24880        })
24881        .await
24882        .unwrap();
24883    drop(editor_1);
24884    pane_1.update(cx, |pane, cx| {
24885        pane.active_item()
24886            .unwrap()
24887            .downcast::<Editor>()
24888            .unwrap()
24889            .update(cx, |editor, cx| {
24890                assert_eq!(
24891                    editor.display_text(cx),
24892                    lib_text,
24893                    "Other file should be open and active",
24894                );
24895            });
24896        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24897    });
24898
24899    let _other_editor_2 = workspace
24900        .update_in(cx, |workspace, window, cx| {
24901            workspace.open_path(
24902                (worktree_id, rel_path("lib.rs")),
24903                Some(pane_2.downgrade()),
24904                true,
24905                window,
24906                cx,
24907            )
24908        })
24909        .unwrap()
24910        .await
24911        .downcast::<Editor>()
24912        .unwrap();
24913    pane_2
24914        .update_in(cx, |pane, window, cx| {
24915            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24916        })
24917        .await
24918        .unwrap();
24919    drop(editor_2);
24920    pane_2.update(cx, |pane, cx| {
24921        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24922        open_editor.update(cx, |editor, cx| {
24923            assert_eq!(
24924                editor.display_text(cx),
24925                lib_text,
24926                "Other file should be open and active in another panel too",
24927            );
24928        });
24929        assert_eq!(
24930            pane.items().count(),
24931            1,
24932            "No other editors should be open in another pane",
24933        );
24934    });
24935
24936    let _editor_1_reopened = workspace
24937        .update_in(cx, |workspace, window, cx| {
24938            workspace.open_path(
24939                (worktree_id, rel_path("main.rs")),
24940                Some(pane_1.downgrade()),
24941                true,
24942                window,
24943                cx,
24944            )
24945        })
24946        .unwrap()
24947        .await
24948        .downcast::<Editor>()
24949        .unwrap();
24950    let _editor_2_reopened = workspace
24951        .update_in(cx, |workspace, window, cx| {
24952            workspace.open_path(
24953                (worktree_id, rel_path("main.rs")),
24954                Some(pane_2.downgrade()),
24955                true,
24956                window,
24957                cx,
24958            )
24959        })
24960        .unwrap()
24961        .await
24962        .downcast::<Editor>()
24963        .unwrap();
24964    pane_1.update(cx, |pane, cx| {
24965        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24966        open_editor.update(cx, |editor, cx| {
24967            assert_eq!(
24968                editor.display_text(cx),
24969                main_text,
24970                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24971            );
24972            assert_eq!(
24973                editor
24974                    .selections
24975                    .all::<Point>(&editor.display_snapshot(cx))
24976                    .into_iter()
24977                    .map(|s| s.range())
24978                    .collect::<Vec<_>>(),
24979                expected_ranges,
24980                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24981            );
24982        })
24983    });
24984    pane_2.update(cx, |pane, cx| {
24985        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24986        open_editor.update(cx, |editor, cx| {
24987            assert_eq!(
24988                editor.display_text(cx),
24989                r#"fn main() {
24990⋯rintln!("1");
24991⋯intln!("2");
24992⋯ntln!("3");
24993println!("4");
24994println!("5");
24995}"#,
24996                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24997            );
24998            assert_eq!(
24999                editor
25000                    .selections
25001                    .all::<Point>(&editor.display_snapshot(cx))
25002                    .into_iter()
25003                    .map(|s| s.range())
25004                    .collect::<Vec<_>>(),
25005                vec![Point::zero()..Point::zero()],
25006                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25007            );
25008        })
25009    });
25010}
25011
25012#[gpui::test]
25013async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25014    init_test(cx, |_| {});
25015
25016    let fs = FakeFs::new(cx.executor());
25017    let main_text = r#"fn main() {
25018println!("1");
25019println!("2");
25020println!("3");
25021println!("4");
25022println!("5");
25023}"#;
25024    let lib_text = "mod foo {}";
25025    fs.insert_tree(
25026        path!("/a"),
25027        json!({
25028            "lib.rs": lib_text,
25029            "main.rs": main_text,
25030        }),
25031    )
25032    .await;
25033
25034    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25035    let (workspace, cx) =
25036        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25037    let worktree_id = workspace.update(cx, |workspace, cx| {
25038        workspace.project().update(cx, |project, cx| {
25039            project.worktrees(cx).next().unwrap().read(cx).id()
25040        })
25041    });
25042
25043    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25044    let editor = workspace
25045        .update_in(cx, |workspace, window, cx| {
25046            workspace.open_path(
25047                (worktree_id, rel_path("main.rs")),
25048                Some(pane.downgrade()),
25049                true,
25050                window,
25051                cx,
25052            )
25053        })
25054        .unwrap()
25055        .await
25056        .downcast::<Editor>()
25057        .unwrap();
25058    pane.update(cx, |pane, cx| {
25059        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25060        open_editor.update(cx, |editor, cx| {
25061            assert_eq!(
25062                editor.display_text(cx),
25063                main_text,
25064                "Original main.rs text on initial open",
25065            );
25066        })
25067    });
25068    editor.update_in(cx, |editor, window, cx| {
25069        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25070    });
25071
25072    cx.update_global(|store: &mut SettingsStore, cx| {
25073        store.update_user_settings(cx, |s| {
25074            s.workspace.restore_on_file_reopen = Some(false);
25075        });
25076    });
25077    editor.update_in(cx, |editor, window, cx| {
25078        editor.fold_ranges(
25079            vec![
25080                Point::new(1, 0)..Point::new(1, 1),
25081                Point::new(2, 0)..Point::new(2, 2),
25082                Point::new(3, 0)..Point::new(3, 3),
25083            ],
25084            false,
25085            window,
25086            cx,
25087        );
25088    });
25089    pane.update_in(cx, |pane, window, cx| {
25090        pane.close_all_items(&CloseAllItems::default(), window, cx)
25091    })
25092    .await
25093    .unwrap();
25094    pane.update(cx, |pane, _| {
25095        assert!(pane.active_item().is_none());
25096    });
25097    cx.update_global(|store: &mut SettingsStore, cx| {
25098        store.update_user_settings(cx, |s| {
25099            s.workspace.restore_on_file_reopen = Some(true);
25100        });
25101    });
25102
25103    let _editor_reopened = workspace
25104        .update_in(cx, |workspace, window, cx| {
25105            workspace.open_path(
25106                (worktree_id, rel_path("main.rs")),
25107                Some(pane.downgrade()),
25108                true,
25109                window,
25110                cx,
25111            )
25112        })
25113        .unwrap()
25114        .await
25115        .downcast::<Editor>()
25116        .unwrap();
25117    pane.update(cx, |pane, cx| {
25118        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25119        open_editor.update(cx, |editor, cx| {
25120            assert_eq!(
25121                editor.display_text(cx),
25122                main_text,
25123                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25124            );
25125        })
25126    });
25127}
25128
25129#[gpui::test]
25130async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25131    struct EmptyModalView {
25132        focus_handle: gpui::FocusHandle,
25133    }
25134    impl EventEmitter<DismissEvent> for EmptyModalView {}
25135    impl Render for EmptyModalView {
25136        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25137            div()
25138        }
25139    }
25140    impl Focusable for EmptyModalView {
25141        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25142            self.focus_handle.clone()
25143        }
25144    }
25145    impl workspace::ModalView for EmptyModalView {}
25146    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25147        EmptyModalView {
25148            focus_handle: cx.focus_handle(),
25149        }
25150    }
25151
25152    init_test(cx, |_| {});
25153
25154    let fs = FakeFs::new(cx.executor());
25155    let project = Project::test(fs, [], cx).await;
25156    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25157    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25158    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25159    let editor = cx.new_window_entity(|window, cx| {
25160        Editor::new(
25161            EditorMode::full(),
25162            buffer,
25163            Some(project.clone()),
25164            window,
25165            cx,
25166        )
25167    });
25168    workspace
25169        .update(cx, |workspace, window, cx| {
25170            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25171        })
25172        .unwrap();
25173    editor.update_in(cx, |editor, window, cx| {
25174        editor.open_context_menu(&OpenContextMenu, window, cx);
25175        assert!(editor.mouse_context_menu.is_some());
25176    });
25177    workspace
25178        .update(cx, |workspace, window, cx| {
25179            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25180        })
25181        .unwrap();
25182    cx.read(|cx| {
25183        assert!(editor.read(cx).mouse_context_menu.is_none());
25184    });
25185}
25186
25187fn set_linked_edit_ranges(
25188    opening: (Point, Point),
25189    closing: (Point, Point),
25190    editor: &mut Editor,
25191    cx: &mut Context<Editor>,
25192) {
25193    let Some((buffer, _)) = editor
25194        .buffer
25195        .read(cx)
25196        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25197    else {
25198        panic!("Failed to get buffer for selection position");
25199    };
25200    let buffer = buffer.read(cx);
25201    let buffer_id = buffer.remote_id();
25202    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25203    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25204    let mut linked_ranges = HashMap::default();
25205    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25206    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25207}
25208
25209#[gpui::test]
25210async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25211    init_test(cx, |_| {});
25212
25213    let fs = FakeFs::new(cx.executor());
25214    fs.insert_file(path!("/file.html"), Default::default())
25215        .await;
25216
25217    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25218
25219    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25220    let html_language = Arc::new(Language::new(
25221        LanguageConfig {
25222            name: "HTML".into(),
25223            matcher: LanguageMatcher {
25224                path_suffixes: vec!["html".to_string()],
25225                ..LanguageMatcher::default()
25226            },
25227            brackets: BracketPairConfig {
25228                pairs: vec![BracketPair {
25229                    start: "<".into(),
25230                    end: ">".into(),
25231                    close: true,
25232                    ..Default::default()
25233                }],
25234                ..Default::default()
25235            },
25236            ..Default::default()
25237        },
25238        Some(tree_sitter_html::LANGUAGE.into()),
25239    ));
25240    language_registry.add(html_language);
25241    let mut fake_servers = language_registry.register_fake_lsp(
25242        "HTML",
25243        FakeLspAdapter {
25244            capabilities: lsp::ServerCapabilities {
25245                completion_provider: Some(lsp::CompletionOptions {
25246                    resolve_provider: Some(true),
25247                    ..Default::default()
25248                }),
25249                ..Default::default()
25250            },
25251            ..Default::default()
25252        },
25253    );
25254
25255    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25256    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25257
25258    let worktree_id = workspace
25259        .update(cx, |workspace, _window, cx| {
25260            workspace.project().update(cx, |project, cx| {
25261                project.worktrees(cx).next().unwrap().read(cx).id()
25262            })
25263        })
25264        .unwrap();
25265    project
25266        .update(cx, |project, cx| {
25267            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25268        })
25269        .await
25270        .unwrap();
25271    let editor = workspace
25272        .update(cx, |workspace, window, cx| {
25273            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25274        })
25275        .unwrap()
25276        .await
25277        .unwrap()
25278        .downcast::<Editor>()
25279        .unwrap();
25280
25281    let fake_server = fake_servers.next().await.unwrap();
25282    editor.update_in(cx, |editor, window, cx| {
25283        editor.set_text("<ad></ad>", window, cx);
25284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25285            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25286        });
25287        set_linked_edit_ranges(
25288            (Point::new(0, 1), Point::new(0, 3)),
25289            (Point::new(0, 6), Point::new(0, 8)),
25290            editor,
25291            cx,
25292        );
25293    });
25294    let mut completion_handle =
25295        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25296            Ok(Some(lsp::CompletionResponse::Array(vec![
25297                lsp::CompletionItem {
25298                    label: "head".to_string(),
25299                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25300                        lsp::InsertReplaceEdit {
25301                            new_text: "head".to_string(),
25302                            insert: lsp::Range::new(
25303                                lsp::Position::new(0, 1),
25304                                lsp::Position::new(0, 3),
25305                            ),
25306                            replace: lsp::Range::new(
25307                                lsp::Position::new(0, 1),
25308                                lsp::Position::new(0, 3),
25309                            ),
25310                        },
25311                    )),
25312                    ..Default::default()
25313                },
25314            ])))
25315        });
25316    editor.update_in(cx, |editor, window, cx| {
25317        editor.show_completions(&ShowCompletions, window, cx);
25318    });
25319    cx.run_until_parked();
25320    completion_handle.next().await.unwrap();
25321    editor.update(cx, |editor, _| {
25322        assert!(
25323            editor.context_menu_visible(),
25324            "Completion menu should be visible"
25325        );
25326    });
25327    editor.update_in(cx, |editor, window, cx| {
25328        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25329    });
25330    cx.executor().run_until_parked();
25331    editor.update(cx, |editor, cx| {
25332        assert_eq!(editor.text(cx), "<head></head>");
25333    });
25334}
25335
25336#[gpui::test]
25337async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25338    init_test(cx, |_| {});
25339
25340    let mut cx = EditorTestContext::new(cx).await;
25341    let language = Arc::new(Language::new(
25342        LanguageConfig {
25343            name: "TSX".into(),
25344            matcher: LanguageMatcher {
25345                path_suffixes: vec!["tsx".to_string()],
25346                ..LanguageMatcher::default()
25347            },
25348            brackets: BracketPairConfig {
25349                pairs: vec![BracketPair {
25350                    start: "<".into(),
25351                    end: ">".into(),
25352                    close: true,
25353                    ..Default::default()
25354                }],
25355                ..Default::default()
25356            },
25357            linked_edit_characters: HashSet::from_iter(['.']),
25358            ..Default::default()
25359        },
25360        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25361    ));
25362    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25363
25364    // Test typing > does not extend linked pair
25365    cx.set_state("<divˇ<div></div>");
25366    cx.update_editor(|editor, _, cx| {
25367        set_linked_edit_ranges(
25368            (Point::new(0, 1), Point::new(0, 4)),
25369            (Point::new(0, 11), Point::new(0, 14)),
25370            editor,
25371            cx,
25372        );
25373    });
25374    cx.update_editor(|editor, window, cx| {
25375        editor.handle_input(">", window, cx);
25376    });
25377    cx.assert_editor_state("<div>ˇ<div></div>");
25378
25379    // Test typing . do extend linked pair
25380    cx.set_state("<Animatedˇ></Animated>");
25381    cx.update_editor(|editor, _, cx| {
25382        set_linked_edit_ranges(
25383            (Point::new(0, 1), Point::new(0, 9)),
25384            (Point::new(0, 12), Point::new(0, 20)),
25385            editor,
25386            cx,
25387        );
25388    });
25389    cx.update_editor(|editor, window, cx| {
25390        editor.handle_input(".", window, cx);
25391    });
25392    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25393    cx.update_editor(|editor, _, cx| {
25394        set_linked_edit_ranges(
25395            (Point::new(0, 1), Point::new(0, 10)),
25396            (Point::new(0, 13), Point::new(0, 21)),
25397            editor,
25398            cx,
25399        );
25400    });
25401    cx.update_editor(|editor, window, cx| {
25402        editor.handle_input("V", window, cx);
25403    });
25404    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25405}
25406
25407#[gpui::test]
25408async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25409    init_test(cx, |_| {});
25410
25411    let fs = FakeFs::new(cx.executor());
25412    fs.insert_tree(
25413        path!("/root"),
25414        json!({
25415            "a": {
25416                "main.rs": "fn main() {}",
25417            },
25418            "foo": {
25419                "bar": {
25420                    "external_file.rs": "pub mod external {}",
25421                }
25422            }
25423        }),
25424    )
25425    .await;
25426
25427    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25428    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25429    language_registry.add(rust_lang());
25430    let _fake_servers = language_registry.register_fake_lsp(
25431        "Rust",
25432        FakeLspAdapter {
25433            ..FakeLspAdapter::default()
25434        },
25435    );
25436    let (workspace, cx) =
25437        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25438    let worktree_id = workspace.update(cx, |workspace, cx| {
25439        workspace.project().update(cx, |project, cx| {
25440            project.worktrees(cx).next().unwrap().read(cx).id()
25441        })
25442    });
25443
25444    let assert_language_servers_count =
25445        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25446            project.update(cx, |project, cx| {
25447                let current = project
25448                    .lsp_store()
25449                    .read(cx)
25450                    .as_local()
25451                    .unwrap()
25452                    .language_servers
25453                    .len();
25454                assert_eq!(expected, current, "{context}");
25455            });
25456        };
25457
25458    assert_language_servers_count(
25459        0,
25460        "No servers should be running before any file is open",
25461        cx,
25462    );
25463    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25464    let main_editor = workspace
25465        .update_in(cx, |workspace, window, cx| {
25466            workspace.open_path(
25467                (worktree_id, rel_path("main.rs")),
25468                Some(pane.downgrade()),
25469                true,
25470                window,
25471                cx,
25472            )
25473        })
25474        .unwrap()
25475        .await
25476        .downcast::<Editor>()
25477        .unwrap();
25478    pane.update(cx, |pane, cx| {
25479        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25480        open_editor.update(cx, |editor, cx| {
25481            assert_eq!(
25482                editor.display_text(cx),
25483                "fn main() {}",
25484                "Original main.rs text on initial open",
25485            );
25486        });
25487        assert_eq!(open_editor, main_editor);
25488    });
25489    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25490
25491    let external_editor = workspace
25492        .update_in(cx, |workspace, window, cx| {
25493            workspace.open_abs_path(
25494                PathBuf::from("/root/foo/bar/external_file.rs"),
25495                OpenOptions::default(),
25496                window,
25497                cx,
25498            )
25499        })
25500        .await
25501        .expect("opening external file")
25502        .downcast::<Editor>()
25503        .expect("downcasted external file's open element to editor");
25504    pane.update(cx, |pane, cx| {
25505        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25506        open_editor.update(cx, |editor, cx| {
25507            assert_eq!(
25508                editor.display_text(cx),
25509                "pub mod external {}",
25510                "External file is open now",
25511            );
25512        });
25513        assert_eq!(open_editor, external_editor);
25514    });
25515    assert_language_servers_count(
25516        1,
25517        "Second, external, *.rs file should join the existing server",
25518        cx,
25519    );
25520
25521    pane.update_in(cx, |pane, window, cx| {
25522        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25523    })
25524    .await
25525    .unwrap();
25526    pane.update_in(cx, |pane, window, cx| {
25527        pane.navigate_backward(&Default::default(), window, cx);
25528    });
25529    cx.run_until_parked();
25530    pane.update(cx, |pane, cx| {
25531        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25532        open_editor.update(cx, |editor, cx| {
25533            assert_eq!(
25534                editor.display_text(cx),
25535                "pub mod external {}",
25536                "External file is open now",
25537            );
25538        });
25539    });
25540    assert_language_servers_count(
25541        1,
25542        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25543        cx,
25544    );
25545
25546    cx.update(|_, cx| {
25547        workspace::reload(cx);
25548    });
25549    assert_language_servers_count(
25550        1,
25551        "After reloading the worktree with local and external files opened, only one project should be started",
25552        cx,
25553    );
25554}
25555
25556#[gpui::test]
25557async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25558    init_test(cx, |_| {});
25559
25560    let mut cx = EditorTestContext::new(cx).await;
25561    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25562    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25563
25564    // test cursor move to start of each line on tab
25565    // for `if`, `elif`, `else`, `while`, `with` and `for`
25566    cx.set_state(indoc! {"
25567        def main():
25568        ˇ    for item in items:
25569        ˇ        while item.active:
25570        ˇ            if item.value > 10:
25571        ˇ                continue
25572        ˇ            elif item.value < 0:
25573        ˇ                break
25574        ˇ            else:
25575        ˇ                with item.context() as ctx:
25576        ˇ                    yield count
25577        ˇ        else:
25578        ˇ            log('while else')
25579        ˇ    else:
25580        ˇ        log('for else')
25581    "});
25582    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25583    cx.wait_for_autoindent_applied().await;
25584    cx.assert_editor_state(indoc! {"
25585        def main():
25586            ˇfor item in items:
25587                ˇwhile item.active:
25588                    ˇif item.value > 10:
25589                        ˇcontinue
25590                    ˇelif item.value < 0:
25591                        ˇbreak
25592                    ˇelse:
25593                        ˇwith item.context() as ctx:
25594                            ˇyield count
25595                ˇelse:
25596                    ˇlog('while else')
25597            ˇelse:
25598                ˇlog('for else')
25599    "});
25600    // test relative indent is preserved when tab
25601    // for `if`, `elif`, `else`, `while`, `with` and `for`
25602    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25603    cx.wait_for_autoindent_applied().await;
25604    cx.assert_editor_state(indoc! {"
25605        def main():
25606                ˇfor item in items:
25607                    ˇwhile item.active:
25608                        ˇif item.value > 10:
25609                            ˇcontinue
25610                        ˇelif item.value < 0:
25611                            ˇbreak
25612                        ˇelse:
25613                            ˇwith item.context() as ctx:
25614                                ˇyield count
25615                    ˇelse:
25616                        ˇlog('while else')
25617                ˇelse:
25618                    ˇlog('for else')
25619    "});
25620
25621    // test cursor move to start of each line on tab
25622    // for `try`, `except`, `else`, `finally`, `match` and `def`
25623    cx.set_state(indoc! {"
25624        def main():
25625        ˇ    try:
25626        ˇ        fetch()
25627        ˇ    except ValueError:
25628        ˇ        handle_error()
25629        ˇ    else:
25630        ˇ        match value:
25631        ˇ            case _:
25632        ˇ    finally:
25633        ˇ        def status():
25634        ˇ            return 0
25635    "});
25636    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25637    cx.wait_for_autoindent_applied().await;
25638    cx.assert_editor_state(indoc! {"
25639        def main():
25640            ˇtry:
25641                ˇfetch()
25642            ˇexcept ValueError:
25643                ˇhandle_error()
25644            ˇelse:
25645                ˇmatch value:
25646                    ˇcase _:
25647            ˇfinally:
25648                ˇdef status():
25649                    ˇreturn 0
25650    "});
25651    // test relative indent is preserved when tab
25652    // for `try`, `except`, `else`, `finally`, `match` and `def`
25653    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25654    cx.wait_for_autoindent_applied().await;
25655    cx.assert_editor_state(indoc! {"
25656        def main():
25657                ˇtry:
25658                    ˇfetch()
25659                ˇexcept ValueError:
25660                    ˇhandle_error()
25661                ˇelse:
25662                    ˇmatch value:
25663                        ˇcase _:
25664                ˇfinally:
25665                    ˇdef status():
25666                        ˇreturn 0
25667    "});
25668}
25669
25670#[gpui::test]
25671async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25672    init_test(cx, |_| {});
25673
25674    let mut cx = EditorTestContext::new(cx).await;
25675    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25676    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25677
25678    // test `else` auto outdents when typed inside `if` block
25679    cx.set_state(indoc! {"
25680        def main():
25681            if i == 2:
25682                return
25683                ˇ
25684    "});
25685    cx.update_editor(|editor, window, cx| {
25686        editor.handle_input("else:", window, cx);
25687    });
25688    cx.wait_for_autoindent_applied().await;
25689    cx.assert_editor_state(indoc! {"
25690        def main():
25691            if i == 2:
25692                return
25693            else:ˇ
25694    "});
25695
25696    // test `except` auto outdents when typed inside `try` block
25697    cx.set_state(indoc! {"
25698        def main():
25699            try:
25700                i = 2
25701                ˇ
25702    "});
25703    cx.update_editor(|editor, window, cx| {
25704        editor.handle_input("except:", window, cx);
25705    });
25706    cx.wait_for_autoindent_applied().await;
25707    cx.assert_editor_state(indoc! {"
25708        def main():
25709            try:
25710                i = 2
25711            except:ˇ
25712    "});
25713
25714    // test `else` auto outdents when typed inside `except` block
25715    cx.set_state(indoc! {"
25716        def main():
25717            try:
25718                i = 2
25719            except:
25720                j = 2
25721                ˇ
25722    "});
25723    cx.update_editor(|editor, window, cx| {
25724        editor.handle_input("else:", window, cx);
25725    });
25726    cx.wait_for_autoindent_applied().await;
25727    cx.assert_editor_state(indoc! {"
25728        def main():
25729            try:
25730                i = 2
25731            except:
25732                j = 2
25733            else:ˇ
25734    "});
25735
25736    // test `finally` auto outdents when typed inside `else` block
25737    cx.set_state(indoc! {"
25738        def main():
25739            try:
25740                i = 2
25741            except:
25742                j = 2
25743            else:
25744                k = 2
25745                ˇ
25746    "});
25747    cx.update_editor(|editor, window, cx| {
25748        editor.handle_input("finally:", window, cx);
25749    });
25750    cx.wait_for_autoindent_applied().await;
25751    cx.assert_editor_state(indoc! {"
25752        def main():
25753            try:
25754                i = 2
25755            except:
25756                j = 2
25757            else:
25758                k = 2
25759            finally:ˇ
25760    "});
25761
25762    // test `else` does not outdents when typed inside `except` block right after for block
25763    cx.set_state(indoc! {"
25764        def main():
25765            try:
25766                i = 2
25767            except:
25768                for i in range(n):
25769                    pass
25770                ˇ
25771    "});
25772    cx.update_editor(|editor, window, cx| {
25773        editor.handle_input("else:", window, cx);
25774    });
25775    cx.wait_for_autoindent_applied().await;
25776    cx.assert_editor_state(indoc! {"
25777        def main():
25778            try:
25779                i = 2
25780            except:
25781                for i in range(n):
25782                    pass
25783                else:ˇ
25784    "});
25785
25786    // test `finally` auto outdents when typed inside `else` block right after for block
25787    cx.set_state(indoc! {"
25788        def main():
25789            try:
25790                i = 2
25791            except:
25792                j = 2
25793            else:
25794                for i in range(n):
25795                    pass
25796                ˇ
25797    "});
25798    cx.update_editor(|editor, window, cx| {
25799        editor.handle_input("finally:", window, cx);
25800    });
25801    cx.wait_for_autoindent_applied().await;
25802    cx.assert_editor_state(indoc! {"
25803        def main():
25804            try:
25805                i = 2
25806            except:
25807                j = 2
25808            else:
25809                for i in range(n):
25810                    pass
25811            finally:ˇ
25812    "});
25813
25814    // test `except` outdents to inner "try" block
25815    cx.set_state(indoc! {"
25816        def main():
25817            try:
25818                i = 2
25819                if i == 2:
25820                    try:
25821                        i = 3
25822                        ˇ
25823    "});
25824    cx.update_editor(|editor, window, cx| {
25825        editor.handle_input("except:", window, cx);
25826    });
25827    cx.wait_for_autoindent_applied().await;
25828    cx.assert_editor_state(indoc! {"
25829        def main():
25830            try:
25831                i = 2
25832                if i == 2:
25833                    try:
25834                        i = 3
25835                    except:ˇ
25836    "});
25837
25838    // test `except` outdents to outer "try" block
25839    cx.set_state(indoc! {"
25840        def main():
25841            try:
25842                i = 2
25843                if i == 2:
25844                    try:
25845                        i = 3
25846                ˇ
25847    "});
25848    cx.update_editor(|editor, window, cx| {
25849        editor.handle_input("except:", window, cx);
25850    });
25851    cx.wait_for_autoindent_applied().await;
25852    cx.assert_editor_state(indoc! {"
25853        def main():
25854            try:
25855                i = 2
25856                if i == 2:
25857                    try:
25858                        i = 3
25859            except:ˇ
25860    "});
25861
25862    // test `else` stays at correct indent when typed after `for` block
25863    cx.set_state(indoc! {"
25864        def main():
25865            for i in range(10):
25866                if i == 3:
25867                    break
25868            ˇ
25869    "});
25870    cx.update_editor(|editor, window, cx| {
25871        editor.handle_input("else:", window, cx);
25872    });
25873    cx.wait_for_autoindent_applied().await;
25874    cx.assert_editor_state(indoc! {"
25875        def main():
25876            for i in range(10):
25877                if i == 3:
25878                    break
25879            else:ˇ
25880    "});
25881
25882    // test does not outdent on typing after line with square brackets
25883    cx.set_state(indoc! {"
25884        def f() -> list[str]:
25885            ˇ
25886    "});
25887    cx.update_editor(|editor, window, cx| {
25888        editor.handle_input("a", window, cx);
25889    });
25890    cx.wait_for_autoindent_applied().await;
25891    cx.assert_editor_state(indoc! {"
25892        def f() -> list[str]:
2589325894    "});
25895
25896    // test does not outdent on typing : after case keyword
25897    cx.set_state(indoc! {"
25898        match 1:
25899            caseˇ
25900    "});
25901    cx.update_editor(|editor, window, cx| {
25902        editor.handle_input(":", window, cx);
25903    });
25904    cx.wait_for_autoindent_applied().await;
25905    cx.assert_editor_state(indoc! {"
25906        match 1:
25907            case:ˇ
25908    "});
25909}
25910
25911#[gpui::test]
25912async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25913    init_test(cx, |_| {});
25914    update_test_language_settings(cx, |settings| {
25915        settings.defaults.extend_comment_on_newline = Some(false);
25916    });
25917    let mut cx = EditorTestContext::new(cx).await;
25918    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25919    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25920
25921    // test correct indent after newline on comment
25922    cx.set_state(indoc! {"
25923        # COMMENT:ˇ
25924    "});
25925    cx.update_editor(|editor, window, cx| {
25926        editor.newline(&Newline, window, cx);
25927    });
25928    cx.wait_for_autoindent_applied().await;
25929    cx.assert_editor_state(indoc! {"
25930        # COMMENT:
25931        ˇ
25932    "});
25933
25934    // test correct indent after newline in brackets
25935    cx.set_state(indoc! {"
25936        {ˇ}
25937    "});
25938    cx.update_editor(|editor, window, cx| {
25939        editor.newline(&Newline, window, cx);
25940    });
25941    cx.wait_for_autoindent_applied().await;
25942    cx.assert_editor_state(indoc! {"
25943        {
25944            ˇ
25945        }
25946    "});
25947
25948    cx.set_state(indoc! {"
25949        (ˇ)
25950    "});
25951    cx.update_editor(|editor, window, cx| {
25952        editor.newline(&Newline, window, cx);
25953    });
25954    cx.run_until_parked();
25955    cx.assert_editor_state(indoc! {"
25956        (
25957            ˇ
25958        )
25959    "});
25960
25961    // do not indent after empty lists or dictionaries
25962    cx.set_state(indoc! {"
25963        a = []ˇ
25964    "});
25965    cx.update_editor(|editor, window, cx| {
25966        editor.newline(&Newline, window, cx);
25967    });
25968    cx.run_until_parked();
25969    cx.assert_editor_state(indoc! {"
25970        a = []
25971        ˇ
25972    "});
25973}
25974
25975#[gpui::test]
25976async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
25977    init_test(cx, |_| {});
25978
25979    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
25980    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
25981    language_registry.add(markdown_lang());
25982    language_registry.add(python_lang);
25983
25984    let mut cx = EditorTestContext::new(cx).await;
25985    cx.update_buffer(|buffer, cx| {
25986        buffer.set_language_registry(language_registry);
25987        buffer.set_language(Some(markdown_lang()), cx);
25988    });
25989
25990    // Test that `else:` correctly outdents to match `if:` inside the Python code block
25991    cx.set_state(indoc! {"
25992        # Heading
25993
25994        ```python
25995        def main():
25996            if condition:
25997                pass
25998                ˇ
25999        ```
26000    "});
26001    cx.update_editor(|editor, window, cx| {
26002        editor.handle_input("else:", window, cx);
26003    });
26004    cx.run_until_parked();
26005    cx.assert_editor_state(indoc! {"
26006        # Heading
26007
26008        ```python
26009        def main():
26010            if condition:
26011                pass
26012            else:ˇ
26013        ```
26014    "});
26015}
26016
26017#[gpui::test]
26018async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26019    init_test(cx, |_| {});
26020
26021    let mut cx = EditorTestContext::new(cx).await;
26022    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26023    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26024
26025    // test cursor move to start of each line on tab
26026    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26027    cx.set_state(indoc! {"
26028        function main() {
26029        ˇ    for item in $items; do
26030        ˇ        while [ -n \"$item\" ]; do
26031        ˇ            if [ \"$value\" -gt 10 ]; then
26032        ˇ                continue
26033        ˇ            elif [ \"$value\" -lt 0 ]; then
26034        ˇ                break
26035        ˇ            else
26036        ˇ                echo \"$item\"
26037        ˇ            fi
26038        ˇ        done
26039        ˇ    done
26040        ˇ}
26041    "});
26042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26043    cx.wait_for_autoindent_applied().await;
26044    cx.assert_editor_state(indoc! {"
26045        function main() {
26046            ˇfor item in $items; do
26047                ˇwhile [ -n \"$item\" ]; do
26048                    ˇif [ \"$value\" -gt 10 ]; then
26049                        ˇcontinue
26050                    ˇelif [ \"$value\" -lt 0 ]; then
26051                        ˇbreak
26052                    ˇelse
26053                        ˇecho \"$item\"
26054                    ˇfi
26055                ˇdone
26056            ˇdone
26057        ˇ}
26058    "});
26059    // test relative indent is preserved when tab
26060    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26061    cx.wait_for_autoindent_applied().await;
26062    cx.assert_editor_state(indoc! {"
26063        function main() {
26064                ˇfor item in $items; do
26065                    ˇwhile [ -n \"$item\" ]; do
26066                        ˇif [ \"$value\" -gt 10 ]; then
26067                            ˇcontinue
26068                        ˇelif [ \"$value\" -lt 0 ]; then
26069                            ˇbreak
26070                        ˇelse
26071                            ˇecho \"$item\"
26072                        ˇfi
26073                    ˇdone
26074                ˇdone
26075            ˇ}
26076    "});
26077
26078    // test cursor move to start of each line on tab
26079    // for `case` statement with patterns
26080    cx.set_state(indoc! {"
26081        function handle() {
26082        ˇ    case \"$1\" in
26083        ˇ        start)
26084        ˇ            echo \"a\"
26085        ˇ            ;;
26086        ˇ        stop)
26087        ˇ            echo \"b\"
26088        ˇ            ;;
26089        ˇ        *)
26090        ˇ            echo \"c\"
26091        ˇ            ;;
26092        ˇ    esac
26093        ˇ}
26094    "});
26095    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26096    cx.wait_for_autoindent_applied().await;
26097    cx.assert_editor_state(indoc! {"
26098        function handle() {
26099            ˇcase \"$1\" in
26100                ˇstart)
26101                    ˇecho \"a\"
26102                    ˇ;;
26103                ˇstop)
26104                    ˇecho \"b\"
26105                    ˇ;;
26106                ˇ*)
26107                    ˇecho \"c\"
26108                    ˇ;;
26109            ˇesac
26110        ˇ}
26111    "});
26112}
26113
26114#[gpui::test]
26115async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26116    init_test(cx, |_| {});
26117
26118    let mut cx = EditorTestContext::new(cx).await;
26119    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26120    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26121
26122    // test indents on comment insert
26123    cx.set_state(indoc! {"
26124        function main() {
26125        ˇ    for item in $items; do
26126        ˇ        while [ -n \"$item\" ]; do
26127        ˇ            if [ \"$value\" -gt 10 ]; then
26128        ˇ                continue
26129        ˇ            elif [ \"$value\" -lt 0 ]; then
26130        ˇ                break
26131        ˇ            else
26132        ˇ                echo \"$item\"
26133        ˇ            fi
26134        ˇ        done
26135        ˇ    done
26136        ˇ}
26137    "});
26138    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26139    cx.wait_for_autoindent_applied().await;
26140    cx.assert_editor_state(indoc! {"
26141        function main() {
26142        #ˇ    for item in $items; do
26143        #ˇ        while [ -n \"$item\" ]; do
26144        #ˇ            if [ \"$value\" -gt 10 ]; then
26145        #ˇ                continue
26146        #ˇ            elif [ \"$value\" -lt 0 ]; then
26147        #ˇ                break
26148        #ˇ            else
26149        #ˇ                echo \"$item\"
26150        #ˇ            fi
26151        #ˇ        done
26152        #ˇ    done
26153        #ˇ}
26154    "});
26155}
26156
26157#[gpui::test]
26158async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26159    init_test(cx, |_| {});
26160
26161    let mut cx = EditorTestContext::new(cx).await;
26162    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26163    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26164
26165    // test `else` auto outdents when typed inside `if` block
26166    cx.set_state(indoc! {"
26167        if [ \"$1\" = \"test\" ]; then
26168            echo \"foo bar\"
26169            ˇ
26170    "});
26171    cx.update_editor(|editor, window, cx| {
26172        editor.handle_input("else", window, cx);
26173    });
26174    cx.wait_for_autoindent_applied().await;
26175    cx.assert_editor_state(indoc! {"
26176        if [ \"$1\" = \"test\" ]; then
26177            echo \"foo bar\"
26178        elseˇ
26179    "});
26180
26181    // test `elif` auto outdents when typed inside `if` block
26182    cx.set_state(indoc! {"
26183        if [ \"$1\" = \"test\" ]; then
26184            echo \"foo bar\"
26185            ˇ
26186    "});
26187    cx.update_editor(|editor, window, cx| {
26188        editor.handle_input("elif", window, cx);
26189    });
26190    cx.wait_for_autoindent_applied().await;
26191    cx.assert_editor_state(indoc! {"
26192        if [ \"$1\" = \"test\" ]; then
26193            echo \"foo bar\"
26194        elifˇ
26195    "});
26196
26197    // test `fi` auto outdents when typed inside `else` block
26198    cx.set_state(indoc! {"
26199        if [ \"$1\" = \"test\" ]; then
26200            echo \"foo bar\"
26201        else
26202            echo \"bar baz\"
26203            ˇ
26204    "});
26205    cx.update_editor(|editor, window, cx| {
26206        editor.handle_input("fi", window, cx);
26207    });
26208    cx.wait_for_autoindent_applied().await;
26209    cx.assert_editor_state(indoc! {"
26210        if [ \"$1\" = \"test\" ]; then
26211            echo \"foo bar\"
26212        else
26213            echo \"bar baz\"
26214        fiˇ
26215    "});
26216
26217    // test `done` auto outdents when typed inside `while` block
26218    cx.set_state(indoc! {"
26219        while read line; do
26220            echo \"$line\"
26221            ˇ
26222    "});
26223    cx.update_editor(|editor, window, cx| {
26224        editor.handle_input("done", window, cx);
26225    });
26226    cx.wait_for_autoindent_applied().await;
26227    cx.assert_editor_state(indoc! {"
26228        while read line; do
26229            echo \"$line\"
26230        doneˇ
26231    "});
26232
26233    // test `done` auto outdents when typed inside `for` block
26234    cx.set_state(indoc! {"
26235        for file in *.txt; do
26236            cat \"$file\"
26237            ˇ
26238    "});
26239    cx.update_editor(|editor, window, cx| {
26240        editor.handle_input("done", window, cx);
26241    });
26242    cx.wait_for_autoindent_applied().await;
26243    cx.assert_editor_state(indoc! {"
26244        for file in *.txt; do
26245            cat \"$file\"
26246        doneˇ
26247    "});
26248
26249    // test `esac` auto outdents when typed inside `case` block
26250    cx.set_state(indoc! {"
26251        case \"$1\" in
26252            start)
26253                echo \"foo bar\"
26254                ;;
26255            stop)
26256                echo \"bar baz\"
26257                ;;
26258            ˇ
26259    "});
26260    cx.update_editor(|editor, window, cx| {
26261        editor.handle_input("esac", window, cx);
26262    });
26263    cx.wait_for_autoindent_applied().await;
26264    cx.assert_editor_state(indoc! {"
26265        case \"$1\" in
26266            start)
26267                echo \"foo bar\"
26268                ;;
26269            stop)
26270                echo \"bar baz\"
26271                ;;
26272        esacˇ
26273    "});
26274
26275    // test `*)` auto outdents when typed inside `case` block
26276    cx.set_state(indoc! {"
26277        case \"$1\" in
26278            start)
26279                echo \"foo bar\"
26280                ;;
26281                ˇ
26282    "});
26283    cx.update_editor(|editor, window, cx| {
26284        editor.handle_input("*)", window, cx);
26285    });
26286    cx.wait_for_autoindent_applied().await;
26287    cx.assert_editor_state(indoc! {"
26288        case \"$1\" in
26289            start)
26290                echo \"foo bar\"
26291                ;;
26292            *)ˇ
26293    "});
26294
26295    // test `fi` outdents to correct level with nested if blocks
26296    cx.set_state(indoc! {"
26297        if [ \"$1\" = \"test\" ]; then
26298            echo \"outer if\"
26299            if [ \"$2\" = \"debug\" ]; then
26300                echo \"inner if\"
26301                ˇ
26302    "});
26303    cx.update_editor(|editor, window, cx| {
26304        editor.handle_input("fi", window, cx);
26305    });
26306    cx.wait_for_autoindent_applied().await;
26307    cx.assert_editor_state(indoc! {"
26308        if [ \"$1\" = \"test\" ]; then
26309            echo \"outer if\"
26310            if [ \"$2\" = \"debug\" ]; then
26311                echo \"inner if\"
26312            fiˇ
26313    "});
26314}
26315
26316#[gpui::test]
26317async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26318    init_test(cx, |_| {});
26319    update_test_language_settings(cx, |settings| {
26320        settings.defaults.extend_comment_on_newline = Some(false);
26321    });
26322    let mut cx = EditorTestContext::new(cx).await;
26323    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26324    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26325
26326    // test correct indent after newline on comment
26327    cx.set_state(indoc! {"
26328        # COMMENT:ˇ
26329    "});
26330    cx.update_editor(|editor, window, cx| {
26331        editor.newline(&Newline, window, cx);
26332    });
26333    cx.wait_for_autoindent_applied().await;
26334    cx.assert_editor_state(indoc! {"
26335        # COMMENT:
26336        ˇ
26337    "});
26338
26339    // test correct indent after newline after `then`
26340    cx.set_state(indoc! {"
26341
26342        if [ \"$1\" = \"test\" ]; thenˇ
26343    "});
26344    cx.update_editor(|editor, window, cx| {
26345        editor.newline(&Newline, window, cx);
26346    });
26347    cx.wait_for_autoindent_applied().await;
26348    cx.assert_editor_state(indoc! {"
26349
26350        if [ \"$1\" = \"test\" ]; then
26351            ˇ
26352    "});
26353
26354    // test correct indent after newline after `else`
26355    cx.set_state(indoc! {"
26356        if [ \"$1\" = \"test\" ]; then
26357        elseˇ
26358    "});
26359    cx.update_editor(|editor, window, cx| {
26360        editor.newline(&Newline, window, cx);
26361    });
26362    cx.wait_for_autoindent_applied().await;
26363    cx.assert_editor_state(indoc! {"
26364        if [ \"$1\" = \"test\" ]; then
26365        else
26366            ˇ
26367    "});
26368
26369    // test correct indent after newline after `elif`
26370    cx.set_state(indoc! {"
26371        if [ \"$1\" = \"test\" ]; then
26372        elifˇ
26373    "});
26374    cx.update_editor(|editor, window, cx| {
26375        editor.newline(&Newline, window, cx);
26376    });
26377    cx.wait_for_autoindent_applied().await;
26378    cx.assert_editor_state(indoc! {"
26379        if [ \"$1\" = \"test\" ]; then
26380        elif
26381            ˇ
26382    "});
26383
26384    // test correct indent after newline after `do`
26385    cx.set_state(indoc! {"
26386        for file in *.txt; doˇ
26387    "});
26388    cx.update_editor(|editor, window, cx| {
26389        editor.newline(&Newline, window, cx);
26390    });
26391    cx.wait_for_autoindent_applied().await;
26392    cx.assert_editor_state(indoc! {"
26393        for file in *.txt; do
26394            ˇ
26395    "});
26396
26397    // test correct indent after newline after case pattern
26398    cx.set_state(indoc! {"
26399        case \"$1\" in
26400            start)ˇ
26401    "});
26402    cx.update_editor(|editor, window, cx| {
26403        editor.newline(&Newline, window, cx);
26404    });
26405    cx.wait_for_autoindent_applied().await;
26406    cx.assert_editor_state(indoc! {"
26407        case \"$1\" in
26408            start)
26409                ˇ
26410    "});
26411
26412    // test correct indent after newline after case pattern
26413    cx.set_state(indoc! {"
26414        case \"$1\" in
26415            start)
26416                ;;
26417            *)ˇ
26418    "});
26419    cx.update_editor(|editor, window, cx| {
26420        editor.newline(&Newline, window, cx);
26421    });
26422    cx.wait_for_autoindent_applied().await;
26423    cx.assert_editor_state(indoc! {"
26424        case \"$1\" in
26425            start)
26426                ;;
26427            *)
26428                ˇ
26429    "});
26430
26431    // test correct indent after newline after function opening brace
26432    cx.set_state(indoc! {"
26433        function test() {ˇ}
26434    "});
26435    cx.update_editor(|editor, window, cx| {
26436        editor.newline(&Newline, window, cx);
26437    });
26438    cx.wait_for_autoindent_applied().await;
26439    cx.assert_editor_state(indoc! {"
26440        function test() {
26441            ˇ
26442        }
26443    "});
26444
26445    // test no extra indent after semicolon on same line
26446    cx.set_state(indoc! {"
26447        echo \"test\"26448    "});
26449    cx.update_editor(|editor, window, cx| {
26450        editor.newline(&Newline, window, cx);
26451    });
26452    cx.wait_for_autoindent_applied().await;
26453    cx.assert_editor_state(indoc! {"
26454        echo \"test\";
26455        ˇ
26456    "});
26457}
26458
26459fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26460    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26461    point..point
26462}
26463
26464#[track_caller]
26465fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26466    let (text, ranges) = marked_text_ranges(marked_text, true);
26467    assert_eq!(editor.text(cx), text);
26468    assert_eq!(
26469        editor.selections.ranges(&editor.display_snapshot(cx)),
26470        ranges
26471            .iter()
26472            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26473            .collect::<Vec<_>>(),
26474        "Assert selections are {}",
26475        marked_text
26476    );
26477}
26478
26479pub fn handle_signature_help_request(
26480    cx: &mut EditorLspTestContext,
26481    mocked_response: lsp::SignatureHelp,
26482) -> impl Future<Output = ()> + use<> {
26483    let mut request =
26484        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26485            let mocked_response = mocked_response.clone();
26486            async move { Ok(Some(mocked_response)) }
26487        });
26488
26489    async move {
26490        request.next().await;
26491    }
26492}
26493
26494#[track_caller]
26495pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26496    cx.update_editor(|editor, _, _| {
26497        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26498            let entries = menu.entries.borrow();
26499            let entries = entries
26500                .iter()
26501                .map(|entry| entry.string.as_str())
26502                .collect::<Vec<_>>();
26503            assert_eq!(entries, expected);
26504        } else {
26505            panic!("Expected completions menu");
26506        }
26507    });
26508}
26509
26510#[gpui::test]
26511async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26512    init_test(cx, |_| {});
26513    let mut cx = EditorLspTestContext::new_rust(
26514        lsp::ServerCapabilities {
26515            completion_provider: Some(lsp::CompletionOptions {
26516                ..Default::default()
26517            }),
26518            ..Default::default()
26519        },
26520        cx,
26521    )
26522    .await;
26523    cx.lsp
26524        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26525            Ok(Some(lsp::CompletionResponse::Array(vec![
26526                lsp::CompletionItem {
26527                    label: "unsafe".into(),
26528                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26529                        range: lsp::Range {
26530                            start: lsp::Position {
26531                                line: 0,
26532                                character: 9,
26533                            },
26534                            end: lsp::Position {
26535                                line: 0,
26536                                character: 11,
26537                            },
26538                        },
26539                        new_text: "unsafe".to_string(),
26540                    })),
26541                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26542                    ..Default::default()
26543                },
26544            ])))
26545        });
26546
26547    cx.update_editor(|editor, _, cx| {
26548        editor.project().unwrap().update(cx, |project, cx| {
26549            project.snippets().update(cx, |snippets, _cx| {
26550                snippets.add_snippet_for_test(
26551                    None,
26552                    PathBuf::from("test_snippets.json"),
26553                    vec![
26554                        Arc::new(project::snippet_provider::Snippet {
26555                            prefix: vec![
26556                                "unlimited word count".to_string(),
26557                                "unlimit word count".to_string(),
26558                                "unlimited unknown".to_string(),
26559                            ],
26560                            body: "this is many words".to_string(),
26561                            description: Some("description".to_string()),
26562                            name: "multi-word snippet test".to_string(),
26563                        }),
26564                        Arc::new(project::snippet_provider::Snippet {
26565                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26566                            body: "fewer words".to_string(),
26567                            description: Some("alt description".to_string()),
26568                            name: "other name".to_string(),
26569                        }),
26570                        Arc::new(project::snippet_provider::Snippet {
26571                            prefix: vec!["ab aa".to_string()],
26572                            body: "abcd".to_string(),
26573                            description: None,
26574                            name: "alphabet".to_string(),
26575                        }),
26576                    ],
26577                );
26578            });
26579        })
26580    });
26581
26582    let get_completions = |cx: &mut EditorLspTestContext| {
26583        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26584            Some(CodeContextMenu::Completions(context_menu)) => {
26585                let entries = context_menu.entries.borrow();
26586                entries
26587                    .iter()
26588                    .map(|entry| entry.string.clone())
26589                    .collect_vec()
26590            }
26591            _ => vec![],
26592        })
26593    };
26594
26595    // snippets:
26596    //  @foo
26597    //  foo bar
26598    //
26599    // when typing:
26600    //
26601    // when typing:
26602    //  - if I type a symbol "open the completions with snippets only"
26603    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26604    //
26605    // stuff we need:
26606    //  - filtering logic change?
26607    //  - remember how far back the completion started.
26608
26609    let test_cases: &[(&str, &[&str])] = &[
26610        (
26611            "un",
26612            &[
26613                "unsafe",
26614                "unlimit word count",
26615                "unlimited unknown",
26616                "unlimited word count",
26617                "unsnip",
26618            ],
26619        ),
26620        (
26621            "u ",
26622            &[
26623                "unlimit word count",
26624                "unlimited unknown",
26625                "unlimited word count",
26626            ],
26627        ),
26628        ("u a", &["ab aa", "unsafe"]), // unsAfe
26629        (
26630            "u u",
26631            &[
26632                "unsafe",
26633                "unlimit word count",
26634                "unlimited unknown", // ranked highest among snippets
26635                "unlimited word count",
26636                "unsnip",
26637            ],
26638        ),
26639        ("uw c", &["unlimit word count", "unlimited word count"]),
26640        (
26641            "u w",
26642            &[
26643                "unlimit word count",
26644                "unlimited word count",
26645                "unlimited unknown",
26646            ],
26647        ),
26648        ("u w ", &["unlimit word count", "unlimited word count"]),
26649        (
26650            "u ",
26651            &[
26652                "unlimit word count",
26653                "unlimited unknown",
26654                "unlimited word count",
26655            ],
26656        ),
26657        ("wor", &[]),
26658        ("uf", &["unsafe"]),
26659        ("af", &["unsafe"]),
26660        ("afu", &[]),
26661        (
26662            "ue",
26663            &["unsafe", "unlimited unknown", "unlimited word count"],
26664        ),
26665        ("@", &["@few"]),
26666        ("@few", &["@few"]),
26667        ("@ ", &[]),
26668        ("a@", &["@few"]),
26669        ("a@f", &["@few", "unsafe"]),
26670        ("a@fw", &["@few"]),
26671        ("a", &["ab aa", "unsafe"]),
26672        ("aa", &["ab aa"]),
26673        ("aaa", &["ab aa"]),
26674        ("ab", &["ab aa"]),
26675        ("ab ", &["ab aa"]),
26676        ("ab a", &["ab aa", "unsafe"]),
26677        ("ab ab", &["ab aa"]),
26678        ("ab ab aa", &["ab aa"]),
26679    ];
26680
26681    for &(input_to_simulate, expected_completions) in test_cases {
26682        cx.set_state("fn a() { ˇ }\n");
26683        for c in input_to_simulate.split("") {
26684            cx.simulate_input(c);
26685            cx.run_until_parked();
26686        }
26687        let expected_completions = expected_completions
26688            .iter()
26689            .map(|s| s.to_string())
26690            .collect_vec();
26691        assert_eq!(
26692            get_completions(&mut cx),
26693            expected_completions,
26694            "< actual / expected >, input = {input_to_simulate:?}",
26695        );
26696    }
26697}
26698
26699/// Handle completion request passing a marked string specifying where the completion
26700/// should be triggered from using '|' character, what range should be replaced, and what completions
26701/// should be returned using '<' and '>' to delimit the range.
26702///
26703/// Also see `handle_completion_request_with_insert_and_replace`.
26704#[track_caller]
26705pub fn handle_completion_request(
26706    marked_string: &str,
26707    completions: Vec<&'static str>,
26708    is_incomplete: bool,
26709    counter: Arc<AtomicUsize>,
26710    cx: &mut EditorLspTestContext,
26711) -> impl Future<Output = ()> {
26712    let complete_from_marker: TextRangeMarker = '|'.into();
26713    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26714    let (_, mut marked_ranges) = marked_text_ranges_by(
26715        marked_string,
26716        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26717    );
26718
26719    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26720        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26721    ));
26722    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26723    let replace_range =
26724        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26725
26726    let mut request =
26727        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26728            let completions = completions.clone();
26729            counter.fetch_add(1, atomic::Ordering::Release);
26730            async move {
26731                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26732                assert_eq!(
26733                    params.text_document_position.position,
26734                    complete_from_position
26735                );
26736                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26737                    is_incomplete,
26738                    item_defaults: None,
26739                    items: completions
26740                        .iter()
26741                        .map(|completion_text| lsp::CompletionItem {
26742                            label: completion_text.to_string(),
26743                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26744                                range: replace_range,
26745                                new_text: completion_text.to_string(),
26746                            })),
26747                            ..Default::default()
26748                        })
26749                        .collect(),
26750                })))
26751            }
26752        });
26753
26754    async move {
26755        request.next().await;
26756    }
26757}
26758
26759/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26760/// given instead, which also contains an `insert` range.
26761///
26762/// This function uses markers to define ranges:
26763/// - `|` marks the cursor position
26764/// - `<>` marks the replace range
26765/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26766pub fn handle_completion_request_with_insert_and_replace(
26767    cx: &mut EditorLspTestContext,
26768    marked_string: &str,
26769    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26770    counter: Arc<AtomicUsize>,
26771) -> impl Future<Output = ()> {
26772    let complete_from_marker: TextRangeMarker = '|'.into();
26773    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26774    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26775
26776    let (_, mut marked_ranges) = marked_text_ranges_by(
26777        marked_string,
26778        vec![
26779            complete_from_marker.clone(),
26780            replace_range_marker.clone(),
26781            insert_range_marker.clone(),
26782        ],
26783    );
26784
26785    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26786        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26787    ));
26788    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26789    let replace_range =
26790        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26791
26792    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26793        Some(ranges) if !ranges.is_empty() => {
26794            let range1 = ranges[0].clone();
26795            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26796        }
26797        _ => lsp::Range {
26798            start: replace_range.start,
26799            end: complete_from_position,
26800        },
26801    };
26802
26803    let mut request =
26804        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26805            let completions = completions.clone();
26806            counter.fetch_add(1, atomic::Ordering::Release);
26807            async move {
26808                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26809                assert_eq!(
26810                    params.text_document_position.position, complete_from_position,
26811                    "marker `|` position doesn't match",
26812                );
26813                Ok(Some(lsp::CompletionResponse::Array(
26814                    completions
26815                        .iter()
26816                        .map(|(label, new_text)| lsp::CompletionItem {
26817                            label: label.to_string(),
26818                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26819                                lsp::InsertReplaceEdit {
26820                                    insert: insert_range,
26821                                    replace: replace_range,
26822                                    new_text: new_text.to_string(),
26823                                },
26824                            )),
26825                            ..Default::default()
26826                        })
26827                        .collect(),
26828                )))
26829            }
26830        });
26831
26832    async move {
26833        request.next().await;
26834    }
26835}
26836
26837fn handle_resolve_completion_request(
26838    cx: &mut EditorLspTestContext,
26839    edits: Option<Vec<(&'static str, &'static str)>>,
26840) -> impl Future<Output = ()> {
26841    let edits = edits.map(|edits| {
26842        edits
26843            .iter()
26844            .map(|(marked_string, new_text)| {
26845                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26846                let replace_range = cx.to_lsp_range(
26847                    MultiBufferOffset(marked_ranges[0].start)
26848                        ..MultiBufferOffset(marked_ranges[0].end),
26849                );
26850                lsp::TextEdit::new(replace_range, new_text.to_string())
26851            })
26852            .collect::<Vec<_>>()
26853    });
26854
26855    let mut request =
26856        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26857            let edits = edits.clone();
26858            async move {
26859                Ok(lsp::CompletionItem {
26860                    additional_text_edits: edits,
26861                    ..Default::default()
26862                })
26863            }
26864        });
26865
26866    async move {
26867        request.next().await;
26868    }
26869}
26870
26871pub(crate) fn update_test_language_settings(
26872    cx: &mut TestAppContext,
26873    f: impl Fn(&mut AllLanguageSettingsContent),
26874) {
26875    cx.update(|cx| {
26876        SettingsStore::update_global(cx, |store, cx| {
26877            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26878        });
26879    });
26880}
26881
26882pub(crate) fn update_test_project_settings(
26883    cx: &mut TestAppContext,
26884    f: impl Fn(&mut ProjectSettingsContent),
26885) {
26886    cx.update(|cx| {
26887        SettingsStore::update_global(cx, |store, cx| {
26888            store.update_user_settings(cx, |settings| f(&mut settings.project));
26889        });
26890    });
26891}
26892
26893pub(crate) fn update_test_editor_settings(
26894    cx: &mut TestAppContext,
26895    f: impl Fn(&mut EditorSettingsContent),
26896) {
26897    cx.update(|cx| {
26898        SettingsStore::update_global(cx, |store, cx| {
26899            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26900        })
26901    })
26902}
26903
26904pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26905    cx.update(|cx| {
26906        assets::Assets.load_test_fonts(cx);
26907        let store = SettingsStore::test(cx);
26908        cx.set_global(store);
26909        theme::init(theme::LoadThemes::JustBase, cx);
26910        release_channel::init(semver::Version::new(0, 0, 0), cx);
26911        crate::init(cx);
26912    });
26913    zlog::init_test();
26914    update_test_language_settings(cx, f);
26915}
26916
26917#[track_caller]
26918fn assert_hunk_revert(
26919    not_reverted_text_with_selections: &str,
26920    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26921    expected_reverted_text_with_selections: &str,
26922    base_text: &str,
26923    cx: &mut EditorLspTestContext,
26924) {
26925    cx.set_state(not_reverted_text_with_selections);
26926    cx.set_head_text(base_text);
26927    cx.executor().run_until_parked();
26928
26929    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26930        let snapshot = editor.snapshot(window, cx);
26931        let reverted_hunk_statuses = snapshot
26932            .buffer_snapshot()
26933            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26934            .map(|hunk| hunk.status().kind)
26935            .collect::<Vec<_>>();
26936
26937        editor.git_restore(&Default::default(), window, cx);
26938        reverted_hunk_statuses
26939    });
26940    cx.executor().run_until_parked();
26941    cx.assert_editor_state(expected_reverted_text_with_selections);
26942    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26943}
26944
26945#[gpui::test(iterations = 10)]
26946async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26947    init_test(cx, |_| {});
26948
26949    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26950    let counter = diagnostic_requests.clone();
26951
26952    let fs = FakeFs::new(cx.executor());
26953    fs.insert_tree(
26954        path!("/a"),
26955        json!({
26956            "first.rs": "fn main() { let a = 5; }",
26957            "second.rs": "// Test file",
26958        }),
26959    )
26960    .await;
26961
26962    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26963    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26964    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26965
26966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26967    language_registry.add(rust_lang());
26968    let mut fake_servers = language_registry.register_fake_lsp(
26969        "Rust",
26970        FakeLspAdapter {
26971            capabilities: lsp::ServerCapabilities {
26972                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26973                    lsp::DiagnosticOptions {
26974                        identifier: None,
26975                        inter_file_dependencies: true,
26976                        workspace_diagnostics: true,
26977                        work_done_progress_options: Default::default(),
26978                    },
26979                )),
26980                ..Default::default()
26981            },
26982            ..Default::default()
26983        },
26984    );
26985
26986    let editor = workspace
26987        .update(cx, |workspace, window, cx| {
26988            workspace.open_abs_path(
26989                PathBuf::from(path!("/a/first.rs")),
26990                OpenOptions::default(),
26991                window,
26992                cx,
26993            )
26994        })
26995        .unwrap()
26996        .await
26997        .unwrap()
26998        .downcast::<Editor>()
26999        .unwrap();
27000    let fake_server = fake_servers.next().await.unwrap();
27001    let server_id = fake_server.server.server_id();
27002    let mut first_request = fake_server
27003        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27004            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27005            let result_id = Some(new_result_id.to_string());
27006            assert_eq!(
27007                params.text_document.uri,
27008                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27009            );
27010            async move {
27011                Ok(lsp::DocumentDiagnosticReportResult::Report(
27012                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27013                        related_documents: None,
27014                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27015                            items: Vec::new(),
27016                            result_id,
27017                        },
27018                    }),
27019                ))
27020            }
27021        });
27022
27023    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27024        project.update(cx, |project, cx| {
27025            let buffer_id = editor
27026                .read(cx)
27027                .buffer()
27028                .read(cx)
27029                .as_singleton()
27030                .expect("created a singleton buffer")
27031                .read(cx)
27032                .remote_id();
27033            let buffer_result_id = project
27034                .lsp_store()
27035                .read(cx)
27036                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27037            assert_eq!(expected, buffer_result_id);
27038        });
27039    };
27040
27041    ensure_result_id(None, cx);
27042    cx.executor().advance_clock(Duration::from_millis(60));
27043    cx.executor().run_until_parked();
27044    assert_eq!(
27045        diagnostic_requests.load(atomic::Ordering::Acquire),
27046        1,
27047        "Opening file should trigger diagnostic request"
27048    );
27049    first_request
27050        .next()
27051        .await
27052        .expect("should have sent the first diagnostics pull request");
27053    ensure_result_id(Some(SharedString::new("1")), cx);
27054
27055    // Editing should trigger diagnostics
27056    editor.update_in(cx, |editor, window, cx| {
27057        editor.handle_input("2", window, cx)
27058    });
27059    cx.executor().advance_clock(Duration::from_millis(60));
27060    cx.executor().run_until_parked();
27061    assert_eq!(
27062        diagnostic_requests.load(atomic::Ordering::Acquire),
27063        2,
27064        "Editing should trigger diagnostic request"
27065    );
27066    ensure_result_id(Some(SharedString::new("2")), cx);
27067
27068    // Moving cursor should not trigger diagnostic request
27069    editor.update_in(cx, |editor, window, cx| {
27070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27071            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27072        });
27073    });
27074    cx.executor().advance_clock(Duration::from_millis(60));
27075    cx.executor().run_until_parked();
27076    assert_eq!(
27077        diagnostic_requests.load(atomic::Ordering::Acquire),
27078        2,
27079        "Cursor movement should not trigger diagnostic request"
27080    );
27081    ensure_result_id(Some(SharedString::new("2")), cx);
27082    // Multiple rapid edits should be debounced
27083    for _ in 0..5 {
27084        editor.update_in(cx, |editor, window, cx| {
27085            editor.handle_input("x", window, cx)
27086        });
27087    }
27088    cx.executor().advance_clock(Duration::from_millis(60));
27089    cx.executor().run_until_parked();
27090
27091    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27092    assert!(
27093        final_requests <= 4,
27094        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27095    );
27096    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27097}
27098
27099#[gpui::test]
27100async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27101    // Regression test for issue #11671
27102    // Previously, adding a cursor after moving multiple cursors would reset
27103    // the cursor count instead of adding to the existing cursors.
27104    init_test(cx, |_| {});
27105    let mut cx = EditorTestContext::new(cx).await;
27106
27107    // Create a simple buffer with cursor at start
27108    cx.set_state(indoc! {"
27109        ˇaaaa
27110        bbbb
27111        cccc
27112        dddd
27113        eeee
27114        ffff
27115        gggg
27116        hhhh"});
27117
27118    // Add 2 cursors below (so we have 3 total)
27119    cx.update_editor(|editor, window, cx| {
27120        editor.add_selection_below(&Default::default(), window, cx);
27121        editor.add_selection_below(&Default::default(), window, cx);
27122    });
27123
27124    // Verify we have 3 cursors
27125    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27126    assert_eq!(
27127        initial_count, 3,
27128        "Should have 3 cursors after adding 2 below"
27129    );
27130
27131    // Move down one line
27132    cx.update_editor(|editor, window, cx| {
27133        editor.move_down(&MoveDown, window, cx);
27134    });
27135
27136    // Add another cursor below
27137    cx.update_editor(|editor, window, cx| {
27138        editor.add_selection_below(&Default::default(), window, cx);
27139    });
27140
27141    // Should now have 4 cursors (3 original + 1 new)
27142    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27143    assert_eq!(
27144        final_count, 4,
27145        "Should have 4 cursors after moving and adding another"
27146    );
27147}
27148
27149#[gpui::test]
27150async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27151    init_test(cx, |_| {});
27152
27153    let mut cx = EditorTestContext::new(cx).await;
27154
27155    cx.set_state(indoc!(
27156        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27157           Second line here"#
27158    ));
27159
27160    cx.update_editor(|editor, window, cx| {
27161        // Enable soft wrapping with a narrow width to force soft wrapping and
27162        // confirm that more than 2 rows are being displayed.
27163        editor.set_wrap_width(Some(100.0.into()), cx);
27164        assert!(editor.display_text(cx).lines().count() > 2);
27165
27166        editor.add_selection_below(
27167            &AddSelectionBelow {
27168                skip_soft_wrap: true,
27169            },
27170            window,
27171            cx,
27172        );
27173
27174        assert_eq!(
27175            display_ranges(editor, cx),
27176            &[
27177                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27178                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27179            ]
27180        );
27181
27182        editor.add_selection_above(
27183            &AddSelectionAbove {
27184                skip_soft_wrap: true,
27185            },
27186            window,
27187            cx,
27188        );
27189
27190        assert_eq!(
27191            display_ranges(editor, cx),
27192            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27193        );
27194
27195        editor.add_selection_below(
27196            &AddSelectionBelow {
27197                skip_soft_wrap: false,
27198            },
27199            window,
27200            cx,
27201        );
27202
27203        assert_eq!(
27204            display_ranges(editor, cx),
27205            &[
27206                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27207                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27208            ]
27209        );
27210
27211        editor.add_selection_above(
27212            &AddSelectionAbove {
27213                skip_soft_wrap: false,
27214            },
27215            window,
27216            cx,
27217        );
27218
27219        assert_eq!(
27220            display_ranges(editor, cx),
27221            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27222        );
27223    });
27224}
27225
27226#[gpui::test]
27227async fn test_insert_snippet(cx: &mut TestAppContext) {
27228    init_test(cx, |_| {});
27229    let mut cx = EditorTestContext::new(cx).await;
27230
27231    cx.update_editor(|editor, _, cx| {
27232        editor.project().unwrap().update(cx, |project, cx| {
27233            project.snippets().update(cx, |snippets, _cx| {
27234                let snippet = project::snippet_provider::Snippet {
27235                    prefix: vec![], // no prefix needed!
27236                    body: "an Unspecified".to_string(),
27237                    description: Some("shhhh it's a secret".to_string()),
27238                    name: "super secret snippet".to_string(),
27239                };
27240                snippets.add_snippet_for_test(
27241                    None,
27242                    PathBuf::from("test_snippets.json"),
27243                    vec![Arc::new(snippet)],
27244                );
27245
27246                let snippet = project::snippet_provider::Snippet {
27247                    prefix: vec![], // no prefix needed!
27248                    body: " Location".to_string(),
27249                    description: Some("the word 'location'".to_string()),
27250                    name: "location word".to_string(),
27251                };
27252                snippets.add_snippet_for_test(
27253                    Some("Markdown".to_string()),
27254                    PathBuf::from("test_snippets.json"),
27255                    vec![Arc::new(snippet)],
27256                );
27257            });
27258        })
27259    });
27260
27261    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27262
27263    cx.update_editor(|editor, window, cx| {
27264        editor.insert_snippet_at_selections(
27265            &InsertSnippet {
27266                language: None,
27267                name: Some("super secret snippet".to_string()),
27268                snippet: None,
27269            },
27270            window,
27271            cx,
27272        );
27273
27274        // Language is specified in the action,
27275        // so the buffer language does not need to match
27276        editor.insert_snippet_at_selections(
27277            &InsertSnippet {
27278                language: Some("Markdown".to_string()),
27279                name: Some("location word".to_string()),
27280                snippet: None,
27281            },
27282            window,
27283            cx,
27284        );
27285
27286        editor.insert_snippet_at_selections(
27287            &InsertSnippet {
27288                language: None,
27289                name: None,
27290                snippet: Some("$0 after".to_string()),
27291            },
27292            window,
27293            cx,
27294        );
27295    });
27296
27297    cx.assert_editor_state(
27298        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27299    );
27300}
27301
27302#[gpui::test(iterations = 10)]
27303async fn test_document_colors(cx: &mut TestAppContext) {
27304    let expected_color = Rgba {
27305        r: 0.33,
27306        g: 0.33,
27307        b: 0.33,
27308        a: 0.33,
27309    };
27310
27311    init_test(cx, |_| {});
27312
27313    let fs = FakeFs::new(cx.executor());
27314    fs.insert_tree(
27315        path!("/a"),
27316        json!({
27317            "first.rs": "fn main() { let a = 5; }",
27318        }),
27319    )
27320    .await;
27321
27322    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27323    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27324    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27325
27326    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27327    language_registry.add(rust_lang());
27328    let mut fake_servers = language_registry.register_fake_lsp(
27329        "Rust",
27330        FakeLspAdapter {
27331            capabilities: lsp::ServerCapabilities {
27332                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27333                ..lsp::ServerCapabilities::default()
27334            },
27335            name: "rust-analyzer",
27336            ..FakeLspAdapter::default()
27337        },
27338    );
27339    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27340        "Rust",
27341        FakeLspAdapter {
27342            capabilities: lsp::ServerCapabilities {
27343                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27344                ..lsp::ServerCapabilities::default()
27345            },
27346            name: "not-rust-analyzer",
27347            ..FakeLspAdapter::default()
27348        },
27349    );
27350
27351    let editor = workspace
27352        .update(cx, |workspace, window, cx| {
27353            workspace.open_abs_path(
27354                PathBuf::from(path!("/a/first.rs")),
27355                OpenOptions::default(),
27356                window,
27357                cx,
27358            )
27359        })
27360        .unwrap()
27361        .await
27362        .unwrap()
27363        .downcast::<Editor>()
27364        .unwrap();
27365    let fake_language_server = fake_servers.next().await.unwrap();
27366    let fake_language_server_without_capabilities =
27367        fake_servers_without_capabilities.next().await.unwrap();
27368    let requests_made = Arc::new(AtomicUsize::new(0));
27369    let closure_requests_made = Arc::clone(&requests_made);
27370    let mut color_request_handle = fake_language_server
27371        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27372            let requests_made = Arc::clone(&closure_requests_made);
27373            async move {
27374                assert_eq!(
27375                    params.text_document.uri,
27376                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27377                );
27378                requests_made.fetch_add(1, atomic::Ordering::Release);
27379                Ok(vec![
27380                    lsp::ColorInformation {
27381                        range: lsp::Range {
27382                            start: lsp::Position {
27383                                line: 0,
27384                                character: 0,
27385                            },
27386                            end: lsp::Position {
27387                                line: 0,
27388                                character: 1,
27389                            },
27390                        },
27391                        color: lsp::Color {
27392                            red: 0.33,
27393                            green: 0.33,
27394                            blue: 0.33,
27395                            alpha: 0.33,
27396                        },
27397                    },
27398                    lsp::ColorInformation {
27399                        range: lsp::Range {
27400                            start: lsp::Position {
27401                                line: 0,
27402                                character: 0,
27403                            },
27404                            end: lsp::Position {
27405                                line: 0,
27406                                character: 1,
27407                            },
27408                        },
27409                        color: lsp::Color {
27410                            red: 0.33,
27411                            green: 0.33,
27412                            blue: 0.33,
27413                            alpha: 0.33,
27414                        },
27415                    },
27416                ])
27417            }
27418        });
27419
27420    let _handle = fake_language_server_without_capabilities
27421        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27422            panic!("Should not be called");
27423        });
27424    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27425    color_request_handle.next().await.unwrap();
27426    cx.run_until_parked();
27427    assert_eq!(
27428        1,
27429        requests_made.load(atomic::Ordering::Acquire),
27430        "Should query for colors once per editor open"
27431    );
27432    editor.update_in(cx, |editor, _, cx| {
27433        assert_eq!(
27434            vec![expected_color],
27435            extract_color_inlays(editor, cx),
27436            "Should have an initial inlay"
27437        );
27438    });
27439
27440    // opening another file in a split should not influence the LSP query counter
27441    workspace
27442        .update(cx, |workspace, window, cx| {
27443            assert_eq!(
27444                workspace.panes().len(),
27445                1,
27446                "Should have one pane with one editor"
27447            );
27448            workspace.move_item_to_pane_in_direction(
27449                &MoveItemToPaneInDirection {
27450                    direction: SplitDirection::Right,
27451                    focus: false,
27452                    clone: true,
27453                },
27454                window,
27455                cx,
27456            );
27457        })
27458        .unwrap();
27459    cx.run_until_parked();
27460    workspace
27461        .update(cx, |workspace, _, cx| {
27462            let panes = workspace.panes();
27463            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27464            for pane in panes {
27465                let editor = pane
27466                    .read(cx)
27467                    .active_item()
27468                    .and_then(|item| item.downcast::<Editor>())
27469                    .expect("Should have opened an editor in each split");
27470                let editor_file = editor
27471                    .read(cx)
27472                    .buffer()
27473                    .read(cx)
27474                    .as_singleton()
27475                    .expect("test deals with singleton buffers")
27476                    .read(cx)
27477                    .file()
27478                    .expect("test buffese should have a file")
27479                    .path();
27480                assert_eq!(
27481                    editor_file.as_ref(),
27482                    rel_path("first.rs"),
27483                    "Both editors should be opened for the same file"
27484                )
27485            }
27486        })
27487        .unwrap();
27488
27489    cx.executor().advance_clock(Duration::from_millis(500));
27490    let save = editor.update_in(cx, |editor, window, cx| {
27491        editor.move_to_end(&MoveToEnd, window, cx);
27492        editor.handle_input("dirty", window, cx);
27493        editor.save(
27494            SaveOptions {
27495                format: true,
27496                autosave: true,
27497            },
27498            project.clone(),
27499            window,
27500            cx,
27501        )
27502    });
27503    save.await.unwrap();
27504
27505    color_request_handle.next().await.unwrap();
27506    cx.run_until_parked();
27507    assert_eq!(
27508        2,
27509        requests_made.load(atomic::Ordering::Acquire),
27510        "Should query for colors once per save (deduplicated) and once per formatting after save"
27511    );
27512
27513    drop(editor);
27514    let close = workspace
27515        .update(cx, |workspace, window, cx| {
27516            workspace.active_pane().update(cx, |pane, cx| {
27517                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27518            })
27519        })
27520        .unwrap();
27521    close.await.unwrap();
27522    let close = workspace
27523        .update(cx, |workspace, window, cx| {
27524            workspace.active_pane().update(cx, |pane, cx| {
27525                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27526            })
27527        })
27528        .unwrap();
27529    close.await.unwrap();
27530    assert_eq!(
27531        2,
27532        requests_made.load(atomic::Ordering::Acquire),
27533        "After saving and closing all editors, no extra requests should be made"
27534    );
27535    workspace
27536        .update(cx, |workspace, _, cx| {
27537            assert!(
27538                workspace.active_item(cx).is_none(),
27539                "Should close all editors"
27540            )
27541        })
27542        .unwrap();
27543
27544    workspace
27545        .update(cx, |workspace, window, cx| {
27546            workspace.active_pane().update(cx, |pane, cx| {
27547                pane.navigate_backward(&workspace::GoBack, window, cx);
27548            })
27549        })
27550        .unwrap();
27551    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27552    cx.run_until_parked();
27553    let editor = workspace
27554        .update(cx, |workspace, _, cx| {
27555            workspace
27556                .active_item(cx)
27557                .expect("Should have reopened the editor again after navigating back")
27558                .downcast::<Editor>()
27559                .expect("Should be an editor")
27560        })
27561        .unwrap();
27562
27563    assert_eq!(
27564        2,
27565        requests_made.load(atomic::Ordering::Acquire),
27566        "Cache should be reused on buffer close and reopen"
27567    );
27568    editor.update(cx, |editor, cx| {
27569        assert_eq!(
27570            vec![expected_color],
27571            extract_color_inlays(editor, cx),
27572            "Should have an initial inlay"
27573        );
27574    });
27575
27576    drop(color_request_handle);
27577    let closure_requests_made = Arc::clone(&requests_made);
27578    let mut empty_color_request_handle = fake_language_server
27579        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27580            let requests_made = Arc::clone(&closure_requests_made);
27581            async move {
27582                assert_eq!(
27583                    params.text_document.uri,
27584                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27585                );
27586                requests_made.fetch_add(1, atomic::Ordering::Release);
27587                Ok(Vec::new())
27588            }
27589        });
27590    let save = editor.update_in(cx, |editor, window, cx| {
27591        editor.move_to_end(&MoveToEnd, window, cx);
27592        editor.handle_input("dirty_again", window, cx);
27593        editor.save(
27594            SaveOptions {
27595                format: false,
27596                autosave: true,
27597            },
27598            project.clone(),
27599            window,
27600            cx,
27601        )
27602    });
27603    save.await.unwrap();
27604
27605    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27606    empty_color_request_handle.next().await.unwrap();
27607    cx.run_until_parked();
27608    assert_eq!(
27609        3,
27610        requests_made.load(atomic::Ordering::Acquire),
27611        "Should query for colors once per save only, as formatting was not requested"
27612    );
27613    editor.update(cx, |editor, cx| {
27614        assert_eq!(
27615            Vec::<Rgba>::new(),
27616            extract_color_inlays(editor, cx),
27617            "Should clear all colors when the server returns an empty response"
27618        );
27619    });
27620}
27621
27622#[gpui::test]
27623async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27624    init_test(cx, |_| {});
27625    let (editor, cx) = cx.add_window_view(Editor::single_line);
27626    editor.update_in(cx, |editor, window, cx| {
27627        editor.set_text("oops\n\nwow\n", window, cx)
27628    });
27629    cx.run_until_parked();
27630    editor.update(cx, |editor, cx| {
27631        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27632    });
27633    editor.update(cx, |editor, cx| {
27634        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27635    });
27636    cx.run_until_parked();
27637    editor.update(cx, |editor, cx| {
27638        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27639    });
27640}
27641
27642#[gpui::test]
27643async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27644    init_test(cx, |_| {});
27645
27646    cx.update(|cx| {
27647        register_project_item::<Editor>(cx);
27648    });
27649
27650    let fs = FakeFs::new(cx.executor());
27651    fs.insert_tree("/root1", json!({})).await;
27652    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27653        .await;
27654
27655    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27656    let (workspace, cx) =
27657        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27658
27659    let worktree_id = project.update(cx, |project, cx| {
27660        project.worktrees(cx).next().unwrap().read(cx).id()
27661    });
27662
27663    let handle = workspace
27664        .update_in(cx, |workspace, window, cx| {
27665            let project_path = (worktree_id, rel_path("one.pdf"));
27666            workspace.open_path(project_path, None, true, window, cx)
27667        })
27668        .await
27669        .unwrap();
27670
27671    assert_eq!(
27672        handle.to_any_view().entity_type(),
27673        TypeId::of::<InvalidItemView>()
27674    );
27675}
27676
27677#[gpui::test]
27678async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27679    init_test(cx, |_| {});
27680
27681    let language = Arc::new(Language::new(
27682        LanguageConfig::default(),
27683        Some(tree_sitter_rust::LANGUAGE.into()),
27684    ));
27685
27686    // Test hierarchical sibling navigation
27687    let text = r#"
27688        fn outer() {
27689            if condition {
27690                let a = 1;
27691            }
27692            let b = 2;
27693        }
27694
27695        fn another() {
27696            let c = 3;
27697        }
27698    "#;
27699
27700    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27701    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27702    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27703
27704    // Wait for parsing to complete
27705    editor
27706        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27707        .await;
27708
27709    editor.update_in(cx, |editor, window, cx| {
27710        // Start by selecting "let a = 1;" inside the if block
27711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27712            s.select_display_ranges([
27713                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27714            ]);
27715        });
27716
27717        let initial_selection = editor
27718            .selections
27719            .display_ranges(&editor.display_snapshot(cx));
27720        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27721
27722        // Test select next sibling - should move up levels to find the next sibling
27723        // Since "let a = 1;" has no siblings in the if block, it should move up
27724        // to find "let b = 2;" which is a sibling of the if block
27725        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27726        let next_selection = editor
27727            .selections
27728            .display_ranges(&editor.display_snapshot(cx));
27729
27730        // Should have a selection and it should be different from the initial
27731        assert_eq!(
27732            next_selection.len(),
27733            1,
27734            "Should have one selection after next"
27735        );
27736        assert_ne!(
27737            next_selection[0], initial_selection[0],
27738            "Next sibling selection should be different"
27739        );
27740
27741        // Test hierarchical navigation by going to the end of the current function
27742        // and trying to navigate to the next function
27743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27744            s.select_display_ranges([
27745                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27746            ]);
27747        });
27748
27749        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27750        let function_next_selection = editor
27751            .selections
27752            .display_ranges(&editor.display_snapshot(cx));
27753
27754        // Should move to the next function
27755        assert_eq!(
27756            function_next_selection.len(),
27757            1,
27758            "Should have one selection after function next"
27759        );
27760
27761        // Test select previous sibling navigation
27762        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27763        let prev_selection = editor
27764            .selections
27765            .display_ranges(&editor.display_snapshot(cx));
27766
27767        // Should have a selection and it should be different
27768        assert_eq!(
27769            prev_selection.len(),
27770            1,
27771            "Should have one selection after prev"
27772        );
27773        assert_ne!(
27774            prev_selection[0], function_next_selection[0],
27775            "Previous sibling selection should be different from next"
27776        );
27777    });
27778}
27779
27780#[gpui::test]
27781async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27782    init_test(cx, |_| {});
27783
27784    let mut cx = EditorTestContext::new(cx).await;
27785    cx.set_state(
27786        "let ˇvariable = 42;
27787let another = variable + 1;
27788let result = variable * 2;",
27789    );
27790
27791    // Set up document highlights manually (simulating LSP response)
27792    cx.update_editor(|editor, _window, cx| {
27793        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27794
27795        // Create highlights for "variable" occurrences
27796        let highlight_ranges = [
27797            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27798            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27799            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27800        ];
27801
27802        let anchor_ranges: Vec<_> = highlight_ranges
27803            .iter()
27804            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27805            .collect();
27806
27807        editor.highlight_background::<DocumentHighlightRead>(
27808            &anchor_ranges,
27809            |_, theme| theme.colors().editor_document_highlight_read_background,
27810            cx,
27811        );
27812    });
27813
27814    // Go to next highlight - should move to second "variable"
27815    cx.update_editor(|editor, window, cx| {
27816        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27817    });
27818    cx.assert_editor_state(
27819        "let variable = 42;
27820let another = ˇvariable + 1;
27821let result = variable * 2;",
27822    );
27823
27824    // Go to next highlight - should move to third "variable"
27825    cx.update_editor(|editor, window, cx| {
27826        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27827    });
27828    cx.assert_editor_state(
27829        "let variable = 42;
27830let another = variable + 1;
27831let result = ˇvariable * 2;",
27832    );
27833
27834    // Go to next highlight - should stay at third "variable" (no wrap-around)
27835    cx.update_editor(|editor, window, cx| {
27836        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27837    });
27838    cx.assert_editor_state(
27839        "let variable = 42;
27840let another = variable + 1;
27841let result = ˇvariable * 2;",
27842    );
27843
27844    // Now test going backwards from third position
27845    cx.update_editor(|editor, window, cx| {
27846        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27847    });
27848    cx.assert_editor_state(
27849        "let variable = 42;
27850let another = ˇvariable + 1;
27851let result = variable * 2;",
27852    );
27853
27854    // Go to previous highlight - should move to first "variable"
27855    cx.update_editor(|editor, window, cx| {
27856        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27857    });
27858    cx.assert_editor_state(
27859        "let ˇvariable = 42;
27860let another = variable + 1;
27861let result = variable * 2;",
27862    );
27863
27864    // Go to previous highlight - should stay on first "variable"
27865    cx.update_editor(|editor, window, cx| {
27866        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27867    });
27868    cx.assert_editor_state(
27869        "let ˇvariable = 42;
27870let another = variable + 1;
27871let result = variable * 2;",
27872    );
27873}
27874
27875#[gpui::test]
27876async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27877    cx: &mut gpui::TestAppContext,
27878) {
27879    init_test(cx, |_| {});
27880
27881    let url = "https://zed.dev";
27882
27883    let markdown_language = Arc::new(Language::new(
27884        LanguageConfig {
27885            name: "Markdown".into(),
27886            ..LanguageConfig::default()
27887        },
27888        None,
27889    ));
27890
27891    let mut cx = EditorTestContext::new(cx).await;
27892    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27893    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27894
27895    cx.update_editor(|editor, window, cx| {
27896        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27897        editor.paste(&Paste, window, cx);
27898    });
27899
27900    cx.assert_editor_state(&format!(
27901        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27902    ));
27903}
27904
27905#[gpui::test]
27906async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27907    init_test(cx, |_| {});
27908
27909    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27910    let mut cx = EditorTestContext::new(cx).await;
27911
27912    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27913
27914    // Case 1: Test if adding a character with multi cursors preserves nested list indents
27915    cx.set_state(&indoc! {"
27916        - [ ] Item 1
27917            - [ ] Item 1.a
27918        - [ˇ] Item 2
27919            - [ˇ] Item 2.a
27920            - [ˇ] Item 2.b
27921        "
27922    });
27923    cx.update_editor(|editor, window, cx| {
27924        editor.handle_input("x", window, cx);
27925    });
27926    cx.run_until_parked();
27927    cx.assert_editor_state(indoc! {"
27928        - [ ] Item 1
27929            - [ ] Item 1.a
27930        - [xˇ] Item 2
27931            - [xˇ] Item 2.a
27932            - [xˇ] Item 2.b
27933        "
27934    });
27935
27936    // Case 2: Test adding new line after nested list preserves indent of previous line
27937    cx.set_state(&indoc! {"
27938        - [ ] Item 1
27939            - [ ] Item 1.a
27940        - [x] Item 2
27941            - [x] Item 2.a
27942            - [x] Item 2.bˇ"
27943    });
27944    cx.update_editor(|editor, window, cx| {
27945        editor.newline(&Newline, window, cx);
27946    });
27947    cx.assert_editor_state(indoc! {"
27948        - [ ] Item 1
27949            - [ ] Item 1.a
27950        - [x] Item 2
27951            - [x] Item 2.a
27952            - [x] Item 2.b
27953            ˇ"
27954    });
27955
27956    // Case 3: Test adding a new nested list item preserves indent
27957    cx.set_state(&indoc! {"
27958        - [ ] Item 1
27959            - [ ] Item 1.a
27960        - [x] Item 2
27961            - [x] Item 2.a
27962            - [x] Item 2.b
27963            ˇ"
27964    });
27965    cx.update_editor(|editor, window, cx| {
27966        editor.handle_input("-", window, cx);
27967    });
27968    cx.run_until_parked();
27969    cx.assert_editor_state(indoc! {"
27970        - [ ] Item 1
27971            - [ ] Item 1.a
27972        - [x] Item 2
27973            - [x] Item 2.a
27974            - [x] Item 2.b
27975"
27976    });
27977    cx.update_editor(|editor, window, cx| {
27978        editor.handle_input(" [x] Item 2.c", window, cx);
27979    });
27980    cx.run_until_parked();
27981    cx.assert_editor_state(indoc! {"
27982        - [ ] Item 1
27983            - [ ] Item 1.a
27984        - [x] Item 2
27985            - [x] Item 2.a
27986            - [x] Item 2.b
27987            - [x] Item 2.cˇ"
27988    });
27989
27990    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
27991    cx.set_state(indoc! {"
27992        1. Item 1
27993            1. Item 1.a
27994        2. Item 2
27995            1. Item 2.a
27996            2. Item 2.bˇ"
27997    });
27998    cx.update_editor(|editor, window, cx| {
27999        editor.newline(&Newline, window, cx);
28000    });
28001    cx.assert_editor_state(indoc! {"
28002        1. Item 1
28003            1. Item 1.a
28004        2. Item 2
28005            1. Item 2.a
28006            2. Item 2.b
28007            ˇ"
28008    });
28009
28010    // Case 5: Adding new ordered list item preserves indent
28011    cx.set_state(indoc! {"
28012        1. Item 1
28013            1. Item 1.a
28014        2. Item 2
28015            1. Item 2.a
28016            2. Item 2.b
28017            ˇ"
28018    });
28019    cx.update_editor(|editor, window, cx| {
28020        editor.handle_input("3", window, cx);
28021    });
28022    cx.run_until_parked();
28023    cx.assert_editor_state(indoc! {"
28024        1. Item 1
28025            1. Item 1.a
28026        2. Item 2
28027            1. Item 2.a
28028            2. Item 2.b
28029"
28030    });
28031    cx.update_editor(|editor, window, cx| {
28032        editor.handle_input(".", window, cx);
28033    });
28034    cx.run_until_parked();
28035    cx.assert_editor_state(indoc! {"
28036        1. Item 1
28037            1. Item 1.a
28038        2. Item 2
28039            1. Item 2.a
28040            2. Item 2.b
28041            3.ˇ"
28042    });
28043    cx.update_editor(|editor, window, cx| {
28044        editor.handle_input(" Item 2.c", window, cx);
28045    });
28046    cx.run_until_parked();
28047    cx.assert_editor_state(indoc! {"
28048        1. Item 1
28049            1. Item 1.a
28050        2. Item 2
28051            1. Item 2.a
28052            2. Item 2.b
28053            3. Item 2.cˇ"
28054    });
28055
28056    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28057    cx.set_state(indoc! {"
28058        - Item 1
28059            - Item 1.a
28060            - Item 1.a
28061        ˇ"});
28062    cx.update_editor(|editor, window, cx| {
28063        editor.handle_input("-", window, cx);
28064    });
28065    cx.run_until_parked();
28066    cx.assert_editor_state(indoc! {"
28067        - Item 1
28068            - Item 1.a
28069            - Item 1.a
28070"});
28071
28072    // Case 7: Test blockquote newline preserves something
28073    cx.set_state(indoc! {"
28074        > Item 1ˇ"
28075    });
28076    cx.update_editor(|editor, window, cx| {
28077        editor.newline(&Newline, window, cx);
28078    });
28079    cx.assert_editor_state(indoc! {"
28080        > Item 1
28081        ˇ"
28082    });
28083}
28084
28085#[gpui::test]
28086async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28087    cx: &mut gpui::TestAppContext,
28088) {
28089    init_test(cx, |_| {});
28090
28091    let url = "https://zed.dev";
28092
28093    let markdown_language = Arc::new(Language::new(
28094        LanguageConfig {
28095            name: "Markdown".into(),
28096            ..LanguageConfig::default()
28097        },
28098        None,
28099    ));
28100
28101    let mut cx = EditorTestContext::new(cx).await;
28102    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28103    cx.set_state(&format!(
28104        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28105    ));
28106
28107    cx.update_editor(|editor, window, cx| {
28108        editor.copy(&Copy, window, cx);
28109    });
28110
28111    cx.set_state(&format!(
28112        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28113    ));
28114
28115    cx.update_editor(|editor, window, cx| {
28116        editor.paste(&Paste, window, cx);
28117    });
28118
28119    cx.assert_editor_state(&format!(
28120        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28121    ));
28122}
28123
28124#[gpui::test]
28125async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28126    cx: &mut gpui::TestAppContext,
28127) {
28128    init_test(cx, |_| {});
28129
28130    let url = "https://zed.dev";
28131
28132    let markdown_language = Arc::new(Language::new(
28133        LanguageConfig {
28134            name: "Markdown".into(),
28135            ..LanguageConfig::default()
28136        },
28137        None,
28138    ));
28139
28140    let mut cx = EditorTestContext::new(cx).await;
28141    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28142    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28143
28144    cx.update_editor(|editor, window, cx| {
28145        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28146        editor.paste(&Paste, window, cx);
28147    });
28148
28149    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28150}
28151
28152#[gpui::test]
28153async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28154    cx: &mut gpui::TestAppContext,
28155) {
28156    init_test(cx, |_| {});
28157
28158    let text = "Awesome";
28159
28160    let markdown_language = Arc::new(Language::new(
28161        LanguageConfig {
28162            name: "Markdown".into(),
28163            ..LanguageConfig::default()
28164        },
28165        None,
28166    ));
28167
28168    let mut cx = EditorTestContext::new(cx).await;
28169    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28170    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28171
28172    cx.update_editor(|editor, window, cx| {
28173        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28174        editor.paste(&Paste, window, cx);
28175    });
28176
28177    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28178}
28179
28180#[gpui::test]
28181async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28182    cx: &mut gpui::TestAppContext,
28183) {
28184    init_test(cx, |_| {});
28185
28186    let url = "https://zed.dev";
28187
28188    let markdown_language = Arc::new(Language::new(
28189        LanguageConfig {
28190            name: "Rust".into(),
28191            ..LanguageConfig::default()
28192        },
28193        None,
28194    ));
28195
28196    let mut cx = EditorTestContext::new(cx).await;
28197    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28198    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28199
28200    cx.update_editor(|editor, window, cx| {
28201        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28202        editor.paste(&Paste, window, cx);
28203    });
28204
28205    cx.assert_editor_state(&format!(
28206        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28207    ));
28208}
28209
28210#[gpui::test]
28211async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28212    cx: &mut TestAppContext,
28213) {
28214    init_test(cx, |_| {});
28215
28216    let url = "https://zed.dev";
28217
28218    let markdown_language = Arc::new(Language::new(
28219        LanguageConfig {
28220            name: "Markdown".into(),
28221            ..LanguageConfig::default()
28222        },
28223        None,
28224    ));
28225
28226    let (editor, cx) = cx.add_window_view(|window, cx| {
28227        let multi_buffer = MultiBuffer::build_multi(
28228            [
28229                ("this will embed -> link", vec![Point::row_range(0..1)]),
28230                ("this will replace -> link", vec![Point::row_range(0..1)]),
28231            ],
28232            cx,
28233        );
28234        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28236            s.select_ranges(vec![
28237                Point::new(0, 19)..Point::new(0, 23),
28238                Point::new(1, 21)..Point::new(1, 25),
28239            ])
28240        });
28241        let first_buffer_id = multi_buffer
28242            .read(cx)
28243            .excerpt_buffer_ids()
28244            .into_iter()
28245            .next()
28246            .unwrap();
28247        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28248        first_buffer.update(cx, |buffer, cx| {
28249            buffer.set_language(Some(markdown_language.clone()), cx);
28250        });
28251
28252        editor
28253    });
28254    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28255
28256    cx.update_editor(|editor, window, cx| {
28257        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28258        editor.paste(&Paste, window, cx);
28259    });
28260
28261    cx.assert_editor_state(&format!(
28262        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28263    ));
28264}
28265
28266#[gpui::test]
28267async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28268    init_test(cx, |_| {});
28269
28270    let fs = FakeFs::new(cx.executor());
28271    fs.insert_tree(
28272        path!("/project"),
28273        json!({
28274            "first.rs": "# First Document\nSome content here.",
28275            "second.rs": "Plain text content for second file.",
28276        }),
28277    )
28278    .await;
28279
28280    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28281    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28282    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28283
28284    let language = rust_lang();
28285    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28286    language_registry.add(language.clone());
28287    let mut fake_servers = language_registry.register_fake_lsp(
28288        "Rust",
28289        FakeLspAdapter {
28290            ..FakeLspAdapter::default()
28291        },
28292    );
28293
28294    let buffer1 = project
28295        .update(cx, |project, cx| {
28296            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28297        })
28298        .await
28299        .unwrap();
28300    let buffer2 = project
28301        .update(cx, |project, cx| {
28302            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28303        })
28304        .await
28305        .unwrap();
28306
28307    let multi_buffer = cx.new(|cx| {
28308        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28309        multi_buffer.set_excerpts_for_path(
28310            PathKey::for_buffer(&buffer1, cx),
28311            buffer1.clone(),
28312            [Point::zero()..buffer1.read(cx).max_point()],
28313            3,
28314            cx,
28315        );
28316        multi_buffer.set_excerpts_for_path(
28317            PathKey::for_buffer(&buffer2, cx),
28318            buffer2.clone(),
28319            [Point::zero()..buffer1.read(cx).max_point()],
28320            3,
28321            cx,
28322        );
28323        multi_buffer
28324    });
28325
28326    let (editor, cx) = cx.add_window_view(|window, cx| {
28327        Editor::new(
28328            EditorMode::full(),
28329            multi_buffer,
28330            Some(project.clone()),
28331            window,
28332            cx,
28333        )
28334    });
28335
28336    let fake_language_server = fake_servers.next().await.unwrap();
28337
28338    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28339
28340    let save = editor.update_in(cx, |editor, window, cx| {
28341        assert!(editor.is_dirty(cx));
28342
28343        editor.save(
28344            SaveOptions {
28345                format: true,
28346                autosave: true,
28347            },
28348            project,
28349            window,
28350            cx,
28351        )
28352    });
28353    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28354    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28355    let mut done_edit_rx = Some(done_edit_rx);
28356    let mut start_edit_tx = Some(start_edit_tx);
28357
28358    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28359        start_edit_tx.take().unwrap().send(()).unwrap();
28360        let done_edit_rx = done_edit_rx.take().unwrap();
28361        async move {
28362            done_edit_rx.await.unwrap();
28363            Ok(None)
28364        }
28365    });
28366
28367    start_edit_rx.await.unwrap();
28368    buffer2
28369        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28370        .unwrap();
28371
28372    done_edit_tx.send(()).unwrap();
28373
28374    save.await.unwrap();
28375    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28376}
28377
28378#[track_caller]
28379fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28380    editor
28381        .all_inlays(cx)
28382        .into_iter()
28383        .filter_map(|inlay| inlay.get_color())
28384        .map(Rgba::from)
28385        .collect()
28386}
28387
28388#[gpui::test]
28389fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28390    init_test(cx, |_| {});
28391
28392    let editor = cx.add_window(|window, cx| {
28393        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28394        build_editor(buffer, window, cx)
28395    });
28396
28397    editor
28398        .update(cx, |editor, window, cx| {
28399            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28400                s.select_display_ranges([
28401                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28402                ])
28403            });
28404
28405            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28406
28407            assert_eq!(
28408                editor.display_text(cx),
28409                "line1\nline2\nline2",
28410                "Duplicating last line upward should create duplicate above, not on same line"
28411            );
28412
28413            assert_eq!(
28414                editor
28415                    .selections
28416                    .display_ranges(&editor.display_snapshot(cx)),
28417                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28418                "Selection should move to the duplicated line"
28419            );
28420        })
28421        .unwrap();
28422}
28423
28424#[gpui::test]
28425async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28426    init_test(cx, |_| {});
28427
28428    let mut cx = EditorTestContext::new(cx).await;
28429
28430    cx.set_state("line1\nline2ˇ");
28431
28432    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28433
28434    let clipboard_text = cx
28435        .read_from_clipboard()
28436        .and_then(|item| item.text().as_deref().map(str::to_string));
28437
28438    assert_eq!(
28439        clipboard_text,
28440        Some("line2\n".to_string()),
28441        "Copying a line without trailing newline should include a newline"
28442    );
28443
28444    cx.set_state("line1\nˇ");
28445
28446    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28447
28448    cx.assert_editor_state("line1\nline2\nˇ");
28449}
28450
28451#[gpui::test]
28452async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28453    init_test(cx, |_| {});
28454
28455    let mut cx = EditorTestContext::new(cx).await;
28456
28457    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28458
28459    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28460
28461    let clipboard_text = cx
28462        .read_from_clipboard()
28463        .and_then(|item| item.text().as_deref().map(str::to_string));
28464
28465    assert_eq!(
28466        clipboard_text,
28467        Some("line1\nline2\nline3\n".to_string()),
28468        "Copying multiple lines should include a single newline between lines"
28469    );
28470
28471    cx.set_state("lineA\nˇ");
28472
28473    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28474
28475    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28476}
28477
28478#[gpui::test]
28479async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28480    init_test(cx, |_| {});
28481
28482    let mut cx = EditorTestContext::new(cx).await;
28483
28484    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28485
28486    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28487
28488    let clipboard_text = cx
28489        .read_from_clipboard()
28490        .and_then(|item| item.text().as_deref().map(str::to_string));
28491
28492    assert_eq!(
28493        clipboard_text,
28494        Some("line1\nline2\nline3\n".to_string()),
28495        "Copying multiple lines should include a single newline between lines"
28496    );
28497
28498    cx.set_state("lineA\nˇ");
28499
28500    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28501
28502    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28503}
28504
28505#[gpui::test]
28506async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28507    init_test(cx, |_| {});
28508
28509    let mut cx = EditorTestContext::new(cx).await;
28510
28511    cx.set_state("line1\nline2ˇ");
28512    cx.update_editor(|e, window, cx| {
28513        e.set_mode(EditorMode::SingleLine);
28514        assert!(e.key_context(window, cx).contains("end_of_input"));
28515    });
28516    cx.set_state("ˇline1\nline2");
28517    cx.update_editor(|e, window, cx| {
28518        assert!(!e.key_context(window, cx).contains("end_of_input"));
28519    });
28520    cx.set_state("line1ˇ\nline2");
28521    cx.update_editor(|e, window, cx| {
28522        assert!(!e.key_context(window, cx).contains("end_of_input"));
28523    });
28524}
28525
28526#[gpui::test]
28527async fn test_sticky_scroll(cx: &mut TestAppContext) {
28528    init_test(cx, |_| {});
28529    let mut cx = EditorTestContext::new(cx).await;
28530
28531    let buffer = indoc! {"
28532            ˇfn foo() {
28533                let abc = 123;
28534            }
28535            struct Bar;
28536            impl Bar {
28537                fn new() -> Self {
28538                    Self
28539                }
28540            }
28541            fn baz() {
28542            }
28543        "};
28544    cx.set_state(&buffer);
28545
28546    cx.update_editor(|e, _, cx| {
28547        e.buffer()
28548            .read(cx)
28549            .as_singleton()
28550            .unwrap()
28551            .update(cx, |buffer, cx| {
28552                buffer.set_language(Some(rust_lang()), cx);
28553            })
28554    });
28555
28556    let mut sticky_headers = |offset: ScrollOffset| {
28557        cx.update_editor(|e, window, cx| {
28558            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28559            let style = e.style(cx).clone();
28560            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28561                .into_iter()
28562                .map(
28563                    |StickyHeader {
28564                         start_point,
28565                         offset,
28566                         ..
28567                     }| { (start_point, offset) },
28568                )
28569                .collect::<Vec<_>>()
28570        })
28571    };
28572
28573    let fn_foo = Point { row: 0, column: 0 };
28574    let impl_bar = Point { row: 4, column: 0 };
28575    let fn_new = Point { row: 5, column: 4 };
28576
28577    assert_eq!(sticky_headers(0.0), vec![]);
28578    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28579    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28580    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28581    assert_eq!(sticky_headers(2.0), vec![]);
28582    assert_eq!(sticky_headers(2.5), vec![]);
28583    assert_eq!(sticky_headers(3.0), vec![]);
28584    assert_eq!(sticky_headers(3.5), vec![]);
28585    assert_eq!(sticky_headers(4.0), vec![]);
28586    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28587    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28588    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28589    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28590    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28591    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28592    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28593    assert_eq!(sticky_headers(8.0), vec![]);
28594    assert_eq!(sticky_headers(8.5), vec![]);
28595    assert_eq!(sticky_headers(9.0), vec![]);
28596    assert_eq!(sticky_headers(9.5), vec![]);
28597    assert_eq!(sticky_headers(10.0), vec![]);
28598}
28599
28600#[gpui::test]
28601async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28602    init_test(cx, |_| {});
28603    cx.update(|cx| {
28604        SettingsStore::update_global(cx, |store, cx| {
28605            store.update_user_settings(cx, |settings| {
28606                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28607                    enabled: Some(true),
28608                })
28609            });
28610        });
28611    });
28612    let mut cx = EditorTestContext::new(cx).await;
28613
28614    let line_height = cx.update_editor(|editor, window, cx| {
28615        editor
28616            .style(cx)
28617            .text
28618            .line_height_in_pixels(window.rem_size())
28619    });
28620
28621    let buffer = indoc! {"
28622            ˇfn foo() {
28623                let abc = 123;
28624            }
28625            struct Bar;
28626            impl Bar {
28627                fn new() -> Self {
28628                    Self
28629                }
28630            }
28631            fn baz() {
28632            }
28633        "};
28634    cx.set_state(&buffer);
28635
28636    cx.update_editor(|e, _, cx| {
28637        e.buffer()
28638            .read(cx)
28639            .as_singleton()
28640            .unwrap()
28641            .update(cx, |buffer, cx| {
28642                buffer.set_language(Some(rust_lang()), cx);
28643            })
28644    });
28645
28646    let fn_foo = || empty_range(0, 0);
28647    let impl_bar = || empty_range(4, 0);
28648    let fn_new = || empty_range(5, 4);
28649
28650    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28651        cx.update_editor(|e, window, cx| {
28652            e.scroll(
28653                gpui::Point {
28654                    x: 0.,
28655                    y: scroll_offset,
28656                },
28657                None,
28658                window,
28659                cx,
28660            );
28661        });
28662        cx.simulate_click(
28663            gpui::Point {
28664                x: px(0.),
28665                y: click_offset as f32 * line_height,
28666            },
28667            Modifiers::none(),
28668        );
28669        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28670    };
28671
28672    assert_eq!(
28673        scroll_and_click(
28674            4.5, // impl Bar is halfway off the screen
28675            0.0  // click top of screen
28676        ),
28677        // scrolled to impl Bar
28678        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28679    );
28680
28681    assert_eq!(
28682        scroll_and_click(
28683            4.5,  // impl Bar is halfway off the screen
28684            0.25  // click middle of impl Bar
28685        ),
28686        // scrolled to impl Bar
28687        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28688    );
28689
28690    assert_eq!(
28691        scroll_and_click(
28692            4.5, // impl Bar is halfway off the screen
28693            1.5  // click below impl Bar (e.g. fn new())
28694        ),
28695        // scrolled to fn new() - this is below the impl Bar header which has persisted
28696        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28697    );
28698
28699    assert_eq!(
28700        scroll_and_click(
28701            5.5,  // fn new is halfway underneath impl Bar
28702            0.75  // click on the overlap of impl Bar and fn new()
28703        ),
28704        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28705    );
28706
28707    assert_eq!(
28708        scroll_and_click(
28709            5.5,  // fn new is halfway underneath impl Bar
28710            1.25  // click on the visible part of fn new()
28711        ),
28712        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28713    );
28714
28715    assert_eq!(
28716        scroll_and_click(
28717            1.5, // fn foo is halfway off the screen
28718            0.0  // click top of screen
28719        ),
28720        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28721    );
28722
28723    assert_eq!(
28724        scroll_and_click(
28725            1.5,  // fn foo is halfway off the screen
28726            0.75  // click visible part of let abc...
28727        )
28728        .0,
28729        // no change in scroll
28730        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28731        (gpui::Point { x: 0., y: 1.5 })
28732    );
28733}
28734
28735#[gpui::test]
28736async fn test_next_prev_reference(cx: &mut TestAppContext) {
28737    const CYCLE_POSITIONS: &[&'static str] = &[
28738        indoc! {"
28739            fn foo() {
28740                let ˇabc = 123;
28741                let x = abc + 1;
28742                let y = abc + 2;
28743                let z = abc + 2;
28744            }
28745        "},
28746        indoc! {"
28747            fn foo() {
28748                let abc = 123;
28749                let x = ˇabc + 1;
28750                let y = abc + 2;
28751                let z = abc + 2;
28752            }
28753        "},
28754        indoc! {"
28755            fn foo() {
28756                let abc = 123;
28757                let x = abc + 1;
28758                let y = ˇabc + 2;
28759                let z = abc + 2;
28760            }
28761        "},
28762        indoc! {"
28763            fn foo() {
28764                let abc = 123;
28765                let x = abc + 1;
28766                let y = abc + 2;
28767                let z = ˇabc + 2;
28768            }
28769        "},
28770    ];
28771
28772    init_test(cx, |_| {});
28773
28774    let mut cx = EditorLspTestContext::new_rust(
28775        lsp::ServerCapabilities {
28776            references_provider: Some(lsp::OneOf::Left(true)),
28777            ..Default::default()
28778        },
28779        cx,
28780    )
28781    .await;
28782
28783    // importantly, the cursor is in the middle
28784    cx.set_state(indoc! {"
28785        fn foo() {
28786            let aˇbc = 123;
28787            let x = abc + 1;
28788            let y = abc + 2;
28789            let z = abc + 2;
28790        }
28791    "});
28792
28793    let reference_ranges = [
28794        lsp::Position::new(1, 8),
28795        lsp::Position::new(2, 12),
28796        lsp::Position::new(3, 12),
28797        lsp::Position::new(4, 12),
28798    ]
28799    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28800
28801    cx.lsp
28802        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28803            Ok(Some(
28804                reference_ranges
28805                    .map(|range| lsp::Location {
28806                        uri: params.text_document_position.text_document.uri.clone(),
28807                        range,
28808                    })
28809                    .to_vec(),
28810            ))
28811        });
28812
28813    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28814        cx.update_editor(|editor, window, cx| {
28815            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28816        })
28817        .unwrap()
28818        .await
28819        .unwrap()
28820    };
28821
28822    _move(Direction::Next, 1, &mut cx).await;
28823    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28824
28825    _move(Direction::Next, 1, &mut cx).await;
28826    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28827
28828    _move(Direction::Next, 1, &mut cx).await;
28829    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28830
28831    // loops back to the start
28832    _move(Direction::Next, 1, &mut cx).await;
28833    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28834
28835    // loops back to the end
28836    _move(Direction::Prev, 1, &mut cx).await;
28837    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28838
28839    _move(Direction::Prev, 1, &mut cx).await;
28840    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28841
28842    _move(Direction::Prev, 1, &mut cx).await;
28843    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28844
28845    _move(Direction::Prev, 1, &mut cx).await;
28846    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28847
28848    _move(Direction::Next, 3, &mut cx).await;
28849    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28850
28851    _move(Direction::Prev, 2, &mut cx).await;
28852    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28853}
28854
28855#[gpui::test]
28856async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28857    init_test(cx, |_| {});
28858
28859    let (editor, cx) = cx.add_window_view(|window, cx| {
28860        let multi_buffer = MultiBuffer::build_multi(
28861            [
28862                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28863                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28864            ],
28865            cx,
28866        );
28867        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28868    });
28869
28870    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28871    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28872
28873    cx.assert_excerpts_with_selections(indoc! {"
28874        [EXCERPT]
28875        ˇ1
28876        2
28877        3
28878        [EXCERPT]
28879        1
28880        2
28881        3
28882        "});
28883
28884    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28885    cx.update_editor(|editor, window, cx| {
28886        editor.change_selections(None.into(), window, cx, |s| {
28887            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28888        });
28889    });
28890    cx.assert_excerpts_with_selections(indoc! {"
28891        [EXCERPT]
28892        1
2889328894        3
28895        [EXCERPT]
28896        1
28897        2
28898        3
28899        "});
28900
28901    cx.update_editor(|editor, window, cx| {
28902        editor
28903            .select_all_matches(&SelectAllMatches, window, cx)
28904            .unwrap();
28905    });
28906    cx.assert_excerpts_with_selections(indoc! {"
28907        [EXCERPT]
28908        1
2890928910        3
28911        [EXCERPT]
28912        1
2891328914        3
28915        "});
28916
28917    cx.update_editor(|editor, window, cx| {
28918        editor.handle_input("X", window, cx);
28919    });
28920    cx.assert_excerpts_with_selections(indoc! {"
28921        [EXCERPT]
28922        1
2892328924        3
28925        [EXCERPT]
28926        1
2892728928        3
28929        "});
28930
28931    // Scenario 2: Select "2", then fold second buffer before insertion
28932    cx.update_multibuffer(|mb, cx| {
28933        for buffer_id in buffer_ids.iter() {
28934            let buffer = mb.buffer(*buffer_id).unwrap();
28935            buffer.update(cx, |buffer, cx| {
28936                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28937            });
28938        }
28939    });
28940
28941    // Select "2" and select all matches
28942    cx.update_editor(|editor, window, cx| {
28943        editor.change_selections(None.into(), window, cx, |s| {
28944            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28945        });
28946        editor
28947            .select_all_matches(&SelectAllMatches, window, cx)
28948            .unwrap();
28949    });
28950
28951    // Fold second buffer - should remove selections from folded buffer
28952    cx.update_editor(|editor, _, cx| {
28953        editor.fold_buffer(buffer_ids[1], cx);
28954    });
28955    cx.assert_excerpts_with_selections(indoc! {"
28956        [EXCERPT]
28957        1
2895828959        3
28960        [EXCERPT]
28961        [FOLDED]
28962        "});
28963
28964    // Insert text - should only affect first buffer
28965    cx.update_editor(|editor, window, cx| {
28966        editor.handle_input("Y", window, cx);
28967    });
28968    cx.update_editor(|editor, _, cx| {
28969        editor.unfold_buffer(buffer_ids[1], cx);
28970    });
28971    cx.assert_excerpts_with_selections(indoc! {"
28972        [EXCERPT]
28973        1
2897428975        3
28976        [EXCERPT]
28977        1
28978        2
28979        3
28980        "});
28981
28982    // Scenario 3: Select "2", then fold first buffer before insertion
28983    cx.update_multibuffer(|mb, cx| {
28984        for buffer_id in buffer_ids.iter() {
28985            let buffer = mb.buffer(*buffer_id).unwrap();
28986            buffer.update(cx, |buffer, cx| {
28987                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28988            });
28989        }
28990    });
28991
28992    // Select "2" and select all matches
28993    cx.update_editor(|editor, window, cx| {
28994        editor.change_selections(None.into(), window, cx, |s| {
28995            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28996        });
28997        editor
28998            .select_all_matches(&SelectAllMatches, window, cx)
28999            .unwrap();
29000    });
29001
29002    // Fold first buffer - should remove selections from folded buffer
29003    cx.update_editor(|editor, _, cx| {
29004        editor.fold_buffer(buffer_ids[0], cx);
29005    });
29006    cx.assert_excerpts_with_selections(indoc! {"
29007        [EXCERPT]
29008        [FOLDED]
29009        [EXCERPT]
29010        1
2901129012        3
29013        "});
29014
29015    // Insert text - should only affect second buffer
29016    cx.update_editor(|editor, window, cx| {
29017        editor.handle_input("Z", window, cx);
29018    });
29019    cx.update_editor(|editor, _, cx| {
29020        editor.unfold_buffer(buffer_ids[0], cx);
29021    });
29022    cx.assert_excerpts_with_selections(indoc! {"
29023        [EXCERPT]
29024        1
29025        2
29026        3
29027        [EXCERPT]
29028        1
2902929030        3
29031        "});
29032
29033    // Test correct folded header is selected upon fold
29034    cx.update_editor(|editor, _, cx| {
29035        editor.fold_buffer(buffer_ids[0], cx);
29036        editor.fold_buffer(buffer_ids[1], cx);
29037    });
29038    cx.assert_excerpts_with_selections(indoc! {"
29039        [EXCERPT]
29040        [FOLDED]
29041        [EXCERPT]
29042        ˇ[FOLDED]
29043        "});
29044
29045    // Test selection inside folded buffer unfolds it on type
29046    cx.update_editor(|editor, window, cx| {
29047        editor.handle_input("W", window, cx);
29048    });
29049    cx.update_editor(|editor, _, cx| {
29050        editor.unfold_buffer(buffer_ids[0], cx);
29051    });
29052    cx.assert_excerpts_with_selections(indoc! {"
29053        [EXCERPT]
29054        1
29055        2
29056        3
29057        [EXCERPT]
29058        Wˇ1
29059        Z
29060        3
29061        "});
29062}
29063
29064#[gpui::test]
29065async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
29066    init_test(cx, |_| {});
29067    let mut leader_cx = EditorTestContext::new(cx).await;
29068
29069    let diff_base = indoc!(
29070        r#"
29071        one
29072        two
29073        three
29074        four
29075        five
29076        six
29077        "#
29078    );
29079
29080    let initial_state = indoc!(
29081        r#"
29082        ˇone
29083        two
29084        THREE
29085        four
29086        five
29087        six
29088        "#
29089    );
29090
29091    leader_cx.set_state(initial_state);
29092
29093    leader_cx.set_head_text(&diff_base);
29094    leader_cx.run_until_parked();
29095
29096    let follower = leader_cx.update_multibuffer(|leader, cx| {
29097        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29098        leader.set_all_diff_hunks_expanded(cx);
29099        leader.get_or_create_follower(cx)
29100    });
29101    follower.update(cx, |follower, cx| {
29102        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29103        follower.set_all_diff_hunks_expanded(cx);
29104    });
29105
29106    let follower_editor =
29107        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
29108    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
29109
29110    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
29111    cx.run_until_parked();
29112
29113    leader_cx.assert_editor_state(initial_state);
29114    follower_cx.assert_editor_state(indoc! {
29115        r#"
29116        ˇone
29117        two
29118        three
29119        four
29120        five
29121        six
29122        "#
29123    });
29124
29125    follower_cx.editor(|editor, _window, cx| {
29126        assert!(editor.read_only(cx));
29127    });
29128
29129    leader_cx.update_editor(|editor, _window, cx| {
29130        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
29131    });
29132    cx.run_until_parked();
29133
29134    leader_cx.assert_editor_state(indoc! {
29135        r#"
29136        ˇone
29137        two
29138        THREE
29139        four
29140        FIVE
29141        six
29142        "#
29143    });
29144
29145    follower_cx.assert_editor_state(indoc! {
29146        r#"
29147        ˇone
29148        two
29149        three
29150        four
29151        five
29152        six
29153        "#
29154    });
29155
29156    leader_cx.update_editor(|editor, _window, cx| {
29157        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
29158    });
29159    cx.run_until_parked();
29160
29161    leader_cx.assert_editor_state(indoc! {
29162        r#"
29163        ˇone
29164        two
29165        THREE
29166        four
29167        FIVE
29168        six
29169        SEVEN"#
29170    });
29171
29172    follower_cx.assert_editor_state(indoc! {
29173        r#"
29174        ˇone
29175        two
29176        three
29177        four
29178        five
29179        six
29180        "#
29181    });
29182
29183    leader_cx.update_editor(|editor, window, cx| {
29184        editor.move_down(&MoveDown, window, cx);
29185        editor.refresh_selected_text_highlights(true, window, cx);
29186    });
29187    leader_cx.run_until_parked();
29188}
29189
29190#[gpui::test]
29191async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
29192    init_test(cx, |_| {});
29193    let base_text = "base\n";
29194    let buffer_text = "buffer\n";
29195
29196    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
29197    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
29198
29199    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
29200    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
29201    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
29202    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
29203
29204    let leader = cx.new(|cx| {
29205        let mut leader = MultiBuffer::new(Capability::ReadWrite);
29206        leader.set_all_diff_hunks_expanded(cx);
29207        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29208        leader
29209    });
29210    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
29211    follower.update(cx, |follower, _| {
29212        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29213    });
29214
29215    leader.update(cx, |leader, cx| {
29216        leader.insert_excerpts_after(
29217            ExcerptId::min(),
29218            extra_buffer_2.clone(),
29219            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29220            cx,
29221        );
29222        leader.add_diff(extra_diff_2.clone(), cx);
29223
29224        leader.insert_excerpts_after(
29225            ExcerptId::min(),
29226            extra_buffer_1.clone(),
29227            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29228            cx,
29229        );
29230        leader.add_diff(extra_diff_1.clone(), cx);
29231
29232        leader.insert_excerpts_after(
29233            ExcerptId::min(),
29234            buffer1.clone(),
29235            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29236            cx,
29237        );
29238        leader.add_diff(diff1.clone(), cx);
29239    });
29240
29241    cx.run_until_parked();
29242    let mut cx = cx.add_empty_window();
29243
29244    let leader_editor = cx
29245        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29246    let follower_editor = cx.new_window_entity(|window, cx| {
29247        Editor::for_multibuffer(follower.clone(), None, window, cx)
29248    });
29249
29250    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29251    leader_cx.assert_editor_state(indoc! {"
29252       ˇbuffer
29253
29254       dummy text 1
29255
29256       dummy text 2
29257    "});
29258    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29259    follower_cx.assert_editor_state(indoc! {"
29260        ˇbase
29261
29262
29263    "});
29264}
29265
29266#[gpui::test]
29267async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29268    init_test(cx, |_| {});
29269
29270    let (editor, cx) = cx.add_window_view(|window, cx| {
29271        let multi_buffer = MultiBuffer::build_multi(
29272            [
29273                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29274                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29275            ],
29276            cx,
29277        );
29278        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29279    });
29280
29281    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29282
29283    cx.assert_excerpts_with_selections(indoc! {"
29284        [EXCERPT]
29285        ˇ1
29286        2
29287        3
29288        [EXCERPT]
29289        1
29290        2
29291        3
29292        4
29293        5
29294        6
29295        7
29296        8
29297        9
29298        "});
29299
29300    cx.update_editor(|editor, window, cx| {
29301        editor.change_selections(None.into(), window, cx, |s| {
29302            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29303        });
29304    });
29305
29306    cx.assert_excerpts_with_selections(indoc! {"
29307        [EXCERPT]
29308        1
29309        2
29310        3
29311        [EXCERPT]
29312        1
29313        2
29314        3
29315        4
29316        5
29317        6
29318        ˇ7
29319        8
29320        9
29321        "});
29322
29323    cx.update_editor(|editor, _window, cx| {
29324        editor.set_vertical_scroll_margin(0, cx);
29325    });
29326
29327    cx.update_editor(|editor, window, cx| {
29328        assert_eq!(editor.vertical_scroll_margin(), 0);
29329        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29330        assert_eq!(
29331            editor.snapshot(window, cx).scroll_position(),
29332            gpui::Point::new(0., 12.0)
29333        );
29334    });
29335
29336    cx.update_editor(|editor, _window, cx| {
29337        editor.set_vertical_scroll_margin(3, cx);
29338    });
29339
29340    cx.update_editor(|editor, window, cx| {
29341        assert_eq!(editor.vertical_scroll_margin(), 3);
29342        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29343        assert_eq!(
29344            editor.snapshot(window, cx).scroll_position(),
29345            gpui::Point::new(0., 9.0)
29346        );
29347    });
29348}
29349
29350#[gpui::test]
29351async fn test_find_references_single_case(cx: &mut TestAppContext) {
29352    init_test(cx, |_| {});
29353    let mut cx = EditorLspTestContext::new_rust(
29354        lsp::ServerCapabilities {
29355            references_provider: Some(lsp::OneOf::Left(true)),
29356            ..lsp::ServerCapabilities::default()
29357        },
29358        cx,
29359    )
29360    .await;
29361
29362    let before = indoc!(
29363        r#"
29364        fn main() {
29365            let aˇbc = 123;
29366            let xyz = abc;
29367        }
29368        "#
29369    );
29370    let after = indoc!(
29371        r#"
29372        fn main() {
29373            let abc = 123;
29374            let xyz = ˇabc;
29375        }
29376        "#
29377    );
29378
29379    cx.lsp
29380        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29381            Ok(Some(vec![
29382                lsp::Location {
29383                    uri: params.text_document_position.text_document.uri.clone(),
29384                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29385                },
29386                lsp::Location {
29387                    uri: params.text_document_position.text_document.uri,
29388                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29389                },
29390            ]))
29391        });
29392
29393    cx.set_state(before);
29394
29395    let action = FindAllReferences {
29396        always_open_multibuffer: false,
29397    };
29398
29399    let navigated = cx
29400        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29401        .expect("should have spawned a task")
29402        .await
29403        .unwrap();
29404
29405    assert_eq!(navigated, Navigated::No);
29406
29407    cx.run_until_parked();
29408
29409    cx.assert_editor_state(after);
29410}
29411
29412#[gpui::test]
29413async fn test_local_worktree_trust(cx: &mut TestAppContext) {
29414    init_test(cx, |_| {});
29415    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
29416
29417    cx.update(|cx| {
29418        SettingsStore::update_global(cx, |store, cx| {
29419            store.update_user_settings(cx, |settings| {
29420                settings.project.all_languages.defaults.inlay_hints =
29421                    Some(InlayHintSettingsContent {
29422                        enabled: Some(true),
29423                        ..InlayHintSettingsContent::default()
29424                    });
29425            });
29426        });
29427    });
29428
29429    let fs = FakeFs::new(cx.executor());
29430    fs.insert_tree(
29431        path!("/project"),
29432        json!({
29433            ".zed": {
29434                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
29435            },
29436            "main.rs": "fn main() {}"
29437        }),
29438    )
29439    .await;
29440
29441    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
29442    let server_name = "override-rust-analyzer";
29443    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
29444
29445    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29446    language_registry.add(rust_lang());
29447
29448    let capabilities = lsp::ServerCapabilities {
29449        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29450        ..lsp::ServerCapabilities::default()
29451    };
29452    let mut fake_language_servers = language_registry.register_fake_lsp(
29453        "Rust",
29454        FakeLspAdapter {
29455            name: server_name,
29456            capabilities,
29457            initializer: Some(Box::new({
29458                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29459                move |fake_server| {
29460                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29461                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29462                        move |_params, _| {
29463                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
29464                            async move {
29465                                Ok(Some(vec![lsp::InlayHint {
29466                                    position: lsp::Position::new(0, 0),
29467                                    label: lsp::InlayHintLabel::String("hint".to_string()),
29468                                    kind: None,
29469                                    text_edits: None,
29470                                    tooltip: None,
29471                                    padding_left: None,
29472                                    padding_right: None,
29473                                    data: None,
29474                                }]))
29475                            }
29476                        },
29477                    );
29478                }
29479            })),
29480            ..FakeLspAdapter::default()
29481        },
29482    );
29483
29484    cx.run_until_parked();
29485
29486    let worktree_id = project.read_with(cx, |project, cx| {
29487        project
29488            .worktrees(cx)
29489            .next()
29490            .map(|wt| wt.read(cx).id())
29491            .expect("should have a worktree")
29492    });
29493
29494    let trusted_worktrees =
29495        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
29496
29497    let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29498    assert!(!can_trust, "worktree should be restricted initially");
29499
29500    let buffer_before_approval = project
29501        .update(cx, |project, cx| {
29502            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
29503        })
29504        .await
29505        .unwrap();
29506
29507    let (editor, cx) = cx.add_window_view(|window, cx| {
29508        Editor::new(
29509            EditorMode::full(),
29510            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
29511            Some(project.clone()),
29512            window,
29513            cx,
29514        )
29515    });
29516    cx.run_until_parked();
29517    let fake_language_server = fake_language_servers.next();
29518
29519    cx.read(|cx| {
29520        let file = buffer_before_approval.read(cx).file();
29521        assert_eq!(
29522            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29523                .language_servers,
29524            ["...".to_string()],
29525            "local .zed/settings.json must not apply before trust approval"
29526        )
29527    });
29528
29529    editor.update_in(cx, |editor, window, cx| {
29530        editor.handle_input("1", window, cx);
29531    });
29532    cx.run_until_parked();
29533    cx.executor()
29534        .advance_clock(std::time::Duration::from_secs(1));
29535    assert_eq!(
29536        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
29537        0,
29538        "inlay hints must not be queried before trust approval"
29539    );
29540
29541    trusted_worktrees.update(cx, |store, cx| {
29542        store.trust(
29543            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
29544            None,
29545            cx,
29546        );
29547    });
29548    cx.run_until_parked();
29549
29550    cx.read(|cx| {
29551        let file = buffer_before_approval.read(cx).file();
29552        assert_eq!(
29553            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29554                .language_servers,
29555            ["override-rust-analyzer".to_string()],
29556            "local .zed/settings.json should apply after trust approval"
29557        )
29558    });
29559    let _fake_language_server = fake_language_server.await.unwrap();
29560    editor.update_in(cx, |editor, window, cx| {
29561        editor.handle_input("1", window, cx);
29562    });
29563    cx.run_until_parked();
29564    cx.executor()
29565        .advance_clock(std::time::Duration::from_secs(1));
29566    assert!(
29567        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
29568        "inlay hints should be queried after trust approval"
29569    );
29570
29571    let can_trust_after =
29572        trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29573    assert!(can_trust_after, "worktree should be trusted after trust()");
29574}
29575
29576#[gpui::test]
29577fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
29578    // This test reproduces a bug where drawing an editor at a position above the viewport
29579    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
29580    // causes an infinite loop in blocks_in_range.
29581    //
29582    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
29583    // the content mask intersection produces visible_bounds with origin at the viewport top.
29584    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
29585    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
29586    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
29587    init_test(cx, |_| {});
29588
29589    let window = cx.add_window(|_, _| gpui::Empty);
29590    let mut cx = VisualTestContext::from_window(*window, cx);
29591
29592    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
29593    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
29594
29595    // Simulate a small viewport (500x500 pixels at origin 0,0)
29596    cx.simulate_resize(gpui::size(px(500.), px(500.)));
29597
29598    // Draw the editor at a very negative Y position, simulating an editor that's been
29599    // scrolled way above the visible viewport (like in a List that has scrolled past it).
29600    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
29601    // This should NOT hang - it should just render nothing.
29602    cx.draw(
29603        gpui::point(px(0.), px(-10000.)),
29604        gpui::size(px(500.), px(3000.)),
29605        |_, _| editor.clone(),
29606    );
29607
29608    // If we get here without hanging, the test passes
29609}