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    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferFilterMode, MultiBufferOffset,
   40    MultiBufferOffsetUtf16, PathKey,
   41};
   42use parking_lot::Mutex;
   43use pretty_assertions::{assert_eq, assert_ne};
   44use project::{
   45    FakeFs, Project,
   46    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   47    project_settings::LspSettings,
   48    trusted_worktrees::{PathTrust, TrustedWorktrees},
   49};
   50use serde_json::{self, json};
   51use settings::{
   52    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   53    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   54    SettingsStore,
   55};
   56use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   57use std::{
   58    iter,
   59    sync::atomic::{self, AtomicUsize},
   60};
   61use test::build_editor_with_project;
   62use text::ToPoint as _;
   63use unindent::Unindent;
   64use util::{
   65    assert_set_eq, path,
   66    rel_path::rel_path,
   67    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   68    uri,
   69};
   70use workspace::{
   71    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   72    OpenOptions, ViewId,
   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), 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.0.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.0.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.0.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.0.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.update_editor(|editor, window, cx| {
20885        editor.move_up(&MoveUp, window, cx);
20886        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20887    });
20888    cx.assert_state_with_diff(
20889        indoc! { "
20890        ˇone
20891      - two
20892        three
20893        five
20894    "}
20895        .to_string(),
20896    );
20897
20898    cx.update_editor(|editor, window, cx| {
20899        editor.move_down(&MoveDown, window, cx);
20900        editor.move_down(&MoveDown, window, cx);
20901        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20902    });
20903    cx.assert_state_with_diff(
20904        indoc! { "
20905        one
20906      - two
20907        ˇthree
20908      - four
20909        five
20910    "}
20911        .to_string(),
20912    );
20913
20914    cx.set_state(indoc! { "
20915        one
20916        ˇTWO
20917        three
20918        four
20919        five
20920    "});
20921    cx.run_until_parked();
20922    cx.update_editor(|editor, window, cx| {
20923        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20924    });
20925
20926    cx.assert_state_with_diff(
20927        indoc! { "
20928            one
20929          - two
20930          + ˇTWO
20931            three
20932            four
20933            five
20934        "}
20935        .to_string(),
20936    );
20937    cx.update_editor(|editor, window, cx| {
20938        editor.move_up(&Default::default(), window, cx);
20939        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20940    });
20941    cx.assert_state_with_diff(
20942        indoc! { "
20943            one
20944            ˇTWO
20945            three
20946            four
20947            five
20948        "}
20949        .to_string(),
20950    );
20951}
20952
20953#[gpui::test]
20954async fn test_toggling_adjacent_diff_hunks_2(
20955    executor: BackgroundExecutor,
20956    cx: &mut TestAppContext,
20957) {
20958    init_test(cx, |_| {});
20959
20960    let mut cx = EditorTestContext::new(cx).await;
20961
20962    let diff_base = r#"
20963        lineA
20964        lineB
20965        lineC
20966        lineD
20967        "#
20968    .unindent();
20969
20970    cx.set_state(
20971        &r#"
20972        ˇlineA1
20973        lineB
20974        lineD
20975        "#
20976        .unindent(),
20977    );
20978    cx.set_head_text(&diff_base);
20979    executor.run_until_parked();
20980
20981    cx.update_editor(|editor, window, cx| {
20982        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20983    });
20984    executor.run_until_parked();
20985    cx.assert_state_with_diff(
20986        r#"
20987        - lineA
20988        + ˇlineA1
20989          lineB
20990          lineD
20991        "#
20992        .unindent(),
20993    );
20994
20995    cx.update_editor(|editor, window, cx| {
20996        editor.move_down(&MoveDown, window, cx);
20997        editor.move_right(&MoveRight, window, cx);
20998        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20999    });
21000    executor.run_until_parked();
21001    cx.assert_state_with_diff(
21002        r#"
21003        - lineA
21004        + lineA1
21005          lˇineB
21006        - lineC
21007          lineD
21008        "#
21009        .unindent(),
21010    );
21011}
21012
21013#[gpui::test]
21014async fn test_edits_around_expanded_deletion_hunks(
21015    executor: BackgroundExecutor,
21016    cx: &mut TestAppContext,
21017) {
21018    init_test(cx, |_| {});
21019
21020    let mut cx = EditorTestContext::new(cx).await;
21021
21022    let diff_base = 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    executor.run_until_parked();
21039    cx.set_state(
21040        &r#"
21041        use some::mod1;
21042        use some::mod2;
21043
21044        ˇconst B: u32 = 42;
21045        const C: u32 = 42;
21046
21047
21048        fn main() {
21049            println!("hello");
21050
21051            println!("world");
21052        }
21053        "#
21054        .unindent(),
21055    );
21056
21057    cx.set_head_text(&diff_base);
21058    executor.run_until_parked();
21059
21060    cx.update_editor(|editor, window, cx| {
21061        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21062    });
21063    executor.run_until_parked();
21064
21065    cx.assert_state_with_diff(
21066        r#"
21067        use some::mod1;
21068        use some::mod2;
21069
21070      - const A: u32 = 42;
21071        ˇconst B: u32 = 42;
21072        const C: u32 = 42;
21073
21074
21075        fn main() {
21076            println!("hello");
21077
21078            println!("world");
21079        }
21080      "#
21081        .unindent(),
21082    );
21083
21084    cx.update_editor(|editor, window, cx| {
21085        editor.delete_line(&DeleteLine, window, cx);
21086    });
21087    executor.run_until_parked();
21088    cx.assert_state_with_diff(
21089        r#"
21090        use some::mod1;
21091        use some::mod2;
21092
21093      - const A: u32 = 42;
21094      - const B: u32 = 42;
21095        ˇconst C: u32 = 42;
21096
21097
21098        fn main() {
21099            println!("hello");
21100
21101            println!("world");
21102        }
21103      "#
21104        .unindent(),
21105    );
21106
21107    cx.update_editor(|editor, window, cx| {
21108        editor.delete_line(&DeleteLine, window, cx);
21109    });
21110    executor.run_until_parked();
21111    cx.assert_state_with_diff(
21112        r#"
21113        use some::mod1;
21114        use some::mod2;
21115
21116      - const A: u32 = 42;
21117      - const B: u32 = 42;
21118      - const C: u32 = 42;
21119        ˇ
21120
21121        fn main() {
21122            println!("hello");
21123
21124            println!("world");
21125        }
21126      "#
21127        .unindent(),
21128    );
21129
21130    cx.update_editor(|editor, window, cx| {
21131        editor.handle_input("replacement", window, cx);
21132    });
21133    executor.run_until_parked();
21134    cx.assert_state_with_diff(
21135        r#"
21136        use some::mod1;
21137        use some::mod2;
21138
21139      - const A: u32 = 42;
21140      - const B: u32 = 42;
21141      - const C: u32 = 42;
21142      -
21143      + replacementˇ
21144
21145        fn main() {
21146            println!("hello");
21147
21148            println!("world");
21149        }
21150      "#
21151        .unindent(),
21152    );
21153}
21154
21155#[gpui::test]
21156async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21157    init_test(cx, |_| {});
21158
21159    let mut cx = EditorTestContext::new(cx).await;
21160
21161    let base_text = r#"
21162        one
21163        two
21164        three
21165        four
21166        five
21167    "#
21168    .unindent();
21169    executor.run_until_parked();
21170    cx.set_state(
21171        &r#"
21172        one
21173        two
21174        fˇour
21175        five
21176        "#
21177        .unindent(),
21178    );
21179
21180    cx.set_head_text(&base_text);
21181    executor.run_until_parked();
21182
21183    cx.update_editor(|editor, window, cx| {
21184        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21185    });
21186    executor.run_until_parked();
21187
21188    cx.assert_state_with_diff(
21189        r#"
21190          one
21191          two
21192        - three
21193          fˇour
21194          five
21195        "#
21196        .unindent(),
21197    );
21198
21199    cx.update_editor(|editor, window, cx| {
21200        editor.backspace(&Backspace, window, cx);
21201        editor.backspace(&Backspace, window, cx);
21202    });
21203    executor.run_until_parked();
21204    cx.assert_state_with_diff(
21205        r#"
21206          one
21207          two
21208        - threeˇ
21209        - four
21210        + our
21211          five
21212        "#
21213        .unindent(),
21214    );
21215}
21216
21217#[gpui::test]
21218async fn test_edit_after_expanded_modification_hunk(
21219    executor: BackgroundExecutor,
21220    cx: &mut TestAppContext,
21221) {
21222    init_test(cx, |_| {});
21223
21224    let mut cx = EditorTestContext::new(cx).await;
21225
21226    let diff_base = r#"
21227        use some::mod1;
21228        use some::mod2;
21229
21230        const A: u32 = 42;
21231        const B: u32 = 42;
21232        const C: u32 = 42;
21233        const D: u32 = 42;
21234
21235
21236        fn main() {
21237            println!("hello");
21238
21239            println!("world");
21240        }"#
21241    .unindent();
21242
21243    cx.set_state(
21244        &r#"
21245        use some::mod1;
21246        use some::mod2;
21247
21248        const A: u32 = 42;
21249        const B: u32 = 42;
21250        const C: u32 = 43ˇ
21251        const D: u32 = 42;
21252
21253
21254        fn main() {
21255            println!("hello");
21256
21257            println!("world");
21258        }"#
21259        .unindent(),
21260    );
21261
21262    cx.set_head_text(&diff_base);
21263    executor.run_until_parked();
21264    cx.update_editor(|editor, window, cx| {
21265        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21266    });
21267    executor.run_until_parked();
21268
21269    cx.assert_state_with_diff(
21270        r#"
21271        use some::mod1;
21272        use some::mod2;
21273
21274        const A: u32 = 42;
21275        const B: u32 = 42;
21276      - const C: u32 = 42;
21277      + const C: u32 = 43ˇ
21278        const D: u32 = 42;
21279
21280
21281        fn main() {
21282            println!("hello");
21283
21284            println!("world");
21285        }"#
21286        .unindent(),
21287    );
21288
21289    cx.update_editor(|editor, window, cx| {
21290        editor.handle_input("\nnew_line\n", window, cx);
21291    });
21292    executor.run_until_parked();
21293
21294    cx.assert_state_with_diff(
21295        r#"
21296        use some::mod1;
21297        use some::mod2;
21298
21299        const A: u32 = 42;
21300        const B: u32 = 42;
21301      - const C: u32 = 42;
21302      + const C: u32 = 43
21303      + new_line
21304      + ˇ
21305        const D: u32 = 42;
21306
21307
21308        fn main() {
21309            println!("hello");
21310
21311            println!("world");
21312        }"#
21313        .unindent(),
21314    );
21315}
21316
21317#[gpui::test]
21318async fn test_stage_and_unstage_added_file_hunk(
21319    executor: BackgroundExecutor,
21320    cx: &mut TestAppContext,
21321) {
21322    init_test(cx, |_| {});
21323
21324    let mut cx = EditorTestContext::new(cx).await;
21325    cx.update_editor(|editor, _, cx| {
21326        editor.set_expand_all_diff_hunks(cx);
21327    });
21328
21329    let working_copy = r#"
21330            ˇfn main() {
21331                println!("hello, world!");
21332            }
21333        "#
21334    .unindent();
21335
21336    cx.set_state(&working_copy);
21337    executor.run_until_parked();
21338
21339    cx.assert_state_with_diff(
21340        r#"
21341            + ˇfn main() {
21342            +     println!("hello, world!");
21343            + }
21344        "#
21345        .unindent(),
21346    );
21347    cx.assert_index_text(None);
21348
21349    cx.update_editor(|editor, window, cx| {
21350        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21351    });
21352    executor.run_until_parked();
21353    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21354    cx.assert_state_with_diff(
21355        r#"
21356            + ˇfn main() {
21357            +     println!("hello, world!");
21358            + }
21359        "#
21360        .unindent(),
21361    );
21362
21363    cx.update_editor(|editor, window, cx| {
21364        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21365    });
21366    executor.run_until_parked();
21367    cx.assert_index_text(None);
21368}
21369
21370async fn setup_indent_guides_editor(
21371    text: &str,
21372    cx: &mut TestAppContext,
21373) -> (BufferId, EditorTestContext) {
21374    init_test(cx, |_| {});
21375
21376    let mut cx = EditorTestContext::new(cx).await;
21377
21378    let buffer_id = cx.update_editor(|editor, window, cx| {
21379        editor.set_text(text, window, cx);
21380        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21381
21382        buffer_ids[0]
21383    });
21384
21385    (buffer_id, cx)
21386}
21387
21388fn assert_indent_guides(
21389    range: Range<u32>,
21390    expected: Vec<IndentGuide>,
21391    active_indices: Option<Vec<usize>>,
21392    cx: &mut EditorTestContext,
21393) {
21394    let indent_guides = cx.update_editor(|editor, window, cx| {
21395        let snapshot = editor.snapshot(window, cx).display_snapshot;
21396        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21397            editor,
21398            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21399            true,
21400            &snapshot,
21401            cx,
21402        );
21403
21404        indent_guides.sort_by(|a, b| {
21405            a.depth.cmp(&b.depth).then(
21406                a.start_row
21407                    .cmp(&b.start_row)
21408                    .then(a.end_row.cmp(&b.end_row)),
21409            )
21410        });
21411        indent_guides
21412    });
21413
21414    if let Some(expected) = active_indices {
21415        let active_indices = cx.update_editor(|editor, window, cx| {
21416            let snapshot = editor.snapshot(window, cx).display_snapshot;
21417            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21418        });
21419
21420        assert_eq!(
21421            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21422            expected,
21423            "Active indent guide indices do not match"
21424        );
21425    }
21426
21427    assert_eq!(indent_guides, expected, "Indent guides do not match");
21428}
21429
21430fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21431    IndentGuide {
21432        buffer_id,
21433        start_row: MultiBufferRow(start_row),
21434        end_row: MultiBufferRow(end_row),
21435        depth,
21436        tab_size: 4,
21437        settings: IndentGuideSettings {
21438            enabled: true,
21439            line_width: 1,
21440            active_line_width: 1,
21441            coloring: IndentGuideColoring::default(),
21442            background_coloring: IndentGuideBackgroundColoring::default(),
21443        },
21444    }
21445}
21446
21447#[gpui::test]
21448async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21449    let (buffer_id, mut cx) = setup_indent_guides_editor(
21450        &"
21451        fn main() {
21452            let a = 1;
21453        }"
21454        .unindent(),
21455        cx,
21456    )
21457    .await;
21458
21459    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21460}
21461
21462#[gpui::test]
21463async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21464    let (buffer_id, mut cx) = setup_indent_guides_editor(
21465        &"
21466        fn main() {
21467            let a = 1;
21468            let b = 2;
21469        }"
21470        .unindent(),
21471        cx,
21472    )
21473    .await;
21474
21475    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21476}
21477
21478#[gpui::test]
21479async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21480    let (buffer_id, mut cx) = setup_indent_guides_editor(
21481        &"
21482        fn main() {
21483            let a = 1;
21484            if a == 3 {
21485                let b = 2;
21486            } else {
21487                let c = 3;
21488            }
21489        }"
21490        .unindent(),
21491        cx,
21492    )
21493    .await;
21494
21495    assert_indent_guides(
21496        0..8,
21497        vec![
21498            indent_guide(buffer_id, 1, 6, 0),
21499            indent_guide(buffer_id, 3, 3, 1),
21500            indent_guide(buffer_id, 5, 5, 1),
21501        ],
21502        None,
21503        &mut cx,
21504    );
21505}
21506
21507#[gpui::test]
21508async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21509    let (buffer_id, mut cx) = setup_indent_guides_editor(
21510        &"
21511        fn main() {
21512            let a = 1;
21513                let b = 2;
21514            let c = 3;
21515        }"
21516        .unindent(),
21517        cx,
21518    )
21519    .await;
21520
21521    assert_indent_guides(
21522        0..5,
21523        vec![
21524            indent_guide(buffer_id, 1, 3, 0),
21525            indent_guide(buffer_id, 2, 2, 1),
21526        ],
21527        None,
21528        &mut cx,
21529    );
21530}
21531
21532#[gpui::test]
21533async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21534    let (buffer_id, mut cx) = setup_indent_guides_editor(
21535        &"
21536        fn main() {
21537            let a = 1;
21538
21539            let c = 3;
21540        }"
21541        .unindent(),
21542        cx,
21543    )
21544    .await;
21545
21546    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21547}
21548
21549#[gpui::test]
21550async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21551    let (buffer_id, mut cx) = setup_indent_guides_editor(
21552        &"
21553        fn main() {
21554            let a = 1;
21555
21556            let c = 3;
21557
21558            if a == 3 {
21559                let b = 2;
21560            } else {
21561                let c = 3;
21562            }
21563        }"
21564        .unindent(),
21565        cx,
21566    )
21567    .await;
21568
21569    assert_indent_guides(
21570        0..11,
21571        vec![
21572            indent_guide(buffer_id, 1, 9, 0),
21573            indent_guide(buffer_id, 6, 6, 1),
21574            indent_guide(buffer_id, 8, 8, 1),
21575        ],
21576        None,
21577        &mut cx,
21578    );
21579}
21580
21581#[gpui::test]
21582async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21583    let (buffer_id, mut cx) = setup_indent_guides_editor(
21584        &"
21585        fn main() {
21586            let a = 1;
21587
21588            let c = 3;
21589
21590            if a == 3 {
21591                let b = 2;
21592            } else {
21593                let c = 3;
21594            }
21595        }"
21596        .unindent(),
21597        cx,
21598    )
21599    .await;
21600
21601    assert_indent_guides(
21602        1..11,
21603        vec![
21604            indent_guide(buffer_id, 1, 9, 0),
21605            indent_guide(buffer_id, 6, 6, 1),
21606            indent_guide(buffer_id, 8, 8, 1),
21607        ],
21608        None,
21609        &mut cx,
21610    );
21611}
21612
21613#[gpui::test]
21614async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21615    let (buffer_id, mut cx) = setup_indent_guides_editor(
21616        &"
21617        fn main() {
21618            let a = 1;
21619
21620            let c = 3;
21621
21622            if a == 3 {
21623                let b = 2;
21624            } else {
21625                let c = 3;
21626            }
21627        }"
21628        .unindent(),
21629        cx,
21630    )
21631    .await;
21632
21633    assert_indent_guides(
21634        1..10,
21635        vec![
21636            indent_guide(buffer_id, 1, 9, 0),
21637            indent_guide(buffer_id, 6, 6, 1),
21638            indent_guide(buffer_id, 8, 8, 1),
21639        ],
21640        None,
21641        &mut cx,
21642    );
21643}
21644
21645#[gpui::test]
21646async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21647    let (buffer_id, mut cx) = setup_indent_guides_editor(
21648        &"
21649        fn main() {
21650            if a {
21651                b(
21652                    c,
21653                    d,
21654                )
21655            } else {
21656                e(
21657                    f
21658                )
21659            }
21660        }"
21661        .unindent(),
21662        cx,
21663    )
21664    .await;
21665
21666    assert_indent_guides(
21667        0..11,
21668        vec![
21669            indent_guide(buffer_id, 1, 10, 0),
21670            indent_guide(buffer_id, 2, 5, 1),
21671            indent_guide(buffer_id, 7, 9, 1),
21672            indent_guide(buffer_id, 3, 4, 2),
21673            indent_guide(buffer_id, 8, 8, 2),
21674        ],
21675        None,
21676        &mut cx,
21677    );
21678
21679    cx.update_editor(|editor, window, cx| {
21680        editor.fold_at(MultiBufferRow(2), window, cx);
21681        assert_eq!(
21682            editor.display_text(cx),
21683            "
21684            fn main() {
21685                if a {
21686                    b(⋯
21687                    )
21688                } else {
21689                    e(
21690                        f
21691                    )
21692                }
21693            }"
21694            .unindent()
21695        );
21696    });
21697
21698    assert_indent_guides(
21699        0..11,
21700        vec![
21701            indent_guide(buffer_id, 1, 10, 0),
21702            indent_guide(buffer_id, 2, 5, 1),
21703            indent_guide(buffer_id, 7, 9, 1),
21704            indent_guide(buffer_id, 8, 8, 2),
21705        ],
21706        None,
21707        &mut cx,
21708    );
21709}
21710
21711#[gpui::test]
21712async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21713    let (buffer_id, mut cx) = setup_indent_guides_editor(
21714        &"
21715        block1
21716            block2
21717                block3
21718                    block4
21719            block2
21720        block1
21721        block1"
21722            .unindent(),
21723        cx,
21724    )
21725    .await;
21726
21727    assert_indent_guides(
21728        1..10,
21729        vec![
21730            indent_guide(buffer_id, 1, 4, 0),
21731            indent_guide(buffer_id, 2, 3, 1),
21732            indent_guide(buffer_id, 3, 3, 2),
21733        ],
21734        None,
21735        &mut cx,
21736    );
21737}
21738
21739#[gpui::test]
21740async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21741    let (buffer_id, mut cx) = setup_indent_guides_editor(
21742        &"
21743        block1
21744            block2
21745                block3
21746
21747        block1
21748        block1"
21749            .unindent(),
21750        cx,
21751    )
21752    .await;
21753
21754    assert_indent_guides(
21755        0..6,
21756        vec![
21757            indent_guide(buffer_id, 1, 2, 0),
21758            indent_guide(buffer_id, 2, 2, 1),
21759        ],
21760        None,
21761        &mut cx,
21762    );
21763}
21764
21765#[gpui::test]
21766async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21767    let (buffer_id, mut cx) = setup_indent_guides_editor(
21768        &"
21769        function component() {
21770        \treturn (
21771        \t\t\t
21772        \t\t<div>
21773        \t\t\t<abc></abc>
21774        \t\t</div>
21775        \t)
21776        }"
21777        .unindent(),
21778        cx,
21779    )
21780    .await;
21781
21782    assert_indent_guides(
21783        0..8,
21784        vec![
21785            indent_guide(buffer_id, 1, 6, 0),
21786            indent_guide(buffer_id, 2, 5, 1),
21787            indent_guide(buffer_id, 4, 4, 2),
21788        ],
21789        None,
21790        &mut cx,
21791    );
21792}
21793
21794#[gpui::test]
21795async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21796    let (buffer_id, mut cx) = setup_indent_guides_editor(
21797        &"
21798        function component() {
21799        \treturn (
21800        \t
21801        \t\t<div>
21802        \t\t\t<abc></abc>
21803        \t\t</div>
21804        \t)
21805        }"
21806        .unindent(),
21807        cx,
21808    )
21809    .await;
21810
21811    assert_indent_guides(
21812        0..8,
21813        vec![
21814            indent_guide(buffer_id, 1, 6, 0),
21815            indent_guide(buffer_id, 2, 5, 1),
21816            indent_guide(buffer_id, 4, 4, 2),
21817        ],
21818        None,
21819        &mut cx,
21820    );
21821}
21822
21823#[gpui::test]
21824async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21825    let (buffer_id, mut cx) = setup_indent_guides_editor(
21826        &"
21827        block1
21828
21829
21830
21831            block2
21832        "
21833        .unindent(),
21834        cx,
21835    )
21836    .await;
21837
21838    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21839}
21840
21841#[gpui::test]
21842async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21843    let (buffer_id, mut cx) = setup_indent_guides_editor(
21844        &"
21845        def a:
21846        \tb = 3
21847        \tif True:
21848        \t\tc = 4
21849        \t\td = 5
21850        \tprint(b)
21851        "
21852        .unindent(),
21853        cx,
21854    )
21855    .await;
21856
21857    assert_indent_guides(
21858        0..6,
21859        vec![
21860            indent_guide(buffer_id, 1, 5, 0),
21861            indent_guide(buffer_id, 3, 4, 1),
21862        ],
21863        None,
21864        &mut cx,
21865    );
21866}
21867
21868#[gpui::test]
21869async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21870    let (buffer_id, mut cx) = setup_indent_guides_editor(
21871        &"
21872    fn main() {
21873        let a = 1;
21874    }"
21875        .unindent(),
21876        cx,
21877    )
21878    .await;
21879
21880    cx.update_editor(|editor, window, cx| {
21881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21882            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21883        });
21884    });
21885
21886    assert_indent_guides(
21887        0..3,
21888        vec![indent_guide(buffer_id, 1, 1, 0)],
21889        Some(vec![0]),
21890        &mut cx,
21891    );
21892}
21893
21894#[gpui::test]
21895async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21896    let (buffer_id, mut cx) = setup_indent_guides_editor(
21897        &"
21898    fn main() {
21899        if 1 == 2 {
21900            let a = 1;
21901        }
21902    }"
21903        .unindent(),
21904        cx,
21905    )
21906    .await;
21907
21908    cx.update_editor(|editor, window, cx| {
21909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21910            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21911        });
21912    });
21913
21914    assert_indent_guides(
21915        0..4,
21916        vec![
21917            indent_guide(buffer_id, 1, 3, 0),
21918            indent_guide(buffer_id, 2, 2, 1),
21919        ],
21920        Some(vec![1]),
21921        &mut cx,
21922    );
21923
21924    cx.update_editor(|editor, window, cx| {
21925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21926            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21927        });
21928    });
21929
21930    assert_indent_guides(
21931        0..4,
21932        vec![
21933            indent_guide(buffer_id, 1, 3, 0),
21934            indent_guide(buffer_id, 2, 2, 1),
21935        ],
21936        Some(vec![1]),
21937        &mut cx,
21938    );
21939
21940    cx.update_editor(|editor, window, cx| {
21941        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21942            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21943        });
21944    });
21945
21946    assert_indent_guides(
21947        0..4,
21948        vec![
21949            indent_guide(buffer_id, 1, 3, 0),
21950            indent_guide(buffer_id, 2, 2, 1),
21951        ],
21952        Some(vec![0]),
21953        &mut cx,
21954    );
21955}
21956
21957#[gpui::test]
21958async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21959    let (buffer_id, mut cx) = setup_indent_guides_editor(
21960        &"
21961    fn main() {
21962        let a = 1;
21963
21964        let b = 2;
21965    }"
21966        .unindent(),
21967        cx,
21968    )
21969    .await;
21970
21971    cx.update_editor(|editor, window, cx| {
21972        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21973            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21974        });
21975    });
21976
21977    assert_indent_guides(
21978        0..5,
21979        vec![indent_guide(buffer_id, 1, 3, 0)],
21980        Some(vec![0]),
21981        &mut cx,
21982    );
21983}
21984
21985#[gpui::test]
21986async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21987    let (buffer_id, mut cx) = setup_indent_guides_editor(
21988        &"
21989    def m:
21990        a = 1
21991        pass"
21992            .unindent(),
21993        cx,
21994    )
21995    .await;
21996
21997    cx.update_editor(|editor, window, cx| {
21998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21999            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22000        });
22001    });
22002
22003    assert_indent_guides(
22004        0..3,
22005        vec![indent_guide(buffer_id, 1, 2, 0)],
22006        Some(vec![0]),
22007        &mut cx,
22008    );
22009}
22010
22011#[gpui::test]
22012async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22013    init_test(cx, |_| {});
22014    let mut cx = EditorTestContext::new(cx).await;
22015    let text = indoc! {
22016        "
22017        impl A {
22018            fn b() {
22019                0;
22020                3;
22021                5;
22022                6;
22023                7;
22024            }
22025        }
22026        "
22027    };
22028    let base_text = indoc! {
22029        "
22030        impl A {
22031            fn b() {
22032                0;
22033                1;
22034                2;
22035                3;
22036                4;
22037            }
22038            fn c() {
22039                5;
22040                6;
22041                7;
22042            }
22043        }
22044        "
22045    };
22046
22047    cx.update_editor(|editor, window, cx| {
22048        editor.set_text(text, window, cx);
22049
22050        editor.buffer().update(cx, |multibuffer, cx| {
22051            let buffer = multibuffer.as_singleton().unwrap();
22052            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
22053
22054            multibuffer.set_all_diff_hunks_expanded(cx);
22055            multibuffer.add_diff(diff, cx);
22056
22057            buffer.read(cx).remote_id()
22058        })
22059    });
22060    cx.run_until_parked();
22061
22062    cx.assert_state_with_diff(
22063        indoc! { "
22064          impl A {
22065              fn b() {
22066                  0;
22067        -         1;
22068        -         2;
22069                  3;
22070        -         4;
22071        -     }
22072        -     fn c() {
22073                  5;
22074                  6;
22075                  7;
22076              }
22077          }
22078          ˇ"
22079        }
22080        .to_string(),
22081    );
22082
22083    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22084        editor
22085            .snapshot(window, cx)
22086            .buffer_snapshot()
22087            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22088            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22089            .collect::<Vec<_>>()
22090    });
22091    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22092    assert_eq!(
22093        actual_guides,
22094        vec![
22095            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22096            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22097            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22098        ]
22099    );
22100}
22101
22102#[gpui::test]
22103async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22104    init_test(cx, |_| {});
22105    let mut cx = EditorTestContext::new(cx).await;
22106
22107    let diff_base = r#"
22108        a
22109        b
22110        c
22111        "#
22112    .unindent();
22113
22114    cx.set_state(
22115        &r#"
22116        ˇA
22117        b
22118        C
22119        "#
22120        .unindent(),
22121    );
22122    cx.set_head_text(&diff_base);
22123    cx.update_editor(|editor, window, cx| {
22124        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22125    });
22126    executor.run_until_parked();
22127
22128    let both_hunks_expanded = r#"
22129        - a
22130        + ˇA
22131          b
22132        - c
22133        + C
22134        "#
22135    .unindent();
22136
22137    cx.assert_state_with_diff(both_hunks_expanded.clone());
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[0].clone(), cx);
22154    });
22155    executor.run_until_parked();
22156
22157    let second_hunk_expanded = r#"
22158          ˇA
22159          b
22160        - c
22161        + C
22162        "#
22163    .unindent();
22164
22165    cx.assert_state_with_diff(second_hunk_expanded);
22166
22167    cx.update_editor(|editor, _, cx| {
22168        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22169    });
22170    executor.run_until_parked();
22171
22172    cx.assert_state_with_diff(both_hunks_expanded.clone());
22173
22174    cx.update_editor(|editor, _, cx| {
22175        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22176    });
22177    executor.run_until_parked();
22178
22179    let first_hunk_expanded = r#"
22180        - a
22181        + ˇA
22182          b
22183          C
22184        "#
22185    .unindent();
22186
22187    cx.assert_state_with_diff(first_hunk_expanded);
22188
22189    cx.update_editor(|editor, _, cx| {
22190        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22191    });
22192    executor.run_until_parked();
22193
22194    cx.assert_state_with_diff(both_hunks_expanded);
22195
22196    cx.set_state(
22197        &r#"
22198        ˇA
22199        b
22200        "#
22201        .unindent(),
22202    );
22203    cx.run_until_parked();
22204
22205    // TODO this cursor position seems bad
22206    cx.assert_state_with_diff(
22207        r#"
22208        - ˇa
22209        + A
22210          b
22211        "#
22212        .unindent(),
22213    );
22214
22215    cx.update_editor(|editor, window, cx| {
22216        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22217    });
22218
22219    cx.assert_state_with_diff(
22220        r#"
22221            - ˇa
22222            + A
22223              b
22224            - c
22225            "#
22226        .unindent(),
22227    );
22228
22229    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22230        let snapshot = editor.snapshot(window, cx);
22231        let hunks = editor
22232            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22233            .collect::<Vec<_>>();
22234        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22235        hunks
22236            .into_iter()
22237            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22238            .collect::<Vec<_>>()
22239    });
22240    assert_eq!(hunk_ranges.len(), 2);
22241
22242    cx.update_editor(|editor, _, cx| {
22243        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22244    });
22245    executor.run_until_parked();
22246
22247    cx.assert_state_with_diff(
22248        r#"
22249        - ˇa
22250        + A
22251          b
22252        "#
22253        .unindent(),
22254    );
22255}
22256
22257#[gpui::test]
22258async fn test_toggle_deletion_hunk_at_start_of_file(
22259    executor: BackgroundExecutor,
22260    cx: &mut TestAppContext,
22261) {
22262    init_test(cx, |_| {});
22263    let mut cx = EditorTestContext::new(cx).await;
22264
22265    let diff_base = r#"
22266        a
22267        b
22268        c
22269        "#
22270    .unindent();
22271
22272    cx.set_state(
22273        &r#"
22274        ˇb
22275        c
22276        "#
22277        .unindent(),
22278    );
22279    cx.set_head_text(&diff_base);
22280    cx.update_editor(|editor, window, cx| {
22281        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22282    });
22283    executor.run_until_parked();
22284
22285    let hunk_expanded = r#"
22286        - a
22287          ˇb
22288          c
22289        "#
22290    .unindent();
22291
22292    cx.assert_state_with_diff(hunk_expanded.clone());
22293
22294    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22295        let snapshot = editor.snapshot(window, cx);
22296        let hunks = editor
22297            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22298            .collect::<Vec<_>>();
22299        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22300        hunks
22301            .into_iter()
22302            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22303            .collect::<Vec<_>>()
22304    });
22305    assert_eq!(hunk_ranges.len(), 1);
22306
22307    cx.update_editor(|editor, _, cx| {
22308        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22309    });
22310    executor.run_until_parked();
22311
22312    let hunk_collapsed = r#"
22313          ˇb
22314          c
22315        "#
22316    .unindent();
22317
22318    cx.assert_state_with_diff(hunk_collapsed);
22319
22320    cx.update_editor(|editor, _, cx| {
22321        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22322    });
22323    executor.run_until_parked();
22324
22325    cx.assert_state_with_diff(hunk_expanded);
22326}
22327
22328#[gpui::test]
22329async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22330    executor: BackgroundExecutor,
22331    cx: &mut TestAppContext,
22332) {
22333    init_test(cx, |_| {});
22334    let mut cx = EditorTestContext::new(cx).await;
22335
22336    cx.set_state("ˇnew\nsecond\nthird\n");
22337    cx.set_head_text("old\nsecond\nthird\n");
22338    cx.update_editor(|editor, window, cx| {
22339        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22340    });
22341    executor.run_until_parked();
22342    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22343
22344    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22345    cx.update_editor(|editor, window, cx| {
22346        let snapshot = editor.snapshot(window, cx);
22347        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22348        let hunks = editor
22349            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22350            .collect::<Vec<_>>();
22351        assert_eq!(hunks.len(), 1);
22352        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22353        editor.toggle_single_diff_hunk(hunk_range, cx)
22354    });
22355    executor.run_until_parked();
22356    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22357
22358    // Keep the editor scrolled to the top so the full hunk remains visible.
22359    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22360}
22361
22362#[gpui::test]
22363async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22364    init_test(cx, |_| {});
22365
22366    let fs = FakeFs::new(cx.executor());
22367    fs.insert_tree(
22368        path!("/test"),
22369        json!({
22370            ".git": {},
22371            "file-1": "ONE\n",
22372            "file-2": "TWO\n",
22373            "file-3": "THREE\n",
22374        }),
22375    )
22376    .await;
22377
22378    fs.set_head_for_repo(
22379        path!("/test/.git").as_ref(),
22380        &[
22381            ("file-1", "one\n".into()),
22382            ("file-2", "two\n".into()),
22383            ("file-3", "three\n".into()),
22384        ],
22385        "deadbeef",
22386    );
22387
22388    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22389    let mut buffers = vec![];
22390    for i in 1..=3 {
22391        let buffer = project
22392            .update(cx, |project, cx| {
22393                let path = format!(path!("/test/file-{}"), i);
22394                project.open_local_buffer(path, cx)
22395            })
22396            .await
22397            .unwrap();
22398        buffers.push(buffer);
22399    }
22400
22401    let multibuffer = cx.new(|cx| {
22402        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22403        multibuffer.set_all_diff_hunks_expanded(cx);
22404        for buffer in &buffers {
22405            let snapshot = buffer.read(cx).snapshot();
22406            multibuffer.set_excerpts_for_path(
22407                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22408                buffer.clone(),
22409                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22410                2,
22411                cx,
22412            );
22413        }
22414        multibuffer
22415    });
22416
22417    let editor = cx.add_window(|window, cx| {
22418        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22419    });
22420    cx.run_until_parked();
22421
22422    let snapshot = editor
22423        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22424        .unwrap();
22425    let hunks = snapshot
22426        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22427        .map(|hunk| match hunk {
22428            DisplayDiffHunk::Unfolded {
22429                display_row_range, ..
22430            } => display_row_range,
22431            DisplayDiffHunk::Folded { .. } => unreachable!(),
22432        })
22433        .collect::<Vec<_>>();
22434    assert_eq!(
22435        hunks,
22436        [
22437            DisplayRow(2)..DisplayRow(4),
22438            DisplayRow(7)..DisplayRow(9),
22439            DisplayRow(12)..DisplayRow(14),
22440        ]
22441    );
22442}
22443
22444#[gpui::test]
22445async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22446    init_test(cx, |_| {});
22447
22448    let mut cx = EditorTestContext::new(cx).await;
22449    cx.set_head_text(indoc! { "
22450        one
22451        two
22452        three
22453        four
22454        five
22455        "
22456    });
22457    cx.set_index_text(indoc! { "
22458        one
22459        two
22460        three
22461        four
22462        five
22463        "
22464    });
22465    cx.set_state(indoc! {"
22466        one
22467        TWO
22468        ˇTHREE
22469        FOUR
22470        five
22471    "});
22472    cx.run_until_parked();
22473    cx.update_editor(|editor, window, cx| {
22474        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22475    });
22476    cx.run_until_parked();
22477    cx.assert_index_text(Some(indoc! {"
22478        one
22479        TWO
22480        THREE
22481        FOUR
22482        five
22483    "}));
22484    cx.set_state(indoc! { "
22485        one
22486        TWO
22487        ˇTHREE-HUNDRED
22488        FOUR
22489        five
22490    "});
22491    cx.run_until_parked();
22492    cx.update_editor(|editor, window, cx| {
22493        let snapshot = editor.snapshot(window, cx);
22494        let hunks = editor
22495            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22496            .collect::<Vec<_>>();
22497        assert_eq!(hunks.len(), 1);
22498        assert_eq!(
22499            hunks[0].status(),
22500            DiffHunkStatus {
22501                kind: DiffHunkStatusKind::Modified,
22502                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22503            }
22504        );
22505
22506        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22507    });
22508    cx.run_until_parked();
22509    cx.assert_index_text(Some(indoc! {"
22510        one
22511        TWO
22512        THREE-HUNDRED
22513        FOUR
22514        five
22515    "}));
22516}
22517
22518#[gpui::test]
22519fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22520    init_test(cx, |_| {});
22521
22522    let editor = cx.add_window(|window, cx| {
22523        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22524        build_editor(buffer, window, cx)
22525    });
22526
22527    let render_args = Arc::new(Mutex::new(None));
22528    let snapshot = editor
22529        .update(cx, |editor, window, cx| {
22530            let snapshot = editor.buffer().read(cx).snapshot(cx);
22531            let range =
22532                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22533
22534            struct RenderArgs {
22535                row: MultiBufferRow,
22536                folded: bool,
22537                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22538            }
22539
22540            let crease = Crease::inline(
22541                range,
22542                FoldPlaceholder::test(),
22543                {
22544                    let toggle_callback = render_args.clone();
22545                    move |row, folded, callback, _window, _cx| {
22546                        *toggle_callback.lock() = Some(RenderArgs {
22547                            row,
22548                            folded,
22549                            callback,
22550                        });
22551                        div()
22552                    }
22553                },
22554                |_row, _folded, _window, _cx| div(),
22555            );
22556
22557            editor.insert_creases(Some(crease), cx);
22558            let snapshot = editor.snapshot(window, cx);
22559            let _div =
22560                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22561            snapshot
22562        })
22563        .unwrap();
22564
22565    let render_args = render_args.lock().take().unwrap();
22566    assert_eq!(render_args.row, MultiBufferRow(1));
22567    assert!(!render_args.folded);
22568    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22569
22570    cx.update_window(*editor, |_, window, cx| {
22571        (render_args.callback)(true, window, cx)
22572    })
22573    .unwrap();
22574    let snapshot = editor
22575        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22576        .unwrap();
22577    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22578
22579    cx.update_window(*editor, |_, window, cx| {
22580        (render_args.callback)(false, window, cx)
22581    })
22582    .unwrap();
22583    let snapshot = editor
22584        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22585        .unwrap();
22586    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22587}
22588
22589#[gpui::test]
22590async fn test_input_text(cx: &mut TestAppContext) {
22591    init_test(cx, |_| {});
22592    let mut cx = EditorTestContext::new(cx).await;
22593
22594    cx.set_state(
22595        &r#"ˇone
22596        two
22597
22598        three
22599        fourˇ
22600        five
22601
22602        siˇx"#
22603            .unindent(),
22604    );
22605
22606    cx.dispatch_action(HandleInput(String::new()));
22607    cx.assert_editor_state(
22608        &r#"ˇone
22609        two
22610
22611        three
22612        fourˇ
22613        five
22614
22615        siˇx"#
22616            .unindent(),
22617    );
22618
22619    cx.dispatch_action(HandleInput("AAAA".to_string()));
22620    cx.assert_editor_state(
22621        &r#"AAAAˇone
22622        two
22623
22624        three
22625        fourAAAAˇ
22626        five
22627
22628        siAAAAˇx"#
22629            .unindent(),
22630    );
22631}
22632
22633#[gpui::test]
22634async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22635    init_test(cx, |_| {});
22636
22637    let mut cx = EditorTestContext::new(cx).await;
22638    cx.set_state(
22639        r#"let foo = 1;
22640let foo = 2;
22641let foo = 3;
22642let fooˇ = 4;
22643let foo = 5;
22644let foo = 6;
22645let foo = 7;
22646let foo = 8;
22647let foo = 9;
22648let foo = 10;
22649let foo = 11;
22650let foo = 12;
22651let foo = 13;
22652let foo = 14;
22653let foo = 15;"#,
22654    );
22655
22656    cx.update_editor(|e, window, cx| {
22657        assert_eq!(
22658            e.next_scroll_position,
22659            NextScrollCursorCenterTopBottom::Center,
22660            "Default next scroll direction is center",
22661        );
22662
22663        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22664        assert_eq!(
22665            e.next_scroll_position,
22666            NextScrollCursorCenterTopBottom::Top,
22667            "After center, next scroll direction should be top",
22668        );
22669
22670        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22671        assert_eq!(
22672            e.next_scroll_position,
22673            NextScrollCursorCenterTopBottom::Bottom,
22674            "After top, next scroll direction should be bottom",
22675        );
22676
22677        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22678        assert_eq!(
22679            e.next_scroll_position,
22680            NextScrollCursorCenterTopBottom::Center,
22681            "After bottom, scrolling should start over",
22682        );
22683
22684        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22685        assert_eq!(
22686            e.next_scroll_position,
22687            NextScrollCursorCenterTopBottom::Top,
22688            "Scrolling continues if retriggered fast enough"
22689        );
22690    });
22691
22692    cx.executor()
22693        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22694    cx.executor().run_until_parked();
22695    cx.update_editor(|e, _, _| {
22696        assert_eq!(
22697            e.next_scroll_position,
22698            NextScrollCursorCenterTopBottom::Center,
22699            "If scrolling is not triggered fast enough, it should reset"
22700        );
22701    });
22702}
22703
22704#[gpui::test]
22705async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22706    init_test(cx, |_| {});
22707    let mut cx = EditorLspTestContext::new_rust(
22708        lsp::ServerCapabilities {
22709            definition_provider: Some(lsp::OneOf::Left(true)),
22710            references_provider: Some(lsp::OneOf::Left(true)),
22711            ..lsp::ServerCapabilities::default()
22712        },
22713        cx,
22714    )
22715    .await;
22716
22717    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22718        let go_to_definition = cx
22719            .lsp
22720            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22721                move |params, _| async move {
22722                    if empty_go_to_definition {
22723                        Ok(None)
22724                    } else {
22725                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22726                            uri: params.text_document_position_params.text_document.uri,
22727                            range: lsp::Range::new(
22728                                lsp::Position::new(4, 3),
22729                                lsp::Position::new(4, 6),
22730                            ),
22731                        })))
22732                    }
22733                },
22734            );
22735        let references = cx
22736            .lsp
22737            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22738                Ok(Some(vec![lsp::Location {
22739                    uri: params.text_document_position.text_document.uri,
22740                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22741                }]))
22742            });
22743        (go_to_definition, references)
22744    };
22745
22746    cx.set_state(
22747        &r#"fn one() {
22748            let mut a = ˇtwo();
22749        }
22750
22751        fn two() {}"#
22752            .unindent(),
22753    );
22754    set_up_lsp_handlers(false, &mut cx);
22755    let navigated = cx
22756        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22757        .await
22758        .expect("Failed to navigate to definition");
22759    assert_eq!(
22760        navigated,
22761        Navigated::Yes,
22762        "Should have navigated to definition from the GetDefinition response"
22763    );
22764    cx.assert_editor_state(
22765        &r#"fn one() {
22766            let mut a = two();
22767        }
22768
22769        fn «twoˇ»() {}"#
22770            .unindent(),
22771    );
22772
22773    let editors = cx.update_workspace(|workspace, _, cx| {
22774        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22775    });
22776    cx.update_editor(|_, _, test_editor_cx| {
22777        assert_eq!(
22778            editors.len(),
22779            1,
22780            "Initially, only one, test, editor should be open in the workspace"
22781        );
22782        assert_eq!(
22783            test_editor_cx.entity(),
22784            editors.last().expect("Asserted len is 1").clone()
22785        );
22786    });
22787
22788    set_up_lsp_handlers(true, &mut cx);
22789    let navigated = cx
22790        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22791        .await
22792        .expect("Failed to navigate to lookup references");
22793    assert_eq!(
22794        navigated,
22795        Navigated::Yes,
22796        "Should have navigated to references as a fallback after empty GoToDefinition response"
22797    );
22798    // We should not change the selections in the existing file,
22799    // if opening another milti buffer with the references
22800    cx.assert_editor_state(
22801        &r#"fn one() {
22802            let mut a = two();
22803        }
22804
22805        fn «twoˇ»() {}"#
22806            .unindent(),
22807    );
22808    let editors = cx.update_workspace(|workspace, _, cx| {
22809        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22810    });
22811    cx.update_editor(|_, _, test_editor_cx| {
22812        assert_eq!(
22813            editors.len(),
22814            2,
22815            "After falling back to references search, we open a new editor with the results"
22816        );
22817        let references_fallback_text = editors
22818            .into_iter()
22819            .find(|new_editor| *new_editor != test_editor_cx.entity())
22820            .expect("Should have one non-test editor now")
22821            .read(test_editor_cx)
22822            .text(test_editor_cx);
22823        assert_eq!(
22824            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22825            "Should use the range from the references response and not the GoToDefinition one"
22826        );
22827    });
22828}
22829
22830#[gpui::test]
22831async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22832    init_test(cx, |_| {});
22833    cx.update(|cx| {
22834        let mut editor_settings = EditorSettings::get_global(cx).clone();
22835        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22836        EditorSettings::override_global(editor_settings, cx);
22837    });
22838    let mut cx = EditorLspTestContext::new_rust(
22839        lsp::ServerCapabilities {
22840            definition_provider: Some(lsp::OneOf::Left(true)),
22841            references_provider: Some(lsp::OneOf::Left(true)),
22842            ..lsp::ServerCapabilities::default()
22843        },
22844        cx,
22845    )
22846    .await;
22847    let original_state = r#"fn one() {
22848        let mut a = ˇtwo();
22849    }
22850
22851    fn two() {}"#
22852        .unindent();
22853    cx.set_state(&original_state);
22854
22855    let mut go_to_definition = cx
22856        .lsp
22857        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22858            move |_, _| async move { Ok(None) },
22859        );
22860    let _references = cx
22861        .lsp
22862        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22863            panic!("Should not call for references with no go to definition fallback")
22864        });
22865
22866    let navigated = cx
22867        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22868        .await
22869        .expect("Failed to navigate to lookup references");
22870    go_to_definition
22871        .next()
22872        .await
22873        .expect("Should have called the go_to_definition handler");
22874
22875    assert_eq!(
22876        navigated,
22877        Navigated::No,
22878        "Should have navigated to references as a fallback after empty GoToDefinition response"
22879    );
22880    cx.assert_editor_state(&original_state);
22881    let editors = cx.update_workspace(|workspace, _, cx| {
22882        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22883    });
22884    cx.update_editor(|_, _, _| {
22885        assert_eq!(
22886            editors.len(),
22887            1,
22888            "After unsuccessful fallback, no other editor should have been opened"
22889        );
22890    });
22891}
22892
22893#[gpui::test]
22894async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22895    init_test(cx, |_| {});
22896    let mut cx = EditorLspTestContext::new_rust(
22897        lsp::ServerCapabilities {
22898            references_provider: Some(lsp::OneOf::Left(true)),
22899            ..lsp::ServerCapabilities::default()
22900        },
22901        cx,
22902    )
22903    .await;
22904
22905    cx.set_state(
22906        &r#"
22907        fn one() {
22908            let mut a = two();
22909        }
22910
22911        fn ˇtwo() {}"#
22912            .unindent(),
22913    );
22914    cx.lsp
22915        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22916            Ok(Some(vec![
22917                lsp::Location {
22918                    uri: params.text_document_position.text_document.uri.clone(),
22919                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22920                },
22921                lsp::Location {
22922                    uri: params.text_document_position.text_document.uri,
22923                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22924                },
22925            ]))
22926        });
22927    let navigated = cx
22928        .update_editor(|editor, window, cx| {
22929            editor.find_all_references(&FindAllReferences::default(), window, cx)
22930        })
22931        .unwrap()
22932        .await
22933        .expect("Failed to navigate to references");
22934    assert_eq!(
22935        navigated,
22936        Navigated::Yes,
22937        "Should have navigated to references from the FindAllReferences response"
22938    );
22939    cx.assert_editor_state(
22940        &r#"fn one() {
22941            let mut a = two();
22942        }
22943
22944        fn ˇtwo() {}"#
22945            .unindent(),
22946    );
22947
22948    let editors = cx.update_workspace(|workspace, _, cx| {
22949        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22950    });
22951    cx.update_editor(|_, _, _| {
22952        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22953    });
22954
22955    cx.set_state(
22956        &r#"fn one() {
22957            let mut a = ˇtwo();
22958        }
22959
22960        fn two() {}"#
22961            .unindent(),
22962    );
22963    let navigated = cx
22964        .update_editor(|editor, window, cx| {
22965            editor.find_all_references(&FindAllReferences::default(), window, cx)
22966        })
22967        .unwrap()
22968        .await
22969        .expect("Failed to navigate to references");
22970    assert_eq!(
22971        navigated,
22972        Navigated::Yes,
22973        "Should have navigated to references from the FindAllReferences response"
22974    );
22975    cx.assert_editor_state(
22976        &r#"fn one() {
22977            let mut a = ˇtwo();
22978        }
22979
22980        fn two() {}"#
22981            .unindent(),
22982    );
22983    let editors = cx.update_workspace(|workspace, _, cx| {
22984        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22985    });
22986    cx.update_editor(|_, _, _| {
22987        assert_eq!(
22988            editors.len(),
22989            2,
22990            "should have re-used the previous multibuffer"
22991        );
22992    });
22993
22994    cx.set_state(
22995        &r#"fn one() {
22996            let mut a = ˇtwo();
22997        }
22998        fn three() {}
22999        fn two() {}"#
23000            .unindent(),
23001    );
23002    cx.lsp
23003        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23004            Ok(Some(vec![
23005                lsp::Location {
23006                    uri: params.text_document_position.text_document.uri.clone(),
23007                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23008                },
23009                lsp::Location {
23010                    uri: params.text_document_position.text_document.uri,
23011                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23012                },
23013            ]))
23014        });
23015    let navigated = cx
23016        .update_editor(|editor, window, cx| {
23017            editor.find_all_references(&FindAllReferences::default(), window, cx)
23018        })
23019        .unwrap()
23020        .await
23021        .expect("Failed to navigate to references");
23022    assert_eq!(
23023        navigated,
23024        Navigated::Yes,
23025        "Should have navigated to references from the FindAllReferences response"
23026    );
23027    cx.assert_editor_state(
23028        &r#"fn one() {
23029                let mut a = ˇtwo();
23030            }
23031            fn three() {}
23032            fn two() {}"#
23033            .unindent(),
23034    );
23035    let editors = cx.update_workspace(|workspace, _, cx| {
23036        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23037    });
23038    cx.update_editor(|_, _, _| {
23039        assert_eq!(
23040            editors.len(),
23041            3,
23042            "should have used a new multibuffer as offsets changed"
23043        );
23044    });
23045}
23046#[gpui::test]
23047async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23048    init_test(cx, |_| {});
23049
23050    let language = Arc::new(Language::new(
23051        LanguageConfig::default(),
23052        Some(tree_sitter_rust::LANGUAGE.into()),
23053    ));
23054
23055    let text = r#"
23056        #[cfg(test)]
23057        mod tests() {
23058            #[test]
23059            fn runnable_1() {
23060                let a = 1;
23061            }
23062
23063            #[test]
23064            fn runnable_2() {
23065                let a = 1;
23066                let b = 2;
23067            }
23068        }
23069    "#
23070    .unindent();
23071
23072    let fs = FakeFs::new(cx.executor());
23073    fs.insert_file("/file.rs", Default::default()).await;
23074
23075    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23076    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23077    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23078    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23079    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23080
23081    let editor = cx.new_window_entity(|window, cx| {
23082        Editor::new(
23083            EditorMode::full(),
23084            multi_buffer,
23085            Some(project.clone()),
23086            window,
23087            cx,
23088        )
23089    });
23090
23091    editor.update_in(cx, |editor, window, cx| {
23092        let snapshot = editor.buffer().read(cx).snapshot(cx);
23093        editor.tasks.insert(
23094            (buffer.read(cx).remote_id(), 3),
23095            RunnableTasks {
23096                templates: vec![],
23097                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23098                column: 0,
23099                extra_variables: HashMap::default(),
23100                context_range: BufferOffset(43)..BufferOffset(85),
23101            },
23102        );
23103        editor.tasks.insert(
23104            (buffer.read(cx).remote_id(), 8),
23105            RunnableTasks {
23106                templates: vec![],
23107                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23108                column: 0,
23109                extra_variables: HashMap::default(),
23110                context_range: BufferOffset(86)..BufferOffset(191),
23111            },
23112        );
23113
23114        // Test finding task when cursor is inside function body
23115        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23116            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23117        });
23118        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23119        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23120
23121        // Test finding task when cursor is on function name
23122        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23123            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23124        });
23125        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23126        assert_eq!(row, 8, "Should find task when cursor is on function name");
23127    });
23128}
23129
23130#[gpui::test]
23131async fn test_folding_buffers(cx: &mut TestAppContext) {
23132    init_test(cx, |_| {});
23133
23134    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23135    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23136    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23137
23138    let fs = FakeFs::new(cx.executor());
23139    fs.insert_tree(
23140        path!("/a"),
23141        json!({
23142            "first.rs": sample_text_1,
23143            "second.rs": sample_text_2,
23144            "third.rs": sample_text_3,
23145        }),
23146    )
23147    .await;
23148    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23149    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23150    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23151    let worktree = project.update(cx, |project, cx| {
23152        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23153        assert_eq!(worktrees.len(), 1);
23154        worktrees.pop().unwrap()
23155    });
23156    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23157
23158    let buffer_1 = project
23159        .update(cx, |project, cx| {
23160            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23161        })
23162        .await
23163        .unwrap();
23164    let buffer_2 = project
23165        .update(cx, |project, cx| {
23166            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23167        })
23168        .await
23169        .unwrap();
23170    let buffer_3 = project
23171        .update(cx, |project, cx| {
23172            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23173        })
23174        .await
23175        .unwrap();
23176
23177    let multi_buffer = cx.new(|cx| {
23178        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23179        multi_buffer.push_excerpts(
23180            buffer_1.clone(),
23181            [
23182                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23183                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23184                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23185            ],
23186            cx,
23187        );
23188        multi_buffer.push_excerpts(
23189            buffer_2.clone(),
23190            [
23191                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23192                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23193                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23194            ],
23195            cx,
23196        );
23197        multi_buffer.push_excerpts(
23198            buffer_3.clone(),
23199            [
23200                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23201                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23202                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23203            ],
23204            cx,
23205        );
23206        multi_buffer
23207    });
23208    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23209        Editor::new(
23210            EditorMode::full(),
23211            multi_buffer.clone(),
23212            Some(project.clone()),
23213            window,
23214            cx,
23215        )
23216    });
23217
23218    assert_eq!(
23219        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23220        "\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",
23221    );
23222
23223    multi_buffer_editor.update(cx, |editor, cx| {
23224        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23225    });
23226    assert_eq!(
23227        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23228        "\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",
23229        "After folding the first buffer, its text should not be displayed"
23230    );
23231
23232    multi_buffer_editor.update(cx, |editor, cx| {
23233        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23234    });
23235    assert_eq!(
23236        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23237        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23238        "After folding the second buffer, its text should not be displayed"
23239    );
23240
23241    multi_buffer_editor.update(cx, |editor, cx| {
23242        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23243    });
23244    assert_eq!(
23245        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23246        "\n\n\n\n\n",
23247        "After folding the third buffer, its text should not be displayed"
23248    );
23249
23250    // Emulate selection inside the fold logic, that should work
23251    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23252        editor
23253            .snapshot(window, cx)
23254            .next_line_boundary(Point::new(0, 4));
23255    });
23256
23257    multi_buffer_editor.update(cx, |editor, cx| {
23258        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23259    });
23260    assert_eq!(
23261        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23262        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23263        "After unfolding the second buffer, its text should be displayed"
23264    );
23265
23266    // Typing inside of buffer 1 causes that buffer to be unfolded.
23267    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23268        assert_eq!(
23269            multi_buffer
23270                .read(cx)
23271                .snapshot(cx)
23272                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23273                .collect::<String>(),
23274            "bbbb"
23275        );
23276        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23277            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23278        });
23279        editor.handle_input("B", window, cx);
23280    });
23281
23282    assert_eq!(
23283        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23284        "\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",
23285        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23286    );
23287
23288    multi_buffer_editor.update(cx, |editor, cx| {
23289        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23290    });
23291    assert_eq!(
23292        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23293        "\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",
23294        "After unfolding the all buffers, all original text should be displayed"
23295    );
23296}
23297
23298#[gpui::test]
23299async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23300    init_test(cx, |_| {});
23301
23302    let sample_text_1 = "1111\n2222\n3333".to_string();
23303    let sample_text_2 = "4444\n5555\n6666".to_string();
23304    let sample_text_3 = "7777\n8888\n9999".to_string();
23305
23306    let fs = FakeFs::new(cx.executor());
23307    fs.insert_tree(
23308        path!("/a"),
23309        json!({
23310            "first.rs": sample_text_1,
23311            "second.rs": sample_text_2,
23312            "third.rs": sample_text_3,
23313        }),
23314    )
23315    .await;
23316    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23317    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23318    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23319    let worktree = project.update(cx, |project, cx| {
23320        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23321        assert_eq!(worktrees.len(), 1);
23322        worktrees.pop().unwrap()
23323    });
23324    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23325
23326    let buffer_1 = project
23327        .update(cx, |project, cx| {
23328            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23329        })
23330        .await
23331        .unwrap();
23332    let buffer_2 = project
23333        .update(cx, |project, cx| {
23334            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23335        })
23336        .await
23337        .unwrap();
23338    let buffer_3 = project
23339        .update(cx, |project, cx| {
23340            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23341        })
23342        .await
23343        .unwrap();
23344
23345    let multi_buffer = cx.new(|cx| {
23346        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23347        multi_buffer.push_excerpts(
23348            buffer_1.clone(),
23349            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23350            cx,
23351        );
23352        multi_buffer.push_excerpts(
23353            buffer_2.clone(),
23354            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23355            cx,
23356        );
23357        multi_buffer.push_excerpts(
23358            buffer_3.clone(),
23359            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23360            cx,
23361        );
23362        multi_buffer
23363    });
23364
23365    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23366        Editor::new(
23367            EditorMode::full(),
23368            multi_buffer,
23369            Some(project.clone()),
23370            window,
23371            cx,
23372        )
23373    });
23374
23375    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23376    assert_eq!(
23377        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23378        full_text,
23379    );
23380
23381    multi_buffer_editor.update(cx, |editor, cx| {
23382        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23383    });
23384    assert_eq!(
23385        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23386        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23387        "After folding the first buffer, its text should not be displayed"
23388    );
23389
23390    multi_buffer_editor.update(cx, |editor, cx| {
23391        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23392    });
23393
23394    assert_eq!(
23395        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23396        "\n\n\n\n\n\n7777\n8888\n9999",
23397        "After folding the second buffer, its text should not be displayed"
23398    );
23399
23400    multi_buffer_editor.update(cx, |editor, cx| {
23401        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23402    });
23403    assert_eq!(
23404        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23405        "\n\n\n\n\n",
23406        "After folding the third buffer, its text should not be displayed"
23407    );
23408
23409    multi_buffer_editor.update(cx, |editor, cx| {
23410        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23411    });
23412    assert_eq!(
23413        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23414        "\n\n\n\n4444\n5555\n6666\n\n",
23415        "After unfolding the second buffer, its text should be displayed"
23416    );
23417
23418    multi_buffer_editor.update(cx, |editor, cx| {
23419        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23420    });
23421    assert_eq!(
23422        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23423        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23424        "After unfolding the first buffer, its text should be displayed"
23425    );
23426
23427    multi_buffer_editor.update(cx, |editor, cx| {
23428        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23429    });
23430    assert_eq!(
23431        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23432        full_text,
23433        "After unfolding all buffers, all original text should be displayed"
23434    );
23435}
23436
23437#[gpui::test]
23438async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23439    init_test(cx, |_| {});
23440
23441    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23442
23443    let fs = FakeFs::new(cx.executor());
23444    fs.insert_tree(
23445        path!("/a"),
23446        json!({
23447            "main.rs": sample_text,
23448        }),
23449    )
23450    .await;
23451    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23453    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23454    let worktree = project.update(cx, |project, cx| {
23455        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23456        assert_eq!(worktrees.len(), 1);
23457        worktrees.pop().unwrap()
23458    });
23459    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23460
23461    let buffer_1 = project
23462        .update(cx, |project, cx| {
23463            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23464        })
23465        .await
23466        .unwrap();
23467
23468    let multi_buffer = cx.new(|cx| {
23469        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23470        multi_buffer.push_excerpts(
23471            buffer_1.clone(),
23472            [ExcerptRange::new(
23473                Point::new(0, 0)
23474                    ..Point::new(
23475                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23476                        0,
23477                    ),
23478            )],
23479            cx,
23480        );
23481        multi_buffer
23482    });
23483    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23484        Editor::new(
23485            EditorMode::full(),
23486            multi_buffer,
23487            Some(project.clone()),
23488            window,
23489            cx,
23490        )
23491    });
23492
23493    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23494    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23495        enum TestHighlight {}
23496        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23497        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23498        editor.highlight_text::<TestHighlight>(
23499            vec![highlight_range.clone()],
23500            HighlightStyle::color(Hsla::green()),
23501            cx,
23502        );
23503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23504            s.select_ranges(Some(highlight_range))
23505        });
23506    });
23507
23508    let full_text = format!("\n\n{sample_text}");
23509    assert_eq!(
23510        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23511        full_text,
23512    );
23513}
23514
23515#[gpui::test]
23516async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23517    init_test(cx, |_| {});
23518    cx.update(|cx| {
23519        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23520            "keymaps/default-linux.json",
23521            cx,
23522        )
23523        .unwrap();
23524        cx.bind_keys(default_key_bindings);
23525    });
23526
23527    let (editor, cx) = cx.add_window_view(|window, cx| {
23528        let multi_buffer = MultiBuffer::build_multi(
23529            [
23530                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23531                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23532                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23533                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23534            ],
23535            cx,
23536        );
23537        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23538
23539        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23540        // fold all but the second buffer, so that we test navigating between two
23541        // adjacent folded buffers, as well as folded buffers at the start and
23542        // end the multibuffer
23543        editor.fold_buffer(buffer_ids[0], cx);
23544        editor.fold_buffer(buffer_ids[2], cx);
23545        editor.fold_buffer(buffer_ids[3], cx);
23546
23547        editor
23548    });
23549    cx.simulate_resize(size(px(1000.), px(1000.)));
23550
23551    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23552    cx.assert_excerpts_with_selections(indoc! {"
23553        [EXCERPT]
23554        ˇ[FOLDED]
23555        [EXCERPT]
23556        a1
23557        b1
23558        [EXCERPT]
23559        [FOLDED]
23560        [EXCERPT]
23561        [FOLDED]
23562        "
23563    });
23564    cx.simulate_keystroke("down");
23565    cx.assert_excerpts_with_selections(indoc! {"
23566        [EXCERPT]
23567        [FOLDED]
23568        [EXCERPT]
23569        ˇa1
23570        b1
23571        [EXCERPT]
23572        [FOLDED]
23573        [EXCERPT]
23574        [FOLDED]
23575        "
23576    });
23577    cx.simulate_keystroke("down");
23578    cx.assert_excerpts_with_selections(indoc! {"
23579        [EXCERPT]
23580        [FOLDED]
23581        [EXCERPT]
23582        a1
23583        ˇb1
23584        [EXCERPT]
23585        [FOLDED]
23586        [EXCERPT]
23587        [FOLDED]
23588        "
23589    });
23590    cx.simulate_keystroke("down");
23591    cx.assert_excerpts_with_selections(indoc! {"
23592        [EXCERPT]
23593        [FOLDED]
23594        [EXCERPT]
23595        a1
23596        b1
23597        ˇ[EXCERPT]
23598        [FOLDED]
23599        [EXCERPT]
23600        [FOLDED]
23601        "
23602    });
23603    cx.simulate_keystroke("down");
23604    cx.assert_excerpts_with_selections(indoc! {"
23605        [EXCERPT]
23606        [FOLDED]
23607        [EXCERPT]
23608        a1
23609        b1
23610        [EXCERPT]
23611        ˇ[FOLDED]
23612        [EXCERPT]
23613        [FOLDED]
23614        "
23615    });
23616    for _ in 0..5 {
23617        cx.simulate_keystroke("down");
23618        cx.assert_excerpts_with_selections(indoc! {"
23619            [EXCERPT]
23620            [FOLDED]
23621            [EXCERPT]
23622            a1
23623            b1
23624            [EXCERPT]
23625            [FOLDED]
23626            [EXCERPT]
23627            ˇ[FOLDED]
23628            "
23629        });
23630    }
23631
23632    cx.simulate_keystroke("up");
23633    cx.assert_excerpts_with_selections(indoc! {"
23634        [EXCERPT]
23635        [FOLDED]
23636        [EXCERPT]
23637        a1
23638        b1
23639        [EXCERPT]
23640        ˇ[FOLDED]
23641        [EXCERPT]
23642        [FOLDED]
23643        "
23644    });
23645    cx.simulate_keystroke("up");
23646    cx.assert_excerpts_with_selections(indoc! {"
23647        [EXCERPT]
23648        [FOLDED]
23649        [EXCERPT]
23650        a1
23651        b1
23652        ˇ[EXCERPT]
23653        [FOLDED]
23654        [EXCERPT]
23655        [FOLDED]
23656        "
23657    });
23658    cx.simulate_keystroke("up");
23659    cx.assert_excerpts_with_selections(indoc! {"
23660        [EXCERPT]
23661        [FOLDED]
23662        [EXCERPT]
23663        a1
23664        ˇb1
23665        [EXCERPT]
23666        [FOLDED]
23667        [EXCERPT]
23668        [FOLDED]
23669        "
23670    });
23671    cx.simulate_keystroke("up");
23672    cx.assert_excerpts_with_selections(indoc! {"
23673        [EXCERPT]
23674        [FOLDED]
23675        [EXCERPT]
23676        ˇa1
23677        b1
23678        [EXCERPT]
23679        [FOLDED]
23680        [EXCERPT]
23681        [FOLDED]
23682        "
23683    });
23684    for _ in 0..5 {
23685        cx.simulate_keystroke("up");
23686        cx.assert_excerpts_with_selections(indoc! {"
23687            [EXCERPT]
23688            ˇ[FOLDED]
23689            [EXCERPT]
23690            a1
23691            b1
23692            [EXCERPT]
23693            [FOLDED]
23694            [EXCERPT]
23695            [FOLDED]
23696            "
23697        });
23698    }
23699}
23700
23701#[gpui::test]
23702async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23703    init_test(cx, |_| {});
23704
23705    // Simple insertion
23706    assert_highlighted_edits(
23707        "Hello, world!",
23708        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23709        true,
23710        cx,
23711        |highlighted_edits, cx| {
23712            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23713            assert_eq!(highlighted_edits.highlights.len(), 1);
23714            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23715            assert_eq!(
23716                highlighted_edits.highlights[0].1.background_color,
23717                Some(cx.theme().status().created_background)
23718            );
23719        },
23720    )
23721    .await;
23722
23723    // Replacement
23724    assert_highlighted_edits(
23725        "This is a test.",
23726        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23727        false,
23728        cx,
23729        |highlighted_edits, cx| {
23730            assert_eq!(highlighted_edits.text, "That is a test.");
23731            assert_eq!(highlighted_edits.highlights.len(), 1);
23732            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23733            assert_eq!(
23734                highlighted_edits.highlights[0].1.background_color,
23735                Some(cx.theme().status().created_background)
23736            );
23737        },
23738    )
23739    .await;
23740
23741    // Multiple edits
23742    assert_highlighted_edits(
23743        "Hello, world!",
23744        vec![
23745            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23746            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23747        ],
23748        false,
23749        cx,
23750        |highlighted_edits, cx| {
23751            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23752            assert_eq!(highlighted_edits.highlights.len(), 2);
23753            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23754            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23755            assert_eq!(
23756                highlighted_edits.highlights[0].1.background_color,
23757                Some(cx.theme().status().created_background)
23758            );
23759            assert_eq!(
23760                highlighted_edits.highlights[1].1.background_color,
23761                Some(cx.theme().status().created_background)
23762            );
23763        },
23764    )
23765    .await;
23766
23767    // Multiple lines with edits
23768    assert_highlighted_edits(
23769        "First line\nSecond line\nThird line\nFourth line",
23770        vec![
23771            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23772            (
23773                Point::new(2, 0)..Point::new(2, 10),
23774                "New third line".to_string(),
23775            ),
23776            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23777        ],
23778        false,
23779        cx,
23780        |highlighted_edits, cx| {
23781            assert_eq!(
23782                highlighted_edits.text,
23783                "Second modified\nNew third line\nFourth updated line"
23784            );
23785            assert_eq!(highlighted_edits.highlights.len(), 3);
23786            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23787            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23788            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23789            for highlight in &highlighted_edits.highlights {
23790                assert_eq!(
23791                    highlight.1.background_color,
23792                    Some(cx.theme().status().created_background)
23793                );
23794            }
23795        },
23796    )
23797    .await;
23798}
23799
23800#[gpui::test]
23801async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23802    init_test(cx, |_| {});
23803
23804    // Deletion
23805    assert_highlighted_edits(
23806        "Hello, world!",
23807        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23808        true,
23809        cx,
23810        |highlighted_edits, cx| {
23811            assert_eq!(highlighted_edits.text, "Hello, world!");
23812            assert_eq!(highlighted_edits.highlights.len(), 1);
23813            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23814            assert_eq!(
23815                highlighted_edits.highlights[0].1.background_color,
23816                Some(cx.theme().status().deleted_background)
23817            );
23818        },
23819    )
23820    .await;
23821
23822    // Insertion
23823    assert_highlighted_edits(
23824        "Hello, world!",
23825        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23826        true,
23827        cx,
23828        |highlighted_edits, cx| {
23829            assert_eq!(highlighted_edits.highlights.len(), 1);
23830            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23831            assert_eq!(
23832                highlighted_edits.highlights[0].1.background_color,
23833                Some(cx.theme().status().created_background)
23834            );
23835        },
23836    )
23837    .await;
23838}
23839
23840async fn assert_highlighted_edits(
23841    text: &str,
23842    edits: Vec<(Range<Point>, String)>,
23843    include_deletions: bool,
23844    cx: &mut TestAppContext,
23845    assertion_fn: impl Fn(HighlightedText, &App),
23846) {
23847    let window = cx.add_window(|window, cx| {
23848        let buffer = MultiBuffer::build_simple(text, cx);
23849        Editor::new(EditorMode::full(), buffer, None, window, cx)
23850    });
23851    let cx = &mut VisualTestContext::from_window(*window, cx);
23852
23853    let (buffer, snapshot) = window
23854        .update(cx, |editor, _window, cx| {
23855            (
23856                editor.buffer().clone(),
23857                editor.buffer().read(cx).snapshot(cx),
23858            )
23859        })
23860        .unwrap();
23861
23862    let edits = edits
23863        .into_iter()
23864        .map(|(range, edit)| {
23865            (
23866                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23867                edit,
23868            )
23869        })
23870        .collect::<Vec<_>>();
23871
23872    let text_anchor_edits = edits
23873        .clone()
23874        .into_iter()
23875        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23876        .collect::<Vec<_>>();
23877
23878    let edit_preview = window
23879        .update(cx, |_, _window, cx| {
23880            buffer
23881                .read(cx)
23882                .as_singleton()
23883                .unwrap()
23884                .read(cx)
23885                .preview_edits(text_anchor_edits.into(), cx)
23886        })
23887        .unwrap()
23888        .await;
23889
23890    cx.update(|_window, cx| {
23891        let highlighted_edits = edit_prediction_edit_text(
23892            snapshot.as_singleton().unwrap().2,
23893            &edits,
23894            &edit_preview,
23895            include_deletions,
23896            cx,
23897        );
23898        assertion_fn(highlighted_edits, cx)
23899    });
23900}
23901
23902#[track_caller]
23903fn assert_breakpoint(
23904    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23905    path: &Arc<Path>,
23906    expected: Vec<(u32, Breakpoint)>,
23907) {
23908    if expected.is_empty() {
23909        assert!(!breakpoints.contains_key(path), "{}", path.display());
23910    } else {
23911        let mut breakpoint = breakpoints
23912            .get(path)
23913            .unwrap()
23914            .iter()
23915            .map(|breakpoint| {
23916                (
23917                    breakpoint.row,
23918                    Breakpoint {
23919                        message: breakpoint.message.clone(),
23920                        state: breakpoint.state,
23921                        condition: breakpoint.condition.clone(),
23922                        hit_condition: breakpoint.hit_condition.clone(),
23923                    },
23924                )
23925            })
23926            .collect::<Vec<_>>();
23927
23928        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23929
23930        assert_eq!(expected, breakpoint);
23931    }
23932}
23933
23934fn add_log_breakpoint_at_cursor(
23935    editor: &mut Editor,
23936    log_message: &str,
23937    window: &mut Window,
23938    cx: &mut Context<Editor>,
23939) {
23940    let (anchor, bp) = editor
23941        .breakpoints_at_cursors(window, cx)
23942        .first()
23943        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23944        .unwrap_or_else(|| {
23945            let snapshot = editor.snapshot(window, cx);
23946            let cursor_position: Point =
23947                editor.selections.newest(&snapshot.display_snapshot).head();
23948
23949            let breakpoint_position = snapshot
23950                .buffer_snapshot()
23951                .anchor_before(Point::new(cursor_position.row, 0));
23952
23953            (breakpoint_position, Breakpoint::new_log(log_message))
23954        });
23955
23956    editor.edit_breakpoint_at_anchor(
23957        anchor,
23958        bp,
23959        BreakpointEditAction::EditLogMessage(log_message.into()),
23960        cx,
23961    );
23962}
23963
23964#[gpui::test]
23965async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23966    init_test(cx, |_| {});
23967
23968    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23969    let fs = FakeFs::new(cx.executor());
23970    fs.insert_tree(
23971        path!("/a"),
23972        json!({
23973            "main.rs": sample_text,
23974        }),
23975    )
23976    .await;
23977    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23978    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23979    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23980
23981    let fs = FakeFs::new(cx.executor());
23982    fs.insert_tree(
23983        path!("/a"),
23984        json!({
23985            "main.rs": sample_text,
23986        }),
23987    )
23988    .await;
23989    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23990    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23991    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23992    let worktree_id = workspace
23993        .update(cx, |workspace, _window, cx| {
23994            workspace.project().update(cx, |project, cx| {
23995                project.worktrees(cx).next().unwrap().read(cx).id()
23996            })
23997        })
23998        .unwrap();
23999
24000    let buffer = project
24001        .update(cx, |project, cx| {
24002            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24003        })
24004        .await
24005        .unwrap();
24006
24007    let (editor, cx) = cx.add_window_view(|window, cx| {
24008        Editor::new(
24009            EditorMode::full(),
24010            MultiBuffer::build_from_buffer(buffer, cx),
24011            Some(project.clone()),
24012            window,
24013            cx,
24014        )
24015    });
24016
24017    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24018    let abs_path = project.read_with(cx, |project, cx| {
24019        project
24020            .absolute_path(&project_path, cx)
24021            .map(Arc::from)
24022            .unwrap()
24023    });
24024
24025    // assert we can add breakpoint on the first line
24026    editor.update_in(cx, |editor, window, cx| {
24027        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24028        editor.move_to_end(&MoveToEnd, window, cx);
24029        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24030    });
24031
24032    let breakpoints = editor.update(cx, |editor, cx| {
24033        editor
24034            .breakpoint_store()
24035            .as_ref()
24036            .unwrap()
24037            .read(cx)
24038            .all_source_breakpoints(cx)
24039    });
24040
24041    assert_eq!(1, breakpoints.len());
24042    assert_breakpoint(
24043        &breakpoints,
24044        &abs_path,
24045        vec![
24046            (0, Breakpoint::new_standard()),
24047            (3, Breakpoint::new_standard()),
24048        ],
24049    );
24050
24051    editor.update_in(cx, |editor, window, cx| {
24052        editor.move_to_beginning(&MoveToBeginning, window, cx);
24053        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24054    });
24055
24056    let breakpoints = editor.update(cx, |editor, cx| {
24057        editor
24058            .breakpoint_store()
24059            .as_ref()
24060            .unwrap()
24061            .read(cx)
24062            .all_source_breakpoints(cx)
24063    });
24064
24065    assert_eq!(1, breakpoints.len());
24066    assert_breakpoint(
24067        &breakpoints,
24068        &abs_path,
24069        vec![(3, Breakpoint::new_standard())],
24070    );
24071
24072    editor.update_in(cx, |editor, window, cx| {
24073        editor.move_to_end(&MoveToEnd, window, cx);
24074        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24075    });
24076
24077    let breakpoints = editor.update(cx, |editor, cx| {
24078        editor
24079            .breakpoint_store()
24080            .as_ref()
24081            .unwrap()
24082            .read(cx)
24083            .all_source_breakpoints(cx)
24084    });
24085
24086    assert_eq!(0, breakpoints.len());
24087    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24088}
24089
24090#[gpui::test]
24091async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24092    init_test(cx, |_| {});
24093
24094    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24095
24096    let fs = FakeFs::new(cx.executor());
24097    fs.insert_tree(
24098        path!("/a"),
24099        json!({
24100            "main.rs": sample_text,
24101        }),
24102    )
24103    .await;
24104    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24105    let (workspace, cx) =
24106        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24107
24108    let worktree_id = workspace.update(cx, |workspace, cx| {
24109        workspace.project().update(cx, |project, cx| {
24110            project.worktrees(cx).next().unwrap().read(cx).id()
24111        })
24112    });
24113
24114    let buffer = project
24115        .update(cx, |project, cx| {
24116            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24117        })
24118        .await
24119        .unwrap();
24120
24121    let (editor, cx) = cx.add_window_view(|window, cx| {
24122        Editor::new(
24123            EditorMode::full(),
24124            MultiBuffer::build_from_buffer(buffer, cx),
24125            Some(project.clone()),
24126            window,
24127            cx,
24128        )
24129    });
24130
24131    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24132    let abs_path = project.read_with(cx, |project, cx| {
24133        project
24134            .absolute_path(&project_path, cx)
24135            .map(Arc::from)
24136            .unwrap()
24137    });
24138
24139    editor.update_in(cx, |editor, window, cx| {
24140        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24141    });
24142
24143    let breakpoints = editor.update(cx, |editor, cx| {
24144        editor
24145            .breakpoint_store()
24146            .as_ref()
24147            .unwrap()
24148            .read(cx)
24149            .all_source_breakpoints(cx)
24150    });
24151
24152    assert_breakpoint(
24153        &breakpoints,
24154        &abs_path,
24155        vec![(0, Breakpoint::new_log("hello world"))],
24156    );
24157
24158    // Removing a log message from a log breakpoint should remove it
24159    editor.update_in(cx, |editor, window, cx| {
24160        add_log_breakpoint_at_cursor(editor, "", window, cx);
24161    });
24162
24163    let breakpoints = editor.update(cx, |editor, cx| {
24164        editor
24165            .breakpoint_store()
24166            .as_ref()
24167            .unwrap()
24168            .read(cx)
24169            .all_source_breakpoints(cx)
24170    });
24171
24172    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24173
24174    editor.update_in(cx, |editor, window, cx| {
24175        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24176        editor.move_to_end(&MoveToEnd, window, cx);
24177        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24178        // Not adding a log message to a standard breakpoint shouldn't remove it
24179        add_log_breakpoint_at_cursor(editor, "", window, cx);
24180    });
24181
24182    let breakpoints = editor.update(cx, |editor, cx| {
24183        editor
24184            .breakpoint_store()
24185            .as_ref()
24186            .unwrap()
24187            .read(cx)
24188            .all_source_breakpoints(cx)
24189    });
24190
24191    assert_breakpoint(
24192        &breakpoints,
24193        &abs_path,
24194        vec![
24195            (0, Breakpoint::new_standard()),
24196            (3, Breakpoint::new_standard()),
24197        ],
24198    );
24199
24200    editor.update_in(cx, |editor, window, cx| {
24201        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24202    });
24203
24204    let breakpoints = editor.update(cx, |editor, cx| {
24205        editor
24206            .breakpoint_store()
24207            .as_ref()
24208            .unwrap()
24209            .read(cx)
24210            .all_source_breakpoints(cx)
24211    });
24212
24213    assert_breakpoint(
24214        &breakpoints,
24215        &abs_path,
24216        vec![
24217            (0, Breakpoint::new_standard()),
24218            (3, Breakpoint::new_log("hello world")),
24219        ],
24220    );
24221
24222    editor.update_in(cx, |editor, window, cx| {
24223        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24224    });
24225
24226    let breakpoints = editor.update(cx, |editor, cx| {
24227        editor
24228            .breakpoint_store()
24229            .as_ref()
24230            .unwrap()
24231            .read(cx)
24232            .all_source_breakpoints(cx)
24233    });
24234
24235    assert_breakpoint(
24236        &breakpoints,
24237        &abs_path,
24238        vec![
24239            (0, Breakpoint::new_standard()),
24240            (3, Breakpoint::new_log("hello Earth!!")),
24241        ],
24242    );
24243}
24244
24245/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24246/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24247/// or when breakpoints were placed out of order. This tests for a regression too
24248#[gpui::test]
24249async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24250    init_test(cx, |_| {});
24251
24252    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24253    let fs = FakeFs::new(cx.executor());
24254    fs.insert_tree(
24255        path!("/a"),
24256        json!({
24257            "main.rs": sample_text,
24258        }),
24259    )
24260    .await;
24261    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24262    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24263    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24264
24265    let fs = FakeFs::new(cx.executor());
24266    fs.insert_tree(
24267        path!("/a"),
24268        json!({
24269            "main.rs": sample_text,
24270        }),
24271    )
24272    .await;
24273    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24274    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24275    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24276    let worktree_id = workspace
24277        .update(cx, |workspace, _window, cx| {
24278            workspace.project().update(cx, |project, cx| {
24279                project.worktrees(cx).next().unwrap().read(cx).id()
24280            })
24281        })
24282        .unwrap();
24283
24284    let buffer = project
24285        .update(cx, |project, cx| {
24286            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24287        })
24288        .await
24289        .unwrap();
24290
24291    let (editor, cx) = cx.add_window_view(|window, cx| {
24292        Editor::new(
24293            EditorMode::full(),
24294            MultiBuffer::build_from_buffer(buffer, cx),
24295            Some(project.clone()),
24296            window,
24297            cx,
24298        )
24299    });
24300
24301    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24302    let abs_path = project.read_with(cx, |project, cx| {
24303        project
24304            .absolute_path(&project_path, cx)
24305            .map(Arc::from)
24306            .unwrap()
24307    });
24308
24309    // assert we can add breakpoint on the first line
24310    editor.update_in(cx, |editor, window, cx| {
24311        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24312        editor.move_to_end(&MoveToEnd, window, cx);
24313        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24314        editor.move_up(&MoveUp, window, cx);
24315        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24316    });
24317
24318    let breakpoints = editor.update(cx, |editor, cx| {
24319        editor
24320            .breakpoint_store()
24321            .as_ref()
24322            .unwrap()
24323            .read(cx)
24324            .all_source_breakpoints(cx)
24325    });
24326
24327    assert_eq!(1, breakpoints.len());
24328    assert_breakpoint(
24329        &breakpoints,
24330        &abs_path,
24331        vec![
24332            (0, Breakpoint::new_standard()),
24333            (2, Breakpoint::new_standard()),
24334            (3, Breakpoint::new_standard()),
24335        ],
24336    );
24337
24338    editor.update_in(cx, |editor, window, cx| {
24339        editor.move_to_beginning(&MoveToBeginning, window, cx);
24340        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24341        editor.move_to_end(&MoveToEnd, window, cx);
24342        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24343        // Disabling a breakpoint that doesn't exist should do nothing
24344        editor.move_up(&MoveUp, window, cx);
24345        editor.move_up(&MoveUp, window, cx);
24346        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24347    });
24348
24349    let breakpoints = editor.update(cx, |editor, cx| {
24350        editor
24351            .breakpoint_store()
24352            .as_ref()
24353            .unwrap()
24354            .read(cx)
24355            .all_source_breakpoints(cx)
24356    });
24357
24358    let disable_breakpoint = {
24359        let mut bp = Breakpoint::new_standard();
24360        bp.state = BreakpointState::Disabled;
24361        bp
24362    };
24363
24364    assert_eq!(1, breakpoints.len());
24365    assert_breakpoint(
24366        &breakpoints,
24367        &abs_path,
24368        vec![
24369            (0, disable_breakpoint.clone()),
24370            (2, Breakpoint::new_standard()),
24371            (3, disable_breakpoint.clone()),
24372        ],
24373    );
24374
24375    editor.update_in(cx, |editor, window, cx| {
24376        editor.move_to_beginning(&MoveToBeginning, window, cx);
24377        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24378        editor.move_to_end(&MoveToEnd, window, cx);
24379        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24380        editor.move_up(&MoveUp, window, cx);
24381        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24382    });
24383
24384    let breakpoints = editor.update(cx, |editor, cx| {
24385        editor
24386            .breakpoint_store()
24387            .as_ref()
24388            .unwrap()
24389            .read(cx)
24390            .all_source_breakpoints(cx)
24391    });
24392
24393    assert_eq!(1, breakpoints.len());
24394    assert_breakpoint(
24395        &breakpoints,
24396        &abs_path,
24397        vec![
24398            (0, Breakpoint::new_standard()),
24399            (2, disable_breakpoint),
24400            (3, Breakpoint::new_standard()),
24401        ],
24402    );
24403}
24404
24405#[gpui::test]
24406async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24407    init_test(cx, |_| {});
24408    let capabilities = lsp::ServerCapabilities {
24409        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24410            prepare_provider: Some(true),
24411            work_done_progress_options: Default::default(),
24412        })),
24413        ..Default::default()
24414    };
24415    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24416
24417    cx.set_state(indoc! {"
24418        struct Fˇoo {}
24419    "});
24420
24421    cx.update_editor(|editor, _, cx| {
24422        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24423        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24424        editor.highlight_background::<DocumentHighlightRead>(
24425            &[highlight_range],
24426            |_, theme| theme.colors().editor_document_highlight_read_background,
24427            cx,
24428        );
24429    });
24430
24431    let mut prepare_rename_handler = cx
24432        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24433            move |_, _, _| async move {
24434                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24435                    start: lsp::Position {
24436                        line: 0,
24437                        character: 7,
24438                    },
24439                    end: lsp::Position {
24440                        line: 0,
24441                        character: 10,
24442                    },
24443                })))
24444            },
24445        );
24446    let prepare_rename_task = cx
24447        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24448        .expect("Prepare rename was not started");
24449    prepare_rename_handler.next().await.unwrap();
24450    prepare_rename_task.await.expect("Prepare rename failed");
24451
24452    let mut rename_handler =
24453        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24454            let edit = lsp::TextEdit {
24455                range: lsp::Range {
24456                    start: lsp::Position {
24457                        line: 0,
24458                        character: 7,
24459                    },
24460                    end: lsp::Position {
24461                        line: 0,
24462                        character: 10,
24463                    },
24464                },
24465                new_text: "FooRenamed".to_string(),
24466            };
24467            Ok(Some(lsp::WorkspaceEdit::new(
24468                // Specify the same edit twice
24469                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24470            )))
24471        });
24472    let rename_task = cx
24473        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24474        .expect("Confirm rename was not started");
24475    rename_handler.next().await.unwrap();
24476    rename_task.await.expect("Confirm rename failed");
24477    cx.run_until_parked();
24478
24479    // Despite two edits, only one is actually applied as those are identical
24480    cx.assert_editor_state(indoc! {"
24481        struct FooRenamedˇ {}
24482    "});
24483}
24484
24485#[gpui::test]
24486async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24487    init_test(cx, |_| {});
24488    // These capabilities indicate that the server does not support prepare rename.
24489    let capabilities = lsp::ServerCapabilities {
24490        rename_provider: Some(lsp::OneOf::Left(true)),
24491        ..Default::default()
24492    };
24493    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24494
24495    cx.set_state(indoc! {"
24496        struct Fˇoo {}
24497    "});
24498
24499    cx.update_editor(|editor, _window, cx| {
24500        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24501        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24502        editor.highlight_background::<DocumentHighlightRead>(
24503            &[highlight_range],
24504            |_, theme| theme.colors().editor_document_highlight_read_background,
24505            cx,
24506        );
24507    });
24508
24509    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24510        .expect("Prepare rename was not started")
24511        .await
24512        .expect("Prepare rename failed");
24513
24514    let mut rename_handler =
24515        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24516            let edit = lsp::TextEdit {
24517                range: lsp::Range {
24518                    start: lsp::Position {
24519                        line: 0,
24520                        character: 7,
24521                    },
24522                    end: lsp::Position {
24523                        line: 0,
24524                        character: 10,
24525                    },
24526                },
24527                new_text: "FooRenamed".to_string(),
24528            };
24529            Ok(Some(lsp::WorkspaceEdit::new(
24530                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24531            )))
24532        });
24533    let rename_task = cx
24534        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24535        .expect("Confirm rename was not started");
24536    rename_handler.next().await.unwrap();
24537    rename_task.await.expect("Confirm rename failed");
24538    cx.run_until_parked();
24539
24540    // Correct range is renamed, as `surrounding_word` is used to find it.
24541    cx.assert_editor_state(indoc! {"
24542        struct FooRenamedˇ {}
24543    "});
24544}
24545
24546#[gpui::test]
24547async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24548    init_test(cx, |_| {});
24549    let mut cx = EditorTestContext::new(cx).await;
24550
24551    let language = Arc::new(
24552        Language::new(
24553            LanguageConfig::default(),
24554            Some(tree_sitter_html::LANGUAGE.into()),
24555        )
24556        .with_brackets_query(
24557            r#"
24558            ("<" @open "/>" @close)
24559            ("</" @open ">" @close)
24560            ("<" @open ">" @close)
24561            ("\"" @open "\"" @close)
24562            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24563        "#,
24564        )
24565        .unwrap(),
24566    );
24567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24568
24569    cx.set_state(indoc! {"
24570        <span>ˇ</span>
24571    "});
24572    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24573    cx.assert_editor_state(indoc! {"
24574        <span>
24575        ˇ
24576        </span>
24577    "});
24578
24579    cx.set_state(indoc! {"
24580        <span><span></span>ˇ</span>
24581    "});
24582    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24583    cx.assert_editor_state(indoc! {"
24584        <span><span></span>
24585        ˇ</span>
24586    "});
24587
24588    cx.set_state(indoc! {"
24589        <span>ˇ
24590        </span>
24591    "});
24592    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24593    cx.assert_editor_state(indoc! {"
24594        <span>
24595        ˇ
24596        </span>
24597    "});
24598}
24599
24600#[gpui::test(iterations = 10)]
24601async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24602    init_test(cx, |_| {});
24603
24604    let fs = FakeFs::new(cx.executor());
24605    fs.insert_tree(
24606        path!("/dir"),
24607        json!({
24608            "a.ts": "a",
24609        }),
24610    )
24611    .await;
24612
24613    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24614    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24615    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24616
24617    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24618    language_registry.add(Arc::new(Language::new(
24619        LanguageConfig {
24620            name: "TypeScript".into(),
24621            matcher: LanguageMatcher {
24622                path_suffixes: vec!["ts".to_string()],
24623                ..Default::default()
24624            },
24625            ..Default::default()
24626        },
24627        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24628    )));
24629    let mut fake_language_servers = language_registry.register_fake_lsp(
24630        "TypeScript",
24631        FakeLspAdapter {
24632            capabilities: lsp::ServerCapabilities {
24633                code_lens_provider: Some(lsp::CodeLensOptions {
24634                    resolve_provider: Some(true),
24635                }),
24636                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24637                    commands: vec!["_the/command".to_string()],
24638                    ..lsp::ExecuteCommandOptions::default()
24639                }),
24640                ..lsp::ServerCapabilities::default()
24641            },
24642            ..FakeLspAdapter::default()
24643        },
24644    );
24645
24646    let editor = workspace
24647        .update(cx, |workspace, window, cx| {
24648            workspace.open_abs_path(
24649                PathBuf::from(path!("/dir/a.ts")),
24650                OpenOptions::default(),
24651                window,
24652                cx,
24653            )
24654        })
24655        .unwrap()
24656        .await
24657        .unwrap()
24658        .downcast::<Editor>()
24659        .unwrap();
24660    cx.executor().run_until_parked();
24661
24662    let fake_server = fake_language_servers.next().await.unwrap();
24663
24664    let buffer = editor.update(cx, |editor, cx| {
24665        editor
24666            .buffer()
24667            .read(cx)
24668            .as_singleton()
24669            .expect("have opened a single file by path")
24670    });
24671
24672    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24673    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24674    drop(buffer_snapshot);
24675    let actions = cx
24676        .update_window(*workspace, |_, window, cx| {
24677            project.code_actions(&buffer, anchor..anchor, window, cx)
24678        })
24679        .unwrap();
24680
24681    fake_server
24682        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24683            Ok(Some(vec![
24684                lsp::CodeLens {
24685                    range: lsp::Range::default(),
24686                    command: Some(lsp::Command {
24687                        title: "Code lens command".to_owned(),
24688                        command: "_the/command".to_owned(),
24689                        arguments: None,
24690                    }),
24691                    data: None,
24692                },
24693                lsp::CodeLens {
24694                    range: lsp::Range::default(),
24695                    command: Some(lsp::Command {
24696                        title: "Command not in capabilities".to_owned(),
24697                        command: "not in capabilities".to_owned(),
24698                        arguments: None,
24699                    }),
24700                    data: None,
24701                },
24702                lsp::CodeLens {
24703                    range: lsp::Range {
24704                        start: lsp::Position {
24705                            line: 1,
24706                            character: 1,
24707                        },
24708                        end: lsp::Position {
24709                            line: 1,
24710                            character: 1,
24711                        },
24712                    },
24713                    command: Some(lsp::Command {
24714                        title: "Command not in range".to_owned(),
24715                        command: "_the/command".to_owned(),
24716                        arguments: None,
24717                    }),
24718                    data: None,
24719                },
24720            ]))
24721        })
24722        .next()
24723        .await;
24724
24725    let actions = actions.await.unwrap();
24726    assert_eq!(
24727        actions.len(),
24728        1,
24729        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24730    );
24731    let action = actions[0].clone();
24732    let apply = project.update(cx, |project, cx| {
24733        project.apply_code_action(buffer.clone(), action, true, cx)
24734    });
24735
24736    // Resolving the code action does not populate its edits. In absence of
24737    // edits, we must execute the given command.
24738    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24739        |mut lens, _| async move {
24740            let lens_command = lens.command.as_mut().expect("should have a command");
24741            assert_eq!(lens_command.title, "Code lens command");
24742            lens_command.arguments = Some(vec![json!("the-argument")]);
24743            Ok(lens)
24744        },
24745    );
24746
24747    // While executing the command, the language server sends the editor
24748    // a `workspaceEdit` request.
24749    fake_server
24750        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24751            let fake = fake_server.clone();
24752            move |params, _| {
24753                assert_eq!(params.command, "_the/command");
24754                let fake = fake.clone();
24755                async move {
24756                    fake.server
24757                        .request::<lsp::request::ApplyWorkspaceEdit>(
24758                            lsp::ApplyWorkspaceEditParams {
24759                                label: None,
24760                                edit: lsp::WorkspaceEdit {
24761                                    changes: Some(
24762                                        [(
24763                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24764                                            vec![lsp::TextEdit {
24765                                                range: lsp::Range::new(
24766                                                    lsp::Position::new(0, 0),
24767                                                    lsp::Position::new(0, 0),
24768                                                ),
24769                                                new_text: "X".into(),
24770                                            }],
24771                                        )]
24772                                        .into_iter()
24773                                        .collect(),
24774                                    ),
24775                                    ..lsp::WorkspaceEdit::default()
24776                                },
24777                            },
24778                        )
24779                        .await
24780                        .into_response()
24781                        .unwrap();
24782                    Ok(Some(json!(null)))
24783                }
24784            }
24785        })
24786        .next()
24787        .await;
24788
24789    // Applying the code lens command returns a project transaction containing the edits
24790    // sent by the language server in its `workspaceEdit` request.
24791    let transaction = apply.await.unwrap();
24792    assert!(transaction.0.contains_key(&buffer));
24793    buffer.update(cx, |buffer, cx| {
24794        assert_eq!(buffer.text(), "Xa");
24795        buffer.undo(cx);
24796        assert_eq!(buffer.text(), "a");
24797    });
24798
24799    let actions_after_edits = cx
24800        .update_window(*workspace, |_, window, cx| {
24801            project.code_actions(&buffer, anchor..anchor, window, cx)
24802        })
24803        .unwrap()
24804        .await
24805        .unwrap();
24806    assert_eq!(
24807        actions, actions_after_edits,
24808        "For the same selection, same code lens actions should be returned"
24809    );
24810
24811    let _responses =
24812        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24813            panic!("No more code lens requests are expected");
24814        });
24815    editor.update_in(cx, |editor, window, cx| {
24816        editor.select_all(&SelectAll, window, cx);
24817    });
24818    cx.executor().run_until_parked();
24819    let new_actions = cx
24820        .update_window(*workspace, |_, window, cx| {
24821            project.code_actions(&buffer, anchor..anchor, window, cx)
24822        })
24823        .unwrap()
24824        .await
24825        .unwrap();
24826    assert_eq!(
24827        actions, new_actions,
24828        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24829    );
24830}
24831
24832#[gpui::test]
24833async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24834    init_test(cx, |_| {});
24835
24836    let fs = FakeFs::new(cx.executor());
24837    let main_text = r#"fn main() {
24838println!("1");
24839println!("2");
24840println!("3");
24841println!("4");
24842println!("5");
24843}"#;
24844    let lib_text = "mod foo {}";
24845    fs.insert_tree(
24846        path!("/a"),
24847        json!({
24848            "lib.rs": lib_text,
24849            "main.rs": main_text,
24850        }),
24851    )
24852    .await;
24853
24854    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24855    let (workspace, cx) =
24856        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24857    let worktree_id = workspace.update(cx, |workspace, cx| {
24858        workspace.project().update(cx, |project, cx| {
24859            project.worktrees(cx).next().unwrap().read(cx).id()
24860        })
24861    });
24862
24863    let expected_ranges = vec![
24864        Point::new(0, 0)..Point::new(0, 0),
24865        Point::new(1, 0)..Point::new(1, 1),
24866        Point::new(2, 0)..Point::new(2, 2),
24867        Point::new(3, 0)..Point::new(3, 3),
24868    ];
24869
24870    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24871    let editor_1 = workspace
24872        .update_in(cx, |workspace, window, cx| {
24873            workspace.open_path(
24874                (worktree_id, rel_path("main.rs")),
24875                Some(pane_1.downgrade()),
24876                true,
24877                window,
24878                cx,
24879            )
24880        })
24881        .unwrap()
24882        .await
24883        .downcast::<Editor>()
24884        .unwrap();
24885    pane_1.update(cx, |pane, cx| {
24886        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24887        open_editor.update(cx, |editor, cx| {
24888            assert_eq!(
24889                editor.display_text(cx),
24890                main_text,
24891                "Original main.rs text on initial open",
24892            );
24893            assert_eq!(
24894                editor
24895                    .selections
24896                    .all::<Point>(&editor.display_snapshot(cx))
24897                    .into_iter()
24898                    .map(|s| s.range())
24899                    .collect::<Vec<_>>(),
24900                vec![Point::zero()..Point::zero()],
24901                "Default selections on initial open",
24902            );
24903        })
24904    });
24905    editor_1.update_in(cx, |editor, window, cx| {
24906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24907            s.select_ranges(expected_ranges.clone());
24908        });
24909    });
24910
24911    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24912        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24913    });
24914    let editor_2 = workspace
24915        .update_in(cx, |workspace, window, cx| {
24916            workspace.open_path(
24917                (worktree_id, rel_path("main.rs")),
24918                Some(pane_2.downgrade()),
24919                true,
24920                window,
24921                cx,
24922            )
24923        })
24924        .unwrap()
24925        .await
24926        .downcast::<Editor>()
24927        .unwrap();
24928    pane_2.update(cx, |pane, cx| {
24929        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24930        open_editor.update(cx, |editor, cx| {
24931            assert_eq!(
24932                editor.display_text(cx),
24933                main_text,
24934                "Original main.rs text on initial open in another panel",
24935            );
24936            assert_eq!(
24937                editor
24938                    .selections
24939                    .all::<Point>(&editor.display_snapshot(cx))
24940                    .into_iter()
24941                    .map(|s| s.range())
24942                    .collect::<Vec<_>>(),
24943                vec![Point::zero()..Point::zero()],
24944                "Default selections on initial open in another panel",
24945            );
24946        })
24947    });
24948
24949    editor_2.update_in(cx, |editor, window, cx| {
24950        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24951    });
24952
24953    let _other_editor_1 = workspace
24954        .update_in(cx, |workspace, window, cx| {
24955            workspace.open_path(
24956                (worktree_id, rel_path("lib.rs")),
24957                Some(pane_1.downgrade()),
24958                true,
24959                window,
24960                cx,
24961            )
24962        })
24963        .unwrap()
24964        .await
24965        .downcast::<Editor>()
24966        .unwrap();
24967    pane_1
24968        .update_in(cx, |pane, window, cx| {
24969            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24970        })
24971        .await
24972        .unwrap();
24973    drop(editor_1);
24974    pane_1.update(cx, |pane, cx| {
24975        pane.active_item()
24976            .unwrap()
24977            .downcast::<Editor>()
24978            .unwrap()
24979            .update(cx, |editor, cx| {
24980                assert_eq!(
24981                    editor.display_text(cx),
24982                    lib_text,
24983                    "Other file should be open and active",
24984                );
24985            });
24986        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24987    });
24988
24989    let _other_editor_2 = workspace
24990        .update_in(cx, |workspace, window, cx| {
24991            workspace.open_path(
24992                (worktree_id, rel_path("lib.rs")),
24993                Some(pane_2.downgrade()),
24994                true,
24995                window,
24996                cx,
24997            )
24998        })
24999        .unwrap()
25000        .await
25001        .downcast::<Editor>()
25002        .unwrap();
25003    pane_2
25004        .update_in(cx, |pane, window, cx| {
25005            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25006        })
25007        .await
25008        .unwrap();
25009    drop(editor_2);
25010    pane_2.update(cx, |pane, cx| {
25011        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25012        open_editor.update(cx, |editor, cx| {
25013            assert_eq!(
25014                editor.display_text(cx),
25015                lib_text,
25016                "Other file should be open and active in another panel too",
25017            );
25018        });
25019        assert_eq!(
25020            pane.items().count(),
25021            1,
25022            "No other editors should be open in another pane",
25023        );
25024    });
25025
25026    let _editor_1_reopened = workspace
25027        .update_in(cx, |workspace, window, cx| {
25028            workspace.open_path(
25029                (worktree_id, rel_path("main.rs")),
25030                Some(pane_1.downgrade()),
25031                true,
25032                window,
25033                cx,
25034            )
25035        })
25036        .unwrap()
25037        .await
25038        .downcast::<Editor>()
25039        .unwrap();
25040    let _editor_2_reopened = workspace
25041        .update_in(cx, |workspace, window, cx| {
25042            workspace.open_path(
25043                (worktree_id, rel_path("main.rs")),
25044                Some(pane_2.downgrade()),
25045                true,
25046                window,
25047                cx,
25048            )
25049        })
25050        .unwrap()
25051        .await
25052        .downcast::<Editor>()
25053        .unwrap();
25054    pane_1.update(cx, |pane, cx| {
25055        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25056        open_editor.update(cx, |editor, cx| {
25057            assert_eq!(
25058                editor.display_text(cx),
25059                main_text,
25060                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25061            );
25062            assert_eq!(
25063                editor
25064                    .selections
25065                    .all::<Point>(&editor.display_snapshot(cx))
25066                    .into_iter()
25067                    .map(|s| s.range())
25068                    .collect::<Vec<_>>(),
25069                expected_ranges,
25070                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25071            );
25072        })
25073    });
25074    pane_2.update(cx, |pane, cx| {
25075        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25076        open_editor.update(cx, |editor, cx| {
25077            assert_eq!(
25078                editor.display_text(cx),
25079                r#"fn main() {
25080⋯rintln!("1");
25081⋯intln!("2");
25082⋯ntln!("3");
25083println!("4");
25084println!("5");
25085}"#,
25086                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25087            );
25088            assert_eq!(
25089                editor
25090                    .selections
25091                    .all::<Point>(&editor.display_snapshot(cx))
25092                    .into_iter()
25093                    .map(|s| s.range())
25094                    .collect::<Vec<_>>(),
25095                vec![Point::zero()..Point::zero()],
25096                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25097            );
25098        })
25099    });
25100}
25101
25102#[gpui::test]
25103async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25104    init_test(cx, |_| {});
25105
25106    let fs = FakeFs::new(cx.executor());
25107    let main_text = r#"fn main() {
25108println!("1");
25109println!("2");
25110println!("3");
25111println!("4");
25112println!("5");
25113}"#;
25114    let lib_text = "mod foo {}";
25115    fs.insert_tree(
25116        path!("/a"),
25117        json!({
25118            "lib.rs": lib_text,
25119            "main.rs": main_text,
25120        }),
25121    )
25122    .await;
25123
25124    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25125    let (workspace, cx) =
25126        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25127    let worktree_id = workspace.update(cx, |workspace, cx| {
25128        workspace.project().update(cx, |project, cx| {
25129            project.worktrees(cx).next().unwrap().read(cx).id()
25130        })
25131    });
25132
25133    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25134    let editor = workspace
25135        .update_in(cx, |workspace, window, cx| {
25136            workspace.open_path(
25137                (worktree_id, rel_path("main.rs")),
25138                Some(pane.downgrade()),
25139                true,
25140                window,
25141                cx,
25142            )
25143        })
25144        .unwrap()
25145        .await
25146        .downcast::<Editor>()
25147        .unwrap();
25148    pane.update(cx, |pane, cx| {
25149        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25150        open_editor.update(cx, |editor, cx| {
25151            assert_eq!(
25152                editor.display_text(cx),
25153                main_text,
25154                "Original main.rs text on initial open",
25155            );
25156        })
25157    });
25158    editor.update_in(cx, |editor, window, cx| {
25159        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25160    });
25161
25162    cx.update_global(|store: &mut SettingsStore, cx| {
25163        store.update_user_settings(cx, |s| {
25164            s.workspace.restore_on_file_reopen = Some(false);
25165        });
25166    });
25167    editor.update_in(cx, |editor, window, cx| {
25168        editor.fold_ranges(
25169            vec![
25170                Point::new(1, 0)..Point::new(1, 1),
25171                Point::new(2, 0)..Point::new(2, 2),
25172                Point::new(3, 0)..Point::new(3, 3),
25173            ],
25174            false,
25175            window,
25176            cx,
25177        );
25178    });
25179    pane.update_in(cx, |pane, window, cx| {
25180        pane.close_all_items(&CloseAllItems::default(), window, cx)
25181    })
25182    .await
25183    .unwrap();
25184    pane.update(cx, |pane, _| {
25185        assert!(pane.active_item().is_none());
25186    });
25187    cx.update_global(|store: &mut SettingsStore, cx| {
25188        store.update_user_settings(cx, |s| {
25189            s.workspace.restore_on_file_reopen = Some(true);
25190        });
25191    });
25192
25193    let _editor_reopened = workspace
25194        .update_in(cx, |workspace, window, cx| {
25195            workspace.open_path(
25196                (worktree_id, rel_path("main.rs")),
25197                Some(pane.downgrade()),
25198                true,
25199                window,
25200                cx,
25201            )
25202        })
25203        .unwrap()
25204        .await
25205        .downcast::<Editor>()
25206        .unwrap();
25207    pane.update(cx, |pane, cx| {
25208        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25209        open_editor.update(cx, |editor, cx| {
25210            assert_eq!(
25211                editor.display_text(cx),
25212                main_text,
25213                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25214            );
25215        })
25216    });
25217}
25218
25219#[gpui::test]
25220async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25221    struct EmptyModalView {
25222        focus_handle: gpui::FocusHandle,
25223    }
25224    impl EventEmitter<DismissEvent> for EmptyModalView {}
25225    impl Render for EmptyModalView {
25226        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25227            div()
25228        }
25229    }
25230    impl Focusable for EmptyModalView {
25231        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25232            self.focus_handle.clone()
25233        }
25234    }
25235    impl workspace::ModalView for EmptyModalView {}
25236    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25237        EmptyModalView {
25238            focus_handle: cx.focus_handle(),
25239        }
25240    }
25241
25242    init_test(cx, |_| {});
25243
25244    let fs = FakeFs::new(cx.executor());
25245    let project = Project::test(fs, [], cx).await;
25246    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25247    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25248    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25249    let editor = cx.new_window_entity(|window, cx| {
25250        Editor::new(
25251            EditorMode::full(),
25252            buffer,
25253            Some(project.clone()),
25254            window,
25255            cx,
25256        )
25257    });
25258    workspace
25259        .update(cx, |workspace, window, cx| {
25260            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25261        })
25262        .unwrap();
25263    editor.update_in(cx, |editor, window, cx| {
25264        editor.open_context_menu(&OpenContextMenu, window, cx);
25265        assert!(editor.mouse_context_menu.is_some());
25266    });
25267    workspace
25268        .update(cx, |workspace, window, cx| {
25269            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25270        })
25271        .unwrap();
25272    cx.read(|cx| {
25273        assert!(editor.read(cx).mouse_context_menu.is_none());
25274    });
25275}
25276
25277fn set_linked_edit_ranges(
25278    opening: (Point, Point),
25279    closing: (Point, Point),
25280    editor: &mut Editor,
25281    cx: &mut Context<Editor>,
25282) {
25283    let Some((buffer, _)) = editor
25284        .buffer
25285        .read(cx)
25286        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25287    else {
25288        panic!("Failed to get buffer for selection position");
25289    };
25290    let buffer = buffer.read(cx);
25291    let buffer_id = buffer.remote_id();
25292    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25293    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25294    let mut linked_ranges = HashMap::default();
25295    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25296    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25297}
25298
25299#[gpui::test]
25300async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25301    init_test(cx, |_| {});
25302
25303    let fs = FakeFs::new(cx.executor());
25304    fs.insert_file(path!("/file.html"), Default::default())
25305        .await;
25306
25307    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25308
25309    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25310    let html_language = Arc::new(Language::new(
25311        LanguageConfig {
25312            name: "HTML".into(),
25313            matcher: LanguageMatcher {
25314                path_suffixes: vec!["html".to_string()],
25315                ..LanguageMatcher::default()
25316            },
25317            brackets: BracketPairConfig {
25318                pairs: vec![BracketPair {
25319                    start: "<".into(),
25320                    end: ">".into(),
25321                    close: true,
25322                    ..Default::default()
25323                }],
25324                ..Default::default()
25325            },
25326            ..Default::default()
25327        },
25328        Some(tree_sitter_html::LANGUAGE.into()),
25329    ));
25330    language_registry.add(html_language);
25331    let mut fake_servers = language_registry.register_fake_lsp(
25332        "HTML",
25333        FakeLspAdapter {
25334            capabilities: lsp::ServerCapabilities {
25335                completion_provider: Some(lsp::CompletionOptions {
25336                    resolve_provider: Some(true),
25337                    ..Default::default()
25338                }),
25339                ..Default::default()
25340            },
25341            ..Default::default()
25342        },
25343    );
25344
25345    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25346    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25347
25348    let worktree_id = workspace
25349        .update(cx, |workspace, _window, cx| {
25350            workspace.project().update(cx, |project, cx| {
25351                project.worktrees(cx).next().unwrap().read(cx).id()
25352            })
25353        })
25354        .unwrap();
25355    project
25356        .update(cx, |project, cx| {
25357            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25358        })
25359        .await
25360        .unwrap();
25361    let editor = workspace
25362        .update(cx, |workspace, window, cx| {
25363            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25364        })
25365        .unwrap()
25366        .await
25367        .unwrap()
25368        .downcast::<Editor>()
25369        .unwrap();
25370
25371    let fake_server = fake_servers.next().await.unwrap();
25372    editor.update_in(cx, |editor, window, cx| {
25373        editor.set_text("<ad></ad>", window, cx);
25374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25375            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25376        });
25377        set_linked_edit_ranges(
25378            (Point::new(0, 1), Point::new(0, 3)),
25379            (Point::new(0, 6), Point::new(0, 8)),
25380            editor,
25381            cx,
25382        );
25383    });
25384    let mut completion_handle =
25385        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25386            Ok(Some(lsp::CompletionResponse::Array(vec![
25387                lsp::CompletionItem {
25388                    label: "head".to_string(),
25389                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25390                        lsp::InsertReplaceEdit {
25391                            new_text: "head".to_string(),
25392                            insert: lsp::Range::new(
25393                                lsp::Position::new(0, 1),
25394                                lsp::Position::new(0, 3),
25395                            ),
25396                            replace: lsp::Range::new(
25397                                lsp::Position::new(0, 1),
25398                                lsp::Position::new(0, 3),
25399                            ),
25400                        },
25401                    )),
25402                    ..Default::default()
25403                },
25404            ])))
25405        });
25406    editor.update_in(cx, |editor, window, cx| {
25407        editor.show_completions(&ShowCompletions, window, cx);
25408    });
25409    cx.run_until_parked();
25410    completion_handle.next().await.unwrap();
25411    editor.update(cx, |editor, _| {
25412        assert!(
25413            editor.context_menu_visible(),
25414            "Completion menu should be visible"
25415        );
25416    });
25417    editor.update_in(cx, |editor, window, cx| {
25418        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25419    });
25420    cx.executor().run_until_parked();
25421    editor.update(cx, |editor, cx| {
25422        assert_eq!(editor.text(cx), "<head></head>");
25423    });
25424}
25425
25426#[gpui::test]
25427async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25428    init_test(cx, |_| {});
25429
25430    let mut cx = EditorTestContext::new(cx).await;
25431    let language = Arc::new(Language::new(
25432        LanguageConfig {
25433            name: "TSX".into(),
25434            matcher: LanguageMatcher {
25435                path_suffixes: vec!["tsx".to_string()],
25436                ..LanguageMatcher::default()
25437            },
25438            brackets: BracketPairConfig {
25439                pairs: vec![BracketPair {
25440                    start: "<".into(),
25441                    end: ">".into(),
25442                    close: true,
25443                    ..Default::default()
25444                }],
25445                ..Default::default()
25446            },
25447            linked_edit_characters: HashSet::from_iter(['.']),
25448            ..Default::default()
25449        },
25450        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25451    ));
25452    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25453
25454    // Test typing > does not extend linked pair
25455    cx.set_state("<divˇ<div></div>");
25456    cx.update_editor(|editor, _, cx| {
25457        set_linked_edit_ranges(
25458            (Point::new(0, 1), Point::new(0, 4)),
25459            (Point::new(0, 11), Point::new(0, 14)),
25460            editor,
25461            cx,
25462        );
25463    });
25464    cx.update_editor(|editor, window, cx| {
25465        editor.handle_input(">", window, cx);
25466    });
25467    cx.assert_editor_state("<div>ˇ<div></div>");
25468
25469    // Test typing . do extend linked pair
25470    cx.set_state("<Animatedˇ></Animated>");
25471    cx.update_editor(|editor, _, cx| {
25472        set_linked_edit_ranges(
25473            (Point::new(0, 1), Point::new(0, 9)),
25474            (Point::new(0, 12), Point::new(0, 20)),
25475            editor,
25476            cx,
25477        );
25478    });
25479    cx.update_editor(|editor, window, cx| {
25480        editor.handle_input(".", window, cx);
25481    });
25482    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25483    cx.update_editor(|editor, _, cx| {
25484        set_linked_edit_ranges(
25485            (Point::new(0, 1), Point::new(0, 10)),
25486            (Point::new(0, 13), Point::new(0, 21)),
25487            editor,
25488            cx,
25489        );
25490    });
25491    cx.update_editor(|editor, window, cx| {
25492        editor.handle_input("V", window, cx);
25493    });
25494    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25495}
25496
25497#[gpui::test]
25498async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25499    init_test(cx, |_| {});
25500
25501    let fs = FakeFs::new(cx.executor());
25502    fs.insert_tree(
25503        path!("/root"),
25504        json!({
25505            "a": {
25506                "main.rs": "fn main() {}",
25507            },
25508            "foo": {
25509                "bar": {
25510                    "external_file.rs": "pub mod external {}",
25511                }
25512            }
25513        }),
25514    )
25515    .await;
25516
25517    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25518    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25519    language_registry.add(rust_lang());
25520    let _fake_servers = language_registry.register_fake_lsp(
25521        "Rust",
25522        FakeLspAdapter {
25523            ..FakeLspAdapter::default()
25524        },
25525    );
25526    let (workspace, cx) =
25527        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25528    let worktree_id = workspace.update(cx, |workspace, cx| {
25529        workspace.project().update(cx, |project, cx| {
25530            project.worktrees(cx).next().unwrap().read(cx).id()
25531        })
25532    });
25533
25534    let assert_language_servers_count =
25535        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25536            project.update(cx, |project, cx| {
25537                let current = project
25538                    .lsp_store()
25539                    .read(cx)
25540                    .as_local()
25541                    .unwrap()
25542                    .language_servers
25543                    .len();
25544                assert_eq!(expected, current, "{context}");
25545            });
25546        };
25547
25548    assert_language_servers_count(
25549        0,
25550        "No servers should be running before any file is open",
25551        cx,
25552    );
25553    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25554    let main_editor = workspace
25555        .update_in(cx, |workspace, window, cx| {
25556            workspace.open_path(
25557                (worktree_id, rel_path("main.rs")),
25558                Some(pane.downgrade()),
25559                true,
25560                window,
25561                cx,
25562            )
25563        })
25564        .unwrap()
25565        .await
25566        .downcast::<Editor>()
25567        .unwrap();
25568    pane.update(cx, |pane, cx| {
25569        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25570        open_editor.update(cx, |editor, cx| {
25571            assert_eq!(
25572                editor.display_text(cx),
25573                "fn main() {}",
25574                "Original main.rs text on initial open",
25575            );
25576        });
25577        assert_eq!(open_editor, main_editor);
25578    });
25579    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25580
25581    let external_editor = workspace
25582        .update_in(cx, |workspace, window, cx| {
25583            workspace.open_abs_path(
25584                PathBuf::from("/root/foo/bar/external_file.rs"),
25585                OpenOptions::default(),
25586                window,
25587                cx,
25588            )
25589        })
25590        .await
25591        .expect("opening external file")
25592        .downcast::<Editor>()
25593        .expect("downcasted external file's open element to editor");
25594    pane.update(cx, |pane, cx| {
25595        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25596        open_editor.update(cx, |editor, cx| {
25597            assert_eq!(
25598                editor.display_text(cx),
25599                "pub mod external {}",
25600                "External file is open now",
25601            );
25602        });
25603        assert_eq!(open_editor, external_editor);
25604    });
25605    assert_language_servers_count(
25606        1,
25607        "Second, external, *.rs file should join the existing server",
25608        cx,
25609    );
25610
25611    pane.update_in(cx, |pane, window, cx| {
25612        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25613    })
25614    .await
25615    .unwrap();
25616    pane.update_in(cx, |pane, window, cx| {
25617        pane.navigate_backward(&Default::default(), window, cx);
25618    });
25619    cx.run_until_parked();
25620    pane.update(cx, |pane, cx| {
25621        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25622        open_editor.update(cx, |editor, cx| {
25623            assert_eq!(
25624                editor.display_text(cx),
25625                "pub mod external {}",
25626                "External file is open now",
25627            );
25628        });
25629    });
25630    assert_language_servers_count(
25631        1,
25632        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25633        cx,
25634    );
25635
25636    cx.update(|_, cx| {
25637        workspace::reload(cx);
25638    });
25639    assert_language_servers_count(
25640        1,
25641        "After reloading the worktree with local and external files opened, only one project should be started",
25642        cx,
25643    );
25644}
25645
25646#[gpui::test]
25647async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25648    init_test(cx, |_| {});
25649
25650    let mut cx = EditorTestContext::new(cx).await;
25651    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25652    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25653
25654    // test cursor move to start of each line on tab
25655    // for `if`, `elif`, `else`, `while`, `with` and `for`
25656    cx.set_state(indoc! {"
25657        def main():
25658        ˇ    for item in items:
25659        ˇ        while item.active:
25660        ˇ            if item.value > 10:
25661        ˇ                continue
25662        ˇ            elif item.value < 0:
25663        ˇ                break
25664        ˇ            else:
25665        ˇ                with item.context() as ctx:
25666        ˇ                    yield count
25667        ˇ        else:
25668        ˇ            log('while else')
25669        ˇ    else:
25670        ˇ        log('for else')
25671    "});
25672    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25673    cx.wait_for_autoindent_applied().await;
25674    cx.assert_editor_state(indoc! {"
25675        def main():
25676            ˇfor item in items:
25677                ˇwhile item.active:
25678                    ˇif item.value > 10:
25679                        ˇcontinue
25680                    ˇelif item.value < 0:
25681                        ˇbreak
25682                    ˇelse:
25683                        ˇwith item.context() as ctx:
25684                            ˇyield count
25685                ˇelse:
25686                    ˇlog('while else')
25687            ˇelse:
25688                ˇlog('for else')
25689    "});
25690    // test relative indent is preserved when tab
25691    // for `if`, `elif`, `else`, `while`, `with` and `for`
25692    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25693    cx.wait_for_autoindent_applied().await;
25694    cx.assert_editor_state(indoc! {"
25695        def main():
25696                ˇfor item in items:
25697                    ˇwhile item.active:
25698                        ˇif item.value > 10:
25699                            ˇcontinue
25700                        ˇelif item.value < 0:
25701                            ˇbreak
25702                        ˇelse:
25703                            ˇwith item.context() as ctx:
25704                                ˇyield count
25705                    ˇelse:
25706                        ˇlog('while else')
25707                ˇelse:
25708                    ˇlog('for else')
25709    "});
25710
25711    // test cursor move to start of each line on tab
25712    // for `try`, `except`, `else`, `finally`, `match` and `def`
25713    cx.set_state(indoc! {"
25714        def main():
25715        ˇ    try:
25716        ˇ        fetch()
25717        ˇ    except ValueError:
25718        ˇ        handle_error()
25719        ˇ    else:
25720        ˇ        match value:
25721        ˇ            case _:
25722        ˇ    finally:
25723        ˇ        def status():
25724        ˇ            return 0
25725    "});
25726    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25727    cx.wait_for_autoindent_applied().await;
25728    cx.assert_editor_state(indoc! {"
25729        def main():
25730            ˇtry:
25731                ˇfetch()
25732            ˇexcept ValueError:
25733                ˇhandle_error()
25734            ˇelse:
25735                ˇmatch value:
25736                    ˇcase _:
25737            ˇfinally:
25738                ˇdef status():
25739                    ˇreturn 0
25740    "});
25741    // test relative indent is preserved when tab
25742    // for `try`, `except`, `else`, `finally`, `match` and `def`
25743    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25744    cx.wait_for_autoindent_applied().await;
25745    cx.assert_editor_state(indoc! {"
25746        def main():
25747                ˇtry:
25748                    ˇfetch()
25749                ˇexcept ValueError:
25750                    ˇhandle_error()
25751                ˇelse:
25752                    ˇmatch value:
25753                        ˇcase _:
25754                ˇfinally:
25755                    ˇdef status():
25756                        ˇreturn 0
25757    "});
25758}
25759
25760#[gpui::test]
25761async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25762    init_test(cx, |_| {});
25763
25764    let mut cx = EditorTestContext::new(cx).await;
25765    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25767
25768    // test `else` auto outdents when typed inside `if` block
25769    cx.set_state(indoc! {"
25770        def main():
25771            if i == 2:
25772                return
25773                ˇ
25774    "});
25775    cx.update_editor(|editor, window, cx| {
25776        editor.handle_input("else:", window, cx);
25777    });
25778    cx.wait_for_autoindent_applied().await;
25779    cx.assert_editor_state(indoc! {"
25780        def main():
25781            if i == 2:
25782                return
25783            else:ˇ
25784    "});
25785
25786    // test `except` auto outdents when typed inside `try` block
25787    cx.set_state(indoc! {"
25788        def main():
25789            try:
25790                i = 2
25791                ˇ
25792    "});
25793    cx.update_editor(|editor, window, cx| {
25794        editor.handle_input("except:", window, cx);
25795    });
25796    cx.wait_for_autoindent_applied().await;
25797    cx.assert_editor_state(indoc! {"
25798        def main():
25799            try:
25800                i = 2
25801            except:ˇ
25802    "});
25803
25804    // test `else` auto outdents when typed inside `except` block
25805    cx.set_state(indoc! {"
25806        def main():
25807            try:
25808                i = 2
25809            except:
25810                j = 2
25811                ˇ
25812    "});
25813    cx.update_editor(|editor, window, cx| {
25814        editor.handle_input("else:", window, cx);
25815    });
25816    cx.wait_for_autoindent_applied().await;
25817    cx.assert_editor_state(indoc! {"
25818        def main():
25819            try:
25820                i = 2
25821            except:
25822                j = 2
25823            else:ˇ
25824    "});
25825
25826    // test `finally` auto outdents when typed inside `else` block
25827    cx.set_state(indoc! {"
25828        def main():
25829            try:
25830                i = 2
25831            except:
25832                j = 2
25833            else:
25834                k = 2
25835                ˇ
25836    "});
25837    cx.update_editor(|editor, window, cx| {
25838        editor.handle_input("finally:", window, cx);
25839    });
25840    cx.wait_for_autoindent_applied().await;
25841    cx.assert_editor_state(indoc! {"
25842        def main():
25843            try:
25844                i = 2
25845            except:
25846                j = 2
25847            else:
25848                k = 2
25849            finally:ˇ
25850    "});
25851
25852    // test `else` does not outdents when typed inside `except` block right after for block
25853    cx.set_state(indoc! {"
25854        def main():
25855            try:
25856                i = 2
25857            except:
25858                for i in range(n):
25859                    pass
25860                ˇ
25861    "});
25862    cx.update_editor(|editor, window, cx| {
25863        editor.handle_input("else:", window, cx);
25864    });
25865    cx.wait_for_autoindent_applied().await;
25866    cx.assert_editor_state(indoc! {"
25867        def main():
25868            try:
25869                i = 2
25870            except:
25871                for i in range(n):
25872                    pass
25873                else:ˇ
25874    "});
25875
25876    // test `finally` auto outdents when typed inside `else` block right after for block
25877    cx.set_state(indoc! {"
25878        def main():
25879            try:
25880                i = 2
25881            except:
25882                j = 2
25883            else:
25884                for i in range(n):
25885                    pass
25886                ˇ
25887    "});
25888    cx.update_editor(|editor, window, cx| {
25889        editor.handle_input("finally:", window, cx);
25890    });
25891    cx.wait_for_autoindent_applied().await;
25892    cx.assert_editor_state(indoc! {"
25893        def main():
25894            try:
25895                i = 2
25896            except:
25897                j = 2
25898            else:
25899                for i in range(n):
25900                    pass
25901            finally:ˇ
25902    "});
25903
25904    // test `except` outdents to inner "try" block
25905    cx.set_state(indoc! {"
25906        def main():
25907            try:
25908                i = 2
25909                if i == 2:
25910                    try:
25911                        i = 3
25912                        ˇ
25913    "});
25914    cx.update_editor(|editor, window, cx| {
25915        editor.handle_input("except:", window, cx);
25916    });
25917    cx.wait_for_autoindent_applied().await;
25918    cx.assert_editor_state(indoc! {"
25919        def main():
25920            try:
25921                i = 2
25922                if i == 2:
25923                    try:
25924                        i = 3
25925                    except:ˇ
25926    "});
25927
25928    // test `except` outdents to outer "try" block
25929    cx.set_state(indoc! {"
25930        def main():
25931            try:
25932                i = 2
25933                if i == 2:
25934                    try:
25935                        i = 3
25936                ˇ
25937    "});
25938    cx.update_editor(|editor, window, cx| {
25939        editor.handle_input("except:", window, cx);
25940    });
25941    cx.wait_for_autoindent_applied().await;
25942    cx.assert_editor_state(indoc! {"
25943        def main():
25944            try:
25945                i = 2
25946                if i == 2:
25947                    try:
25948                        i = 3
25949            except:ˇ
25950    "});
25951
25952    // test `else` stays at correct indent when typed after `for` block
25953    cx.set_state(indoc! {"
25954        def main():
25955            for i in range(10):
25956                if i == 3:
25957                    break
25958            ˇ
25959    "});
25960    cx.update_editor(|editor, window, cx| {
25961        editor.handle_input("else:", window, cx);
25962    });
25963    cx.wait_for_autoindent_applied().await;
25964    cx.assert_editor_state(indoc! {"
25965        def main():
25966            for i in range(10):
25967                if i == 3:
25968                    break
25969            else:ˇ
25970    "});
25971
25972    // test does not outdent on typing after line with square brackets
25973    cx.set_state(indoc! {"
25974        def f() -> list[str]:
25975            ˇ
25976    "});
25977    cx.update_editor(|editor, window, cx| {
25978        editor.handle_input("a", window, cx);
25979    });
25980    cx.wait_for_autoindent_applied().await;
25981    cx.assert_editor_state(indoc! {"
25982        def f() -> list[str]:
2598325984    "});
25985
25986    // test does not outdent on typing : after case keyword
25987    cx.set_state(indoc! {"
25988        match 1:
25989            caseˇ
25990    "});
25991    cx.update_editor(|editor, window, cx| {
25992        editor.handle_input(":", window, cx);
25993    });
25994    cx.wait_for_autoindent_applied().await;
25995    cx.assert_editor_state(indoc! {"
25996        match 1:
25997            case:ˇ
25998    "});
25999}
26000
26001#[gpui::test]
26002async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26003    init_test(cx, |_| {});
26004    update_test_language_settings(cx, |settings| {
26005        settings.defaults.extend_comment_on_newline = Some(false);
26006    });
26007    let mut cx = EditorTestContext::new(cx).await;
26008    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26009    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26010
26011    // test correct indent after newline on comment
26012    cx.set_state(indoc! {"
26013        # COMMENT:ˇ
26014    "});
26015    cx.update_editor(|editor, window, cx| {
26016        editor.newline(&Newline, window, cx);
26017    });
26018    cx.wait_for_autoindent_applied().await;
26019    cx.assert_editor_state(indoc! {"
26020        # COMMENT:
26021        ˇ
26022    "});
26023
26024    // test correct indent after newline in brackets
26025    cx.set_state(indoc! {"
26026        {ˇ}
26027    "});
26028    cx.update_editor(|editor, window, cx| {
26029        editor.newline(&Newline, window, cx);
26030    });
26031    cx.wait_for_autoindent_applied().await;
26032    cx.assert_editor_state(indoc! {"
26033        {
26034            ˇ
26035        }
26036    "});
26037
26038    cx.set_state(indoc! {"
26039        (ˇ)
26040    "});
26041    cx.update_editor(|editor, window, cx| {
26042        editor.newline(&Newline, window, cx);
26043    });
26044    cx.run_until_parked();
26045    cx.assert_editor_state(indoc! {"
26046        (
26047            ˇ
26048        )
26049    "});
26050
26051    // do not indent after empty lists or dictionaries
26052    cx.set_state(indoc! {"
26053        a = []ˇ
26054    "});
26055    cx.update_editor(|editor, window, cx| {
26056        editor.newline(&Newline, window, cx);
26057    });
26058    cx.run_until_parked();
26059    cx.assert_editor_state(indoc! {"
26060        a = []
26061        ˇ
26062    "});
26063}
26064
26065#[gpui::test]
26066async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26067    init_test(cx, |_| {});
26068
26069    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26070    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26071    language_registry.add(markdown_lang());
26072    language_registry.add(python_lang);
26073
26074    let mut cx = EditorTestContext::new(cx).await;
26075    cx.update_buffer(|buffer, cx| {
26076        buffer.set_language_registry(language_registry);
26077        buffer.set_language(Some(markdown_lang()), cx);
26078    });
26079
26080    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26081    cx.set_state(indoc! {"
26082        # Heading
26083
26084        ```python
26085        def main():
26086            if condition:
26087                pass
26088                ˇ
26089        ```
26090    "});
26091    cx.update_editor(|editor, window, cx| {
26092        editor.handle_input("else:", window, cx);
26093    });
26094    cx.run_until_parked();
26095    cx.assert_editor_state(indoc! {"
26096        # Heading
26097
26098        ```python
26099        def main():
26100            if condition:
26101                pass
26102            else:ˇ
26103        ```
26104    "});
26105}
26106
26107#[gpui::test]
26108async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26109    init_test(cx, |_| {});
26110
26111    let mut cx = EditorTestContext::new(cx).await;
26112    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26113    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26114
26115    // test cursor move to start of each line on tab
26116    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26117    cx.set_state(indoc! {"
26118        function main() {
26119        ˇ    for item in $items; do
26120        ˇ        while [ -n \"$item\" ]; do
26121        ˇ            if [ \"$value\" -gt 10 ]; then
26122        ˇ                continue
26123        ˇ            elif [ \"$value\" -lt 0 ]; then
26124        ˇ                break
26125        ˇ            else
26126        ˇ                echo \"$item\"
26127        ˇ            fi
26128        ˇ        done
26129        ˇ    done
26130        ˇ}
26131    "});
26132    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26133    cx.wait_for_autoindent_applied().await;
26134    cx.assert_editor_state(indoc! {"
26135        function main() {
26136            ˇfor item in $items; do
26137                ˇwhile [ -n \"$item\" ]; do
26138                    ˇif [ \"$value\" -gt 10 ]; then
26139                        ˇcontinue
26140                    ˇelif [ \"$value\" -lt 0 ]; then
26141                        ˇbreak
26142                    ˇelse
26143                        ˇecho \"$item\"
26144                    ˇfi
26145                ˇdone
26146            ˇdone
26147        ˇ}
26148    "});
26149    // test relative indent is preserved when tab
26150    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26151    cx.wait_for_autoindent_applied().await;
26152    cx.assert_editor_state(indoc! {"
26153        function main() {
26154                ˇfor item in $items; do
26155                    ˇwhile [ -n \"$item\" ]; do
26156                        ˇif [ \"$value\" -gt 10 ]; then
26157                            ˇcontinue
26158                        ˇelif [ \"$value\" -lt 0 ]; then
26159                            ˇbreak
26160                        ˇelse
26161                            ˇecho \"$item\"
26162                        ˇfi
26163                    ˇdone
26164                ˇdone
26165            ˇ}
26166    "});
26167
26168    // test cursor move to start of each line on tab
26169    // for `case` statement with patterns
26170    cx.set_state(indoc! {"
26171        function handle() {
26172        ˇ    case \"$1\" in
26173        ˇ        start)
26174        ˇ            echo \"a\"
26175        ˇ            ;;
26176        ˇ        stop)
26177        ˇ            echo \"b\"
26178        ˇ            ;;
26179        ˇ        *)
26180        ˇ            echo \"c\"
26181        ˇ            ;;
26182        ˇ    esac
26183        ˇ}
26184    "});
26185    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26186    cx.wait_for_autoindent_applied().await;
26187    cx.assert_editor_state(indoc! {"
26188        function handle() {
26189            ˇcase \"$1\" in
26190                ˇstart)
26191                    ˇecho \"a\"
26192                    ˇ;;
26193                ˇstop)
26194                    ˇecho \"b\"
26195                    ˇ;;
26196                ˇ*)
26197                    ˇecho \"c\"
26198                    ˇ;;
26199            ˇesac
26200        ˇ}
26201    "});
26202}
26203
26204#[gpui::test]
26205async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26206    init_test(cx, |_| {});
26207
26208    let mut cx = EditorTestContext::new(cx).await;
26209    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26210    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26211
26212    // test indents on comment insert
26213    cx.set_state(indoc! {"
26214        function main() {
26215        ˇ    for item in $items; do
26216        ˇ        while [ -n \"$item\" ]; do
26217        ˇ            if [ \"$value\" -gt 10 ]; then
26218        ˇ                continue
26219        ˇ            elif [ \"$value\" -lt 0 ]; then
26220        ˇ                break
26221        ˇ            else
26222        ˇ                echo \"$item\"
26223        ˇ            fi
26224        ˇ        done
26225        ˇ    done
26226        ˇ}
26227    "});
26228    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26229    cx.wait_for_autoindent_applied().await;
26230    cx.assert_editor_state(indoc! {"
26231        function main() {
26232        #ˇ    for item in $items; do
26233        #ˇ        while [ -n \"$item\" ]; do
26234        #ˇ            if [ \"$value\" -gt 10 ]; then
26235        #ˇ                continue
26236        #ˇ            elif [ \"$value\" -lt 0 ]; then
26237        #ˇ                break
26238        #ˇ            else
26239        #ˇ                echo \"$item\"
26240        #ˇ            fi
26241        #ˇ        done
26242        #ˇ    done
26243        #ˇ}
26244    "});
26245}
26246
26247#[gpui::test]
26248async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26249    init_test(cx, |_| {});
26250
26251    let mut cx = EditorTestContext::new(cx).await;
26252    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26253    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26254
26255    // test `else` auto outdents when typed inside `if` block
26256    cx.set_state(indoc! {"
26257        if [ \"$1\" = \"test\" ]; then
26258            echo \"foo bar\"
26259            ˇ
26260    "});
26261    cx.update_editor(|editor, window, cx| {
26262        editor.handle_input("else", window, cx);
26263    });
26264    cx.wait_for_autoindent_applied().await;
26265    cx.assert_editor_state(indoc! {"
26266        if [ \"$1\" = \"test\" ]; then
26267            echo \"foo bar\"
26268        elseˇ
26269    "});
26270
26271    // test `elif` auto outdents when typed inside `if` block
26272    cx.set_state(indoc! {"
26273        if [ \"$1\" = \"test\" ]; then
26274            echo \"foo bar\"
26275            ˇ
26276    "});
26277    cx.update_editor(|editor, window, cx| {
26278        editor.handle_input("elif", window, cx);
26279    });
26280    cx.wait_for_autoindent_applied().await;
26281    cx.assert_editor_state(indoc! {"
26282        if [ \"$1\" = \"test\" ]; then
26283            echo \"foo bar\"
26284        elifˇ
26285    "});
26286
26287    // test `fi` auto outdents when typed inside `else` block
26288    cx.set_state(indoc! {"
26289        if [ \"$1\" = \"test\" ]; then
26290            echo \"foo bar\"
26291        else
26292            echo \"bar baz\"
26293            ˇ
26294    "});
26295    cx.update_editor(|editor, window, cx| {
26296        editor.handle_input("fi", window, cx);
26297    });
26298    cx.wait_for_autoindent_applied().await;
26299    cx.assert_editor_state(indoc! {"
26300        if [ \"$1\" = \"test\" ]; then
26301            echo \"foo bar\"
26302        else
26303            echo \"bar baz\"
26304        fiˇ
26305    "});
26306
26307    // test `done` auto outdents when typed inside `while` block
26308    cx.set_state(indoc! {"
26309        while read line; do
26310            echo \"$line\"
26311            ˇ
26312    "});
26313    cx.update_editor(|editor, window, cx| {
26314        editor.handle_input("done", window, cx);
26315    });
26316    cx.wait_for_autoindent_applied().await;
26317    cx.assert_editor_state(indoc! {"
26318        while read line; do
26319            echo \"$line\"
26320        doneˇ
26321    "});
26322
26323    // test `done` auto outdents when typed inside `for` block
26324    cx.set_state(indoc! {"
26325        for file in *.txt; do
26326            cat \"$file\"
26327            ˇ
26328    "});
26329    cx.update_editor(|editor, window, cx| {
26330        editor.handle_input("done", window, cx);
26331    });
26332    cx.wait_for_autoindent_applied().await;
26333    cx.assert_editor_state(indoc! {"
26334        for file in *.txt; do
26335            cat \"$file\"
26336        doneˇ
26337    "});
26338
26339    // test `esac` auto outdents when typed inside `case` block
26340    cx.set_state(indoc! {"
26341        case \"$1\" in
26342            start)
26343                echo \"foo bar\"
26344                ;;
26345            stop)
26346                echo \"bar baz\"
26347                ;;
26348            ˇ
26349    "});
26350    cx.update_editor(|editor, window, cx| {
26351        editor.handle_input("esac", window, cx);
26352    });
26353    cx.wait_for_autoindent_applied().await;
26354    cx.assert_editor_state(indoc! {"
26355        case \"$1\" in
26356            start)
26357                echo \"foo bar\"
26358                ;;
26359            stop)
26360                echo \"bar baz\"
26361                ;;
26362        esacˇ
26363    "});
26364
26365    // test `*)` auto outdents when typed inside `case` block
26366    cx.set_state(indoc! {"
26367        case \"$1\" in
26368            start)
26369                echo \"foo bar\"
26370                ;;
26371                ˇ
26372    "});
26373    cx.update_editor(|editor, window, cx| {
26374        editor.handle_input("*)", window, cx);
26375    });
26376    cx.wait_for_autoindent_applied().await;
26377    cx.assert_editor_state(indoc! {"
26378        case \"$1\" in
26379            start)
26380                echo \"foo bar\"
26381                ;;
26382            *)ˇ
26383    "});
26384
26385    // test `fi` outdents to correct level with nested if blocks
26386    cx.set_state(indoc! {"
26387        if [ \"$1\" = \"test\" ]; then
26388            echo \"outer if\"
26389            if [ \"$2\" = \"debug\" ]; then
26390                echo \"inner if\"
26391                ˇ
26392    "});
26393    cx.update_editor(|editor, window, cx| {
26394        editor.handle_input("fi", window, cx);
26395    });
26396    cx.wait_for_autoindent_applied().await;
26397    cx.assert_editor_state(indoc! {"
26398        if [ \"$1\" = \"test\" ]; then
26399            echo \"outer if\"
26400            if [ \"$2\" = \"debug\" ]; then
26401                echo \"inner if\"
26402            fiˇ
26403    "});
26404}
26405
26406#[gpui::test]
26407async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26408    init_test(cx, |_| {});
26409    update_test_language_settings(cx, |settings| {
26410        settings.defaults.extend_comment_on_newline = Some(false);
26411    });
26412    let mut cx = EditorTestContext::new(cx).await;
26413    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26414    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26415
26416    // test correct indent after newline on comment
26417    cx.set_state(indoc! {"
26418        # COMMENT:ˇ
26419    "});
26420    cx.update_editor(|editor, window, cx| {
26421        editor.newline(&Newline, window, cx);
26422    });
26423    cx.wait_for_autoindent_applied().await;
26424    cx.assert_editor_state(indoc! {"
26425        # COMMENT:
26426        ˇ
26427    "});
26428
26429    // test correct indent after newline after `then`
26430    cx.set_state(indoc! {"
26431
26432        if [ \"$1\" = \"test\" ]; thenˇ
26433    "});
26434    cx.update_editor(|editor, window, cx| {
26435        editor.newline(&Newline, window, cx);
26436    });
26437    cx.wait_for_autoindent_applied().await;
26438    cx.assert_editor_state(indoc! {"
26439
26440        if [ \"$1\" = \"test\" ]; then
26441            ˇ
26442    "});
26443
26444    // test correct indent after newline after `else`
26445    cx.set_state(indoc! {"
26446        if [ \"$1\" = \"test\" ]; then
26447        elseˇ
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        if [ \"$1\" = \"test\" ]; then
26455        else
26456            ˇ
26457    "});
26458
26459    // test correct indent after newline after `elif`
26460    cx.set_state(indoc! {"
26461        if [ \"$1\" = \"test\" ]; then
26462        elifˇ
26463    "});
26464    cx.update_editor(|editor, window, cx| {
26465        editor.newline(&Newline, window, cx);
26466    });
26467    cx.wait_for_autoindent_applied().await;
26468    cx.assert_editor_state(indoc! {"
26469        if [ \"$1\" = \"test\" ]; then
26470        elif
26471            ˇ
26472    "});
26473
26474    // test correct indent after newline after `do`
26475    cx.set_state(indoc! {"
26476        for file in *.txt; doˇ
26477    "});
26478    cx.update_editor(|editor, window, cx| {
26479        editor.newline(&Newline, window, cx);
26480    });
26481    cx.wait_for_autoindent_applied().await;
26482    cx.assert_editor_state(indoc! {"
26483        for file in *.txt; do
26484            ˇ
26485    "});
26486
26487    // test correct indent after newline after case pattern
26488    cx.set_state(indoc! {"
26489        case \"$1\" in
26490            start)ˇ
26491    "});
26492    cx.update_editor(|editor, window, cx| {
26493        editor.newline(&Newline, window, cx);
26494    });
26495    cx.wait_for_autoindent_applied().await;
26496    cx.assert_editor_state(indoc! {"
26497        case \"$1\" in
26498            start)
26499                ˇ
26500    "});
26501
26502    // test correct indent after newline after case pattern
26503    cx.set_state(indoc! {"
26504        case \"$1\" in
26505            start)
26506                ;;
26507            *)ˇ
26508    "});
26509    cx.update_editor(|editor, window, cx| {
26510        editor.newline(&Newline, window, cx);
26511    });
26512    cx.wait_for_autoindent_applied().await;
26513    cx.assert_editor_state(indoc! {"
26514        case \"$1\" in
26515            start)
26516                ;;
26517            *)
26518                ˇ
26519    "});
26520
26521    // test correct indent after newline after function opening brace
26522    cx.set_state(indoc! {"
26523        function test() {ˇ}
26524    "});
26525    cx.update_editor(|editor, window, cx| {
26526        editor.newline(&Newline, window, cx);
26527    });
26528    cx.wait_for_autoindent_applied().await;
26529    cx.assert_editor_state(indoc! {"
26530        function test() {
26531            ˇ
26532        }
26533    "});
26534
26535    // test no extra indent after semicolon on same line
26536    cx.set_state(indoc! {"
26537        echo \"test\"26538    "});
26539    cx.update_editor(|editor, window, cx| {
26540        editor.newline(&Newline, window, cx);
26541    });
26542    cx.wait_for_autoindent_applied().await;
26543    cx.assert_editor_state(indoc! {"
26544        echo \"test\";
26545        ˇ
26546    "});
26547}
26548
26549fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26550    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26551    point..point
26552}
26553
26554#[track_caller]
26555fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26556    let (text, ranges) = marked_text_ranges(marked_text, true);
26557    assert_eq!(editor.text(cx), text);
26558    assert_eq!(
26559        editor.selections.ranges(&editor.display_snapshot(cx)),
26560        ranges
26561            .iter()
26562            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26563            .collect::<Vec<_>>(),
26564        "Assert selections are {}",
26565        marked_text
26566    );
26567}
26568
26569pub fn handle_signature_help_request(
26570    cx: &mut EditorLspTestContext,
26571    mocked_response: lsp::SignatureHelp,
26572) -> impl Future<Output = ()> + use<> {
26573    let mut request =
26574        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26575            let mocked_response = mocked_response.clone();
26576            async move { Ok(Some(mocked_response)) }
26577        });
26578
26579    async move {
26580        request.next().await;
26581    }
26582}
26583
26584#[track_caller]
26585pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26586    cx.update_editor(|editor, _, _| {
26587        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26588            let entries = menu.entries.borrow();
26589            let entries = entries
26590                .iter()
26591                .map(|entry| entry.string.as_str())
26592                .collect::<Vec<_>>();
26593            assert_eq!(entries, expected);
26594        } else {
26595            panic!("Expected completions menu");
26596        }
26597    });
26598}
26599
26600#[gpui::test]
26601async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26602    init_test(cx, |_| {});
26603    let mut cx = EditorLspTestContext::new_rust(
26604        lsp::ServerCapabilities {
26605            completion_provider: Some(lsp::CompletionOptions {
26606                ..Default::default()
26607            }),
26608            ..Default::default()
26609        },
26610        cx,
26611    )
26612    .await;
26613    cx.lsp
26614        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26615            Ok(Some(lsp::CompletionResponse::Array(vec![
26616                lsp::CompletionItem {
26617                    label: "unsafe".into(),
26618                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26619                        range: lsp::Range {
26620                            start: lsp::Position {
26621                                line: 0,
26622                                character: 9,
26623                            },
26624                            end: lsp::Position {
26625                                line: 0,
26626                                character: 11,
26627                            },
26628                        },
26629                        new_text: "unsafe".to_string(),
26630                    })),
26631                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26632                    ..Default::default()
26633                },
26634            ])))
26635        });
26636
26637    cx.update_editor(|editor, _, cx| {
26638        editor.project().unwrap().update(cx, |project, cx| {
26639            project.snippets().update(cx, |snippets, _cx| {
26640                snippets.add_snippet_for_test(
26641                    None,
26642                    PathBuf::from("test_snippets.json"),
26643                    vec![
26644                        Arc::new(project::snippet_provider::Snippet {
26645                            prefix: vec![
26646                                "unlimited word count".to_string(),
26647                                "unlimit word count".to_string(),
26648                                "unlimited unknown".to_string(),
26649                            ],
26650                            body: "this is many words".to_string(),
26651                            description: Some("description".to_string()),
26652                            name: "multi-word snippet test".to_string(),
26653                        }),
26654                        Arc::new(project::snippet_provider::Snippet {
26655                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26656                            body: "fewer words".to_string(),
26657                            description: Some("alt description".to_string()),
26658                            name: "other name".to_string(),
26659                        }),
26660                        Arc::new(project::snippet_provider::Snippet {
26661                            prefix: vec!["ab aa".to_string()],
26662                            body: "abcd".to_string(),
26663                            description: None,
26664                            name: "alphabet".to_string(),
26665                        }),
26666                    ],
26667                );
26668            });
26669        })
26670    });
26671
26672    let get_completions = |cx: &mut EditorLspTestContext| {
26673        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26674            Some(CodeContextMenu::Completions(context_menu)) => {
26675                let entries = context_menu.entries.borrow();
26676                entries
26677                    .iter()
26678                    .map(|entry| entry.string.clone())
26679                    .collect_vec()
26680            }
26681            _ => vec![],
26682        })
26683    };
26684
26685    // snippets:
26686    //  @foo
26687    //  foo bar
26688    //
26689    // when typing:
26690    //
26691    // when typing:
26692    //  - if I type a symbol "open the completions with snippets only"
26693    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26694    //
26695    // stuff we need:
26696    //  - filtering logic change?
26697    //  - remember how far back the completion started.
26698
26699    let test_cases: &[(&str, &[&str])] = &[
26700        (
26701            "un",
26702            &[
26703                "unsafe",
26704                "unlimit word count",
26705                "unlimited unknown",
26706                "unlimited word count",
26707                "unsnip",
26708            ],
26709        ),
26710        (
26711            "u ",
26712            &[
26713                "unlimit word count",
26714                "unlimited unknown",
26715                "unlimited word count",
26716            ],
26717        ),
26718        ("u a", &["ab aa", "unsafe"]), // unsAfe
26719        (
26720            "u u",
26721            &[
26722                "unsafe",
26723                "unlimit word count",
26724                "unlimited unknown", // ranked highest among snippets
26725                "unlimited word count",
26726                "unsnip",
26727            ],
26728        ),
26729        ("uw c", &["unlimit word count", "unlimited word count"]),
26730        (
26731            "u w",
26732            &[
26733                "unlimit word count",
26734                "unlimited word count",
26735                "unlimited unknown",
26736            ],
26737        ),
26738        ("u w ", &["unlimit word count", "unlimited word count"]),
26739        (
26740            "u ",
26741            &[
26742                "unlimit word count",
26743                "unlimited unknown",
26744                "unlimited word count",
26745            ],
26746        ),
26747        ("wor", &[]),
26748        ("uf", &["unsafe"]),
26749        ("af", &["unsafe"]),
26750        ("afu", &[]),
26751        (
26752            "ue",
26753            &["unsafe", "unlimited unknown", "unlimited word count"],
26754        ),
26755        ("@", &["@few"]),
26756        ("@few", &["@few"]),
26757        ("@ ", &[]),
26758        ("a@", &["@few"]),
26759        ("a@f", &["@few", "unsafe"]),
26760        ("a@fw", &["@few"]),
26761        ("a", &["ab aa", "unsafe"]),
26762        ("aa", &["ab aa"]),
26763        ("aaa", &["ab aa"]),
26764        ("ab", &["ab aa"]),
26765        ("ab ", &["ab aa"]),
26766        ("ab a", &["ab aa", "unsafe"]),
26767        ("ab ab", &["ab aa"]),
26768        ("ab ab aa", &["ab aa"]),
26769    ];
26770
26771    for &(input_to_simulate, expected_completions) in test_cases {
26772        cx.set_state("fn a() { ˇ }\n");
26773        for c in input_to_simulate.split("") {
26774            cx.simulate_input(c);
26775            cx.run_until_parked();
26776        }
26777        let expected_completions = expected_completions
26778            .iter()
26779            .map(|s| s.to_string())
26780            .collect_vec();
26781        assert_eq!(
26782            get_completions(&mut cx),
26783            expected_completions,
26784            "< actual / expected >, input = {input_to_simulate:?}",
26785        );
26786    }
26787}
26788
26789/// Handle completion request passing a marked string specifying where the completion
26790/// should be triggered from using '|' character, what range should be replaced, and what completions
26791/// should be returned using '<' and '>' to delimit the range.
26792///
26793/// Also see `handle_completion_request_with_insert_and_replace`.
26794#[track_caller]
26795pub fn handle_completion_request(
26796    marked_string: &str,
26797    completions: Vec<&'static str>,
26798    is_incomplete: bool,
26799    counter: Arc<AtomicUsize>,
26800    cx: &mut EditorLspTestContext,
26801) -> impl Future<Output = ()> {
26802    let complete_from_marker: TextRangeMarker = '|'.into();
26803    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26804    let (_, mut marked_ranges) = marked_text_ranges_by(
26805        marked_string,
26806        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26807    );
26808
26809    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26810        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26811    ));
26812    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26813    let replace_range =
26814        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26815
26816    let mut request =
26817        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26818            let completions = completions.clone();
26819            counter.fetch_add(1, atomic::Ordering::Release);
26820            async move {
26821                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26822                assert_eq!(
26823                    params.text_document_position.position,
26824                    complete_from_position
26825                );
26826                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26827                    is_incomplete,
26828                    item_defaults: None,
26829                    items: completions
26830                        .iter()
26831                        .map(|completion_text| lsp::CompletionItem {
26832                            label: completion_text.to_string(),
26833                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26834                                range: replace_range,
26835                                new_text: completion_text.to_string(),
26836                            })),
26837                            ..Default::default()
26838                        })
26839                        .collect(),
26840                })))
26841            }
26842        });
26843
26844    async move {
26845        request.next().await;
26846    }
26847}
26848
26849/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26850/// given instead, which also contains an `insert` range.
26851///
26852/// This function uses markers to define ranges:
26853/// - `|` marks the cursor position
26854/// - `<>` marks the replace range
26855/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26856pub fn handle_completion_request_with_insert_and_replace(
26857    cx: &mut EditorLspTestContext,
26858    marked_string: &str,
26859    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26860    counter: Arc<AtomicUsize>,
26861) -> impl Future<Output = ()> {
26862    let complete_from_marker: TextRangeMarker = '|'.into();
26863    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26864    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26865
26866    let (_, mut marked_ranges) = marked_text_ranges_by(
26867        marked_string,
26868        vec![
26869            complete_from_marker.clone(),
26870            replace_range_marker.clone(),
26871            insert_range_marker.clone(),
26872        ],
26873    );
26874
26875    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26876        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26877    ));
26878    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26879    let replace_range =
26880        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26881
26882    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26883        Some(ranges) if !ranges.is_empty() => {
26884            let range1 = ranges[0].clone();
26885            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26886        }
26887        _ => lsp::Range {
26888            start: replace_range.start,
26889            end: complete_from_position,
26890        },
26891    };
26892
26893    let mut request =
26894        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26895            let completions = completions.clone();
26896            counter.fetch_add(1, atomic::Ordering::Release);
26897            async move {
26898                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26899                assert_eq!(
26900                    params.text_document_position.position, complete_from_position,
26901                    "marker `|` position doesn't match",
26902                );
26903                Ok(Some(lsp::CompletionResponse::Array(
26904                    completions
26905                        .iter()
26906                        .map(|(label, new_text)| lsp::CompletionItem {
26907                            label: label.to_string(),
26908                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26909                                lsp::InsertReplaceEdit {
26910                                    insert: insert_range,
26911                                    replace: replace_range,
26912                                    new_text: new_text.to_string(),
26913                                },
26914                            )),
26915                            ..Default::default()
26916                        })
26917                        .collect(),
26918                )))
26919            }
26920        });
26921
26922    async move {
26923        request.next().await;
26924    }
26925}
26926
26927fn handle_resolve_completion_request(
26928    cx: &mut EditorLspTestContext,
26929    edits: Option<Vec<(&'static str, &'static str)>>,
26930) -> impl Future<Output = ()> {
26931    let edits = edits.map(|edits| {
26932        edits
26933            .iter()
26934            .map(|(marked_string, new_text)| {
26935                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26936                let replace_range = cx.to_lsp_range(
26937                    MultiBufferOffset(marked_ranges[0].start)
26938                        ..MultiBufferOffset(marked_ranges[0].end),
26939                );
26940                lsp::TextEdit::new(replace_range, new_text.to_string())
26941            })
26942            .collect::<Vec<_>>()
26943    });
26944
26945    let mut request =
26946        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26947            let edits = edits.clone();
26948            async move {
26949                Ok(lsp::CompletionItem {
26950                    additional_text_edits: edits,
26951                    ..Default::default()
26952                })
26953            }
26954        });
26955
26956    async move {
26957        request.next().await;
26958    }
26959}
26960
26961pub(crate) fn update_test_language_settings(
26962    cx: &mut TestAppContext,
26963    f: impl Fn(&mut AllLanguageSettingsContent),
26964) {
26965    cx.update(|cx| {
26966        SettingsStore::update_global(cx, |store, cx| {
26967            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26968        });
26969    });
26970}
26971
26972pub(crate) fn update_test_project_settings(
26973    cx: &mut TestAppContext,
26974    f: impl Fn(&mut ProjectSettingsContent),
26975) {
26976    cx.update(|cx| {
26977        SettingsStore::update_global(cx, |store, cx| {
26978            store.update_user_settings(cx, |settings| f(&mut settings.project));
26979        });
26980    });
26981}
26982
26983pub(crate) fn update_test_editor_settings(
26984    cx: &mut TestAppContext,
26985    f: impl Fn(&mut EditorSettingsContent),
26986) {
26987    cx.update(|cx| {
26988        SettingsStore::update_global(cx, |store, cx| {
26989            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26990        })
26991    })
26992}
26993
26994pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26995    cx.update(|cx| {
26996        assets::Assets.load_test_fonts(cx);
26997        let store = SettingsStore::test(cx);
26998        cx.set_global(store);
26999        theme::init(theme::LoadThemes::JustBase, cx);
27000        release_channel::init(semver::Version::new(0, 0, 0), cx);
27001        crate::init(cx);
27002    });
27003    zlog::init_test();
27004    update_test_language_settings(cx, f);
27005}
27006
27007#[track_caller]
27008fn assert_hunk_revert(
27009    not_reverted_text_with_selections: &str,
27010    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27011    expected_reverted_text_with_selections: &str,
27012    base_text: &str,
27013    cx: &mut EditorLspTestContext,
27014) {
27015    cx.set_state(not_reverted_text_with_selections);
27016    cx.set_head_text(base_text);
27017    cx.executor().run_until_parked();
27018
27019    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27020        let snapshot = editor.snapshot(window, cx);
27021        let reverted_hunk_statuses = snapshot
27022            .buffer_snapshot()
27023            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27024            .map(|hunk| hunk.status().kind)
27025            .collect::<Vec<_>>();
27026
27027        editor.git_restore(&Default::default(), window, cx);
27028        reverted_hunk_statuses
27029    });
27030    cx.executor().run_until_parked();
27031    cx.assert_editor_state(expected_reverted_text_with_selections);
27032    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27033}
27034
27035#[gpui::test(iterations = 10)]
27036async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27037    init_test(cx, |_| {});
27038
27039    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27040    let counter = diagnostic_requests.clone();
27041
27042    let fs = FakeFs::new(cx.executor());
27043    fs.insert_tree(
27044        path!("/a"),
27045        json!({
27046            "first.rs": "fn main() { let a = 5; }",
27047            "second.rs": "// Test file",
27048        }),
27049    )
27050    .await;
27051
27052    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27053    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27054    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27055
27056    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27057    language_registry.add(rust_lang());
27058    let mut fake_servers = language_registry.register_fake_lsp(
27059        "Rust",
27060        FakeLspAdapter {
27061            capabilities: lsp::ServerCapabilities {
27062                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27063                    lsp::DiagnosticOptions {
27064                        identifier: None,
27065                        inter_file_dependencies: true,
27066                        workspace_diagnostics: true,
27067                        work_done_progress_options: Default::default(),
27068                    },
27069                )),
27070                ..Default::default()
27071            },
27072            ..Default::default()
27073        },
27074    );
27075
27076    let editor = workspace
27077        .update(cx, |workspace, window, cx| {
27078            workspace.open_abs_path(
27079                PathBuf::from(path!("/a/first.rs")),
27080                OpenOptions::default(),
27081                window,
27082                cx,
27083            )
27084        })
27085        .unwrap()
27086        .await
27087        .unwrap()
27088        .downcast::<Editor>()
27089        .unwrap();
27090    let fake_server = fake_servers.next().await.unwrap();
27091    let server_id = fake_server.server.server_id();
27092    let mut first_request = fake_server
27093        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27094            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27095            let result_id = Some(new_result_id.to_string());
27096            assert_eq!(
27097                params.text_document.uri,
27098                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27099            );
27100            async move {
27101                Ok(lsp::DocumentDiagnosticReportResult::Report(
27102                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27103                        related_documents: None,
27104                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27105                            items: Vec::new(),
27106                            result_id,
27107                        },
27108                    }),
27109                ))
27110            }
27111        });
27112
27113    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27114        project.update(cx, |project, cx| {
27115            let buffer_id = editor
27116                .read(cx)
27117                .buffer()
27118                .read(cx)
27119                .as_singleton()
27120                .expect("created a singleton buffer")
27121                .read(cx)
27122                .remote_id();
27123            let buffer_result_id = project
27124                .lsp_store()
27125                .read(cx)
27126                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27127            assert_eq!(expected, buffer_result_id);
27128        });
27129    };
27130
27131    ensure_result_id(None, cx);
27132    cx.executor().advance_clock(Duration::from_millis(60));
27133    cx.executor().run_until_parked();
27134    assert_eq!(
27135        diagnostic_requests.load(atomic::Ordering::Acquire),
27136        1,
27137        "Opening file should trigger diagnostic request"
27138    );
27139    first_request
27140        .next()
27141        .await
27142        .expect("should have sent the first diagnostics pull request");
27143    ensure_result_id(Some(SharedString::new("1")), cx);
27144
27145    // Editing should trigger diagnostics
27146    editor.update_in(cx, |editor, window, cx| {
27147        editor.handle_input("2", window, cx)
27148    });
27149    cx.executor().advance_clock(Duration::from_millis(60));
27150    cx.executor().run_until_parked();
27151    assert_eq!(
27152        diagnostic_requests.load(atomic::Ordering::Acquire),
27153        2,
27154        "Editing should trigger diagnostic request"
27155    );
27156    ensure_result_id(Some(SharedString::new("2")), cx);
27157
27158    // Moving cursor should not trigger diagnostic request
27159    editor.update_in(cx, |editor, window, cx| {
27160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27161            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27162        });
27163    });
27164    cx.executor().advance_clock(Duration::from_millis(60));
27165    cx.executor().run_until_parked();
27166    assert_eq!(
27167        diagnostic_requests.load(atomic::Ordering::Acquire),
27168        2,
27169        "Cursor movement should not trigger diagnostic request"
27170    );
27171    ensure_result_id(Some(SharedString::new("2")), cx);
27172    // Multiple rapid edits should be debounced
27173    for _ in 0..5 {
27174        editor.update_in(cx, |editor, window, cx| {
27175            editor.handle_input("x", window, cx)
27176        });
27177    }
27178    cx.executor().advance_clock(Duration::from_millis(60));
27179    cx.executor().run_until_parked();
27180
27181    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27182    assert!(
27183        final_requests <= 4,
27184        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27185    );
27186    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27187}
27188
27189#[gpui::test]
27190async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27191    // Regression test for issue #11671
27192    // Previously, adding a cursor after moving multiple cursors would reset
27193    // the cursor count instead of adding to the existing cursors.
27194    init_test(cx, |_| {});
27195    let mut cx = EditorTestContext::new(cx).await;
27196
27197    // Create a simple buffer with cursor at start
27198    cx.set_state(indoc! {"
27199        ˇaaaa
27200        bbbb
27201        cccc
27202        dddd
27203        eeee
27204        ffff
27205        gggg
27206        hhhh"});
27207
27208    // Add 2 cursors below (so we have 3 total)
27209    cx.update_editor(|editor, window, cx| {
27210        editor.add_selection_below(&Default::default(), window, cx);
27211        editor.add_selection_below(&Default::default(), window, cx);
27212    });
27213
27214    // Verify we have 3 cursors
27215    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27216    assert_eq!(
27217        initial_count, 3,
27218        "Should have 3 cursors after adding 2 below"
27219    );
27220
27221    // Move down one line
27222    cx.update_editor(|editor, window, cx| {
27223        editor.move_down(&MoveDown, window, cx);
27224    });
27225
27226    // Add another cursor below
27227    cx.update_editor(|editor, window, cx| {
27228        editor.add_selection_below(&Default::default(), window, cx);
27229    });
27230
27231    // Should now have 4 cursors (3 original + 1 new)
27232    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27233    assert_eq!(
27234        final_count, 4,
27235        "Should have 4 cursors after moving and adding another"
27236    );
27237}
27238
27239#[gpui::test]
27240async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27241    init_test(cx, |_| {});
27242
27243    let mut cx = EditorTestContext::new(cx).await;
27244
27245    cx.set_state(indoc!(
27246        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27247           Second line here"#
27248    ));
27249
27250    cx.update_editor(|editor, window, cx| {
27251        // Enable soft wrapping with a narrow width to force soft wrapping and
27252        // confirm that more than 2 rows are being displayed.
27253        editor.set_wrap_width(Some(100.0.into()), cx);
27254        assert!(editor.display_text(cx).lines().count() > 2);
27255
27256        editor.add_selection_below(
27257            &AddSelectionBelow {
27258                skip_soft_wrap: true,
27259            },
27260            window,
27261            cx,
27262        );
27263
27264        assert_eq!(
27265            display_ranges(editor, cx),
27266            &[
27267                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27268                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27269            ]
27270        );
27271
27272        editor.add_selection_above(
27273            &AddSelectionAbove {
27274                skip_soft_wrap: true,
27275            },
27276            window,
27277            cx,
27278        );
27279
27280        assert_eq!(
27281            display_ranges(editor, cx),
27282            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27283        );
27284
27285        editor.add_selection_below(
27286            &AddSelectionBelow {
27287                skip_soft_wrap: false,
27288            },
27289            window,
27290            cx,
27291        );
27292
27293        assert_eq!(
27294            display_ranges(editor, cx),
27295            &[
27296                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27297                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27298            ]
27299        );
27300
27301        editor.add_selection_above(
27302            &AddSelectionAbove {
27303                skip_soft_wrap: false,
27304            },
27305            window,
27306            cx,
27307        );
27308
27309        assert_eq!(
27310            display_ranges(editor, cx),
27311            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27312        );
27313    });
27314}
27315
27316#[gpui::test]
27317async fn test_insert_snippet(cx: &mut TestAppContext) {
27318    init_test(cx, |_| {});
27319    let mut cx = EditorTestContext::new(cx).await;
27320
27321    cx.update_editor(|editor, _, cx| {
27322        editor.project().unwrap().update(cx, |project, cx| {
27323            project.snippets().update(cx, |snippets, _cx| {
27324                let snippet = project::snippet_provider::Snippet {
27325                    prefix: vec![], // no prefix needed!
27326                    body: "an Unspecified".to_string(),
27327                    description: Some("shhhh it's a secret".to_string()),
27328                    name: "super secret snippet".to_string(),
27329                };
27330                snippets.add_snippet_for_test(
27331                    None,
27332                    PathBuf::from("test_snippets.json"),
27333                    vec![Arc::new(snippet)],
27334                );
27335
27336                let snippet = project::snippet_provider::Snippet {
27337                    prefix: vec![], // no prefix needed!
27338                    body: " Location".to_string(),
27339                    description: Some("the word 'location'".to_string()),
27340                    name: "location word".to_string(),
27341                };
27342                snippets.add_snippet_for_test(
27343                    Some("Markdown".to_string()),
27344                    PathBuf::from("test_snippets.json"),
27345                    vec![Arc::new(snippet)],
27346                );
27347            });
27348        })
27349    });
27350
27351    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27352
27353    cx.update_editor(|editor, window, cx| {
27354        editor.insert_snippet_at_selections(
27355            &InsertSnippet {
27356                language: None,
27357                name: Some("super secret snippet".to_string()),
27358                snippet: None,
27359            },
27360            window,
27361            cx,
27362        );
27363
27364        // Language is specified in the action,
27365        // so the buffer language does not need to match
27366        editor.insert_snippet_at_selections(
27367            &InsertSnippet {
27368                language: Some("Markdown".to_string()),
27369                name: Some("location word".to_string()),
27370                snippet: None,
27371            },
27372            window,
27373            cx,
27374        );
27375
27376        editor.insert_snippet_at_selections(
27377            &InsertSnippet {
27378                language: None,
27379                name: None,
27380                snippet: Some("$0 after".to_string()),
27381            },
27382            window,
27383            cx,
27384        );
27385    });
27386
27387    cx.assert_editor_state(
27388        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27389    );
27390}
27391
27392#[gpui::test(iterations = 10)]
27393async fn test_document_colors(cx: &mut TestAppContext) {
27394    let expected_color = Rgba {
27395        r: 0.33,
27396        g: 0.33,
27397        b: 0.33,
27398        a: 0.33,
27399    };
27400
27401    init_test(cx, |_| {});
27402
27403    let fs = FakeFs::new(cx.executor());
27404    fs.insert_tree(
27405        path!("/a"),
27406        json!({
27407            "first.rs": "fn main() { let a = 5; }",
27408        }),
27409    )
27410    .await;
27411
27412    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27413    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27414    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27415
27416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27417    language_registry.add(rust_lang());
27418    let mut fake_servers = language_registry.register_fake_lsp(
27419        "Rust",
27420        FakeLspAdapter {
27421            capabilities: lsp::ServerCapabilities {
27422                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27423                ..lsp::ServerCapabilities::default()
27424            },
27425            name: "rust-analyzer",
27426            ..FakeLspAdapter::default()
27427        },
27428    );
27429    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27430        "Rust",
27431        FakeLspAdapter {
27432            capabilities: lsp::ServerCapabilities {
27433                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27434                ..lsp::ServerCapabilities::default()
27435            },
27436            name: "not-rust-analyzer",
27437            ..FakeLspAdapter::default()
27438        },
27439    );
27440
27441    let editor = workspace
27442        .update(cx, |workspace, window, cx| {
27443            workspace.open_abs_path(
27444                PathBuf::from(path!("/a/first.rs")),
27445                OpenOptions::default(),
27446                window,
27447                cx,
27448            )
27449        })
27450        .unwrap()
27451        .await
27452        .unwrap()
27453        .downcast::<Editor>()
27454        .unwrap();
27455    let fake_language_server = fake_servers.next().await.unwrap();
27456    let fake_language_server_without_capabilities =
27457        fake_servers_without_capabilities.next().await.unwrap();
27458    let requests_made = Arc::new(AtomicUsize::new(0));
27459    let closure_requests_made = Arc::clone(&requests_made);
27460    let mut color_request_handle = fake_language_server
27461        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27462            let requests_made = Arc::clone(&closure_requests_made);
27463            async move {
27464                assert_eq!(
27465                    params.text_document.uri,
27466                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27467                );
27468                requests_made.fetch_add(1, atomic::Ordering::Release);
27469                Ok(vec![
27470                    lsp::ColorInformation {
27471                        range: lsp::Range {
27472                            start: lsp::Position {
27473                                line: 0,
27474                                character: 0,
27475                            },
27476                            end: lsp::Position {
27477                                line: 0,
27478                                character: 1,
27479                            },
27480                        },
27481                        color: lsp::Color {
27482                            red: 0.33,
27483                            green: 0.33,
27484                            blue: 0.33,
27485                            alpha: 0.33,
27486                        },
27487                    },
27488                    lsp::ColorInformation {
27489                        range: lsp::Range {
27490                            start: lsp::Position {
27491                                line: 0,
27492                                character: 0,
27493                            },
27494                            end: lsp::Position {
27495                                line: 0,
27496                                character: 1,
27497                            },
27498                        },
27499                        color: lsp::Color {
27500                            red: 0.33,
27501                            green: 0.33,
27502                            blue: 0.33,
27503                            alpha: 0.33,
27504                        },
27505                    },
27506                ])
27507            }
27508        });
27509
27510    let _handle = fake_language_server_without_capabilities
27511        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27512            panic!("Should not be called");
27513        });
27514    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27515    color_request_handle.next().await.unwrap();
27516    cx.run_until_parked();
27517    assert_eq!(
27518        1,
27519        requests_made.load(atomic::Ordering::Acquire),
27520        "Should query for colors once per editor open"
27521    );
27522    editor.update_in(cx, |editor, _, cx| {
27523        assert_eq!(
27524            vec![expected_color],
27525            extract_color_inlays(editor, cx),
27526            "Should have an initial inlay"
27527        );
27528    });
27529
27530    // opening another file in a split should not influence the LSP query counter
27531    workspace
27532        .update(cx, |workspace, window, cx| {
27533            assert_eq!(
27534                workspace.panes().len(),
27535                1,
27536                "Should have one pane with one editor"
27537            );
27538            workspace.move_item_to_pane_in_direction(
27539                &MoveItemToPaneInDirection {
27540                    direction: SplitDirection::Right,
27541                    focus: false,
27542                    clone: true,
27543                },
27544                window,
27545                cx,
27546            );
27547        })
27548        .unwrap();
27549    cx.run_until_parked();
27550    workspace
27551        .update(cx, |workspace, _, cx| {
27552            let panes = workspace.panes();
27553            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27554            for pane in panes {
27555                let editor = pane
27556                    .read(cx)
27557                    .active_item()
27558                    .and_then(|item| item.downcast::<Editor>())
27559                    .expect("Should have opened an editor in each split");
27560                let editor_file = editor
27561                    .read(cx)
27562                    .buffer()
27563                    .read(cx)
27564                    .as_singleton()
27565                    .expect("test deals with singleton buffers")
27566                    .read(cx)
27567                    .file()
27568                    .expect("test buffese should have a file")
27569                    .path();
27570                assert_eq!(
27571                    editor_file.as_ref(),
27572                    rel_path("first.rs"),
27573                    "Both editors should be opened for the same file"
27574                )
27575            }
27576        })
27577        .unwrap();
27578
27579    cx.executor().advance_clock(Duration::from_millis(500));
27580    let save = editor.update_in(cx, |editor, window, cx| {
27581        editor.move_to_end(&MoveToEnd, window, cx);
27582        editor.handle_input("dirty", window, cx);
27583        editor.save(
27584            SaveOptions {
27585                format: true,
27586                autosave: true,
27587            },
27588            project.clone(),
27589            window,
27590            cx,
27591        )
27592    });
27593    save.await.unwrap();
27594
27595    color_request_handle.next().await.unwrap();
27596    cx.run_until_parked();
27597    assert_eq!(
27598        2,
27599        requests_made.load(atomic::Ordering::Acquire),
27600        "Should query for colors once per save (deduplicated) and once per formatting after save"
27601    );
27602
27603    drop(editor);
27604    let close = workspace
27605        .update(cx, |workspace, window, cx| {
27606            workspace.active_pane().update(cx, |pane, cx| {
27607                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27608            })
27609        })
27610        .unwrap();
27611    close.await.unwrap();
27612    let close = workspace
27613        .update(cx, |workspace, window, cx| {
27614            workspace.active_pane().update(cx, |pane, cx| {
27615                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27616            })
27617        })
27618        .unwrap();
27619    close.await.unwrap();
27620    assert_eq!(
27621        2,
27622        requests_made.load(atomic::Ordering::Acquire),
27623        "After saving and closing all editors, no extra requests should be made"
27624    );
27625    workspace
27626        .update(cx, |workspace, _, cx| {
27627            assert!(
27628                workspace.active_item(cx).is_none(),
27629                "Should close all editors"
27630            )
27631        })
27632        .unwrap();
27633
27634    workspace
27635        .update(cx, |workspace, window, cx| {
27636            workspace.active_pane().update(cx, |pane, cx| {
27637                pane.navigate_backward(&workspace::GoBack, window, cx);
27638            })
27639        })
27640        .unwrap();
27641    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27642    cx.run_until_parked();
27643    let editor = workspace
27644        .update(cx, |workspace, _, cx| {
27645            workspace
27646                .active_item(cx)
27647                .expect("Should have reopened the editor again after navigating back")
27648                .downcast::<Editor>()
27649                .expect("Should be an editor")
27650        })
27651        .unwrap();
27652
27653    assert_eq!(
27654        2,
27655        requests_made.load(atomic::Ordering::Acquire),
27656        "Cache should be reused on buffer close and reopen"
27657    );
27658    editor.update(cx, |editor, cx| {
27659        assert_eq!(
27660            vec![expected_color],
27661            extract_color_inlays(editor, cx),
27662            "Should have an initial inlay"
27663        );
27664    });
27665
27666    drop(color_request_handle);
27667    let closure_requests_made = Arc::clone(&requests_made);
27668    let mut empty_color_request_handle = fake_language_server
27669        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27670            let requests_made = Arc::clone(&closure_requests_made);
27671            async move {
27672                assert_eq!(
27673                    params.text_document.uri,
27674                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27675                );
27676                requests_made.fetch_add(1, atomic::Ordering::Release);
27677                Ok(Vec::new())
27678            }
27679        });
27680    let save = editor.update_in(cx, |editor, window, cx| {
27681        editor.move_to_end(&MoveToEnd, window, cx);
27682        editor.handle_input("dirty_again", window, cx);
27683        editor.save(
27684            SaveOptions {
27685                format: false,
27686                autosave: true,
27687            },
27688            project.clone(),
27689            window,
27690            cx,
27691        )
27692    });
27693    save.await.unwrap();
27694
27695    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27696    empty_color_request_handle.next().await.unwrap();
27697    cx.run_until_parked();
27698    assert_eq!(
27699        3,
27700        requests_made.load(atomic::Ordering::Acquire),
27701        "Should query for colors once per save only, as formatting was not requested"
27702    );
27703    editor.update(cx, |editor, cx| {
27704        assert_eq!(
27705            Vec::<Rgba>::new(),
27706            extract_color_inlays(editor, cx),
27707            "Should clear all colors when the server returns an empty response"
27708        );
27709    });
27710}
27711
27712#[gpui::test]
27713async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27714    init_test(cx, |_| {});
27715    let (editor, cx) = cx.add_window_view(Editor::single_line);
27716    editor.update_in(cx, |editor, window, cx| {
27717        editor.set_text("oops\n\nwow\n", window, cx)
27718    });
27719    cx.run_until_parked();
27720    editor.update(cx, |editor, cx| {
27721        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27722    });
27723    editor.update(cx, |editor, cx| {
27724        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27725    });
27726    cx.run_until_parked();
27727    editor.update(cx, |editor, cx| {
27728        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27729    });
27730}
27731
27732#[gpui::test]
27733async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27734    init_test(cx, |_| {});
27735
27736    cx.update(|cx| {
27737        register_project_item::<Editor>(cx);
27738    });
27739
27740    let fs = FakeFs::new(cx.executor());
27741    fs.insert_tree("/root1", json!({})).await;
27742    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27743        .await;
27744
27745    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27746    let (workspace, cx) =
27747        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27748
27749    let worktree_id = project.update(cx, |project, cx| {
27750        project.worktrees(cx).next().unwrap().read(cx).id()
27751    });
27752
27753    let handle = workspace
27754        .update_in(cx, |workspace, window, cx| {
27755            let project_path = (worktree_id, rel_path("one.pdf"));
27756            workspace.open_path(project_path, None, true, window, cx)
27757        })
27758        .await
27759        .unwrap();
27760    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
27761    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
27762    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
27763    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
27764}
27765
27766#[gpui::test]
27767async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27768    init_test(cx, |_| {});
27769
27770    let language = Arc::new(Language::new(
27771        LanguageConfig::default(),
27772        Some(tree_sitter_rust::LANGUAGE.into()),
27773    ));
27774
27775    // Test hierarchical sibling navigation
27776    let text = r#"
27777        fn outer() {
27778            if condition {
27779                let a = 1;
27780            }
27781            let b = 2;
27782        }
27783
27784        fn another() {
27785            let c = 3;
27786        }
27787    "#;
27788
27789    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27790    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27791    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27792
27793    // Wait for parsing to complete
27794    editor
27795        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27796        .await;
27797
27798    editor.update_in(cx, |editor, window, cx| {
27799        // Start by selecting "let a = 1;" inside the if block
27800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27801            s.select_display_ranges([
27802                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27803            ]);
27804        });
27805
27806        let initial_selection = editor
27807            .selections
27808            .display_ranges(&editor.display_snapshot(cx));
27809        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27810
27811        // Test select next sibling - should move up levels to find the next sibling
27812        // Since "let a = 1;" has no siblings in the if block, it should move up
27813        // to find "let b = 2;" which is a sibling of the if block
27814        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27815        let next_selection = editor
27816            .selections
27817            .display_ranges(&editor.display_snapshot(cx));
27818
27819        // Should have a selection and it should be different from the initial
27820        assert_eq!(
27821            next_selection.len(),
27822            1,
27823            "Should have one selection after next"
27824        );
27825        assert_ne!(
27826            next_selection[0], initial_selection[0],
27827            "Next sibling selection should be different"
27828        );
27829
27830        // Test hierarchical navigation by going to the end of the current function
27831        // and trying to navigate to the next function
27832        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27833            s.select_display_ranges([
27834                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27835            ]);
27836        });
27837
27838        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27839        let function_next_selection = editor
27840            .selections
27841            .display_ranges(&editor.display_snapshot(cx));
27842
27843        // Should move to the next function
27844        assert_eq!(
27845            function_next_selection.len(),
27846            1,
27847            "Should have one selection after function next"
27848        );
27849
27850        // Test select previous sibling navigation
27851        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27852        let prev_selection = editor
27853            .selections
27854            .display_ranges(&editor.display_snapshot(cx));
27855
27856        // Should have a selection and it should be different
27857        assert_eq!(
27858            prev_selection.len(),
27859            1,
27860            "Should have one selection after prev"
27861        );
27862        assert_ne!(
27863            prev_selection[0], function_next_selection[0],
27864            "Previous sibling selection should be different from next"
27865        );
27866    });
27867}
27868
27869#[gpui::test]
27870async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27871    init_test(cx, |_| {});
27872
27873    let mut cx = EditorTestContext::new(cx).await;
27874    cx.set_state(
27875        "let ˇvariable = 42;
27876let another = variable + 1;
27877let result = variable * 2;",
27878    );
27879
27880    // Set up document highlights manually (simulating LSP response)
27881    cx.update_editor(|editor, _window, cx| {
27882        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27883
27884        // Create highlights for "variable" occurrences
27885        let highlight_ranges = [
27886            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27887            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27888            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27889        ];
27890
27891        let anchor_ranges: Vec<_> = highlight_ranges
27892            .iter()
27893            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27894            .collect();
27895
27896        editor.highlight_background::<DocumentHighlightRead>(
27897            &anchor_ranges,
27898            |_, theme| theme.colors().editor_document_highlight_read_background,
27899            cx,
27900        );
27901    });
27902
27903    // Go to next highlight - should move to second "variable"
27904    cx.update_editor(|editor, window, cx| {
27905        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27906    });
27907    cx.assert_editor_state(
27908        "let variable = 42;
27909let another = ˇvariable + 1;
27910let result = variable * 2;",
27911    );
27912
27913    // Go to next highlight - should move to third "variable"
27914    cx.update_editor(|editor, window, cx| {
27915        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27916    });
27917    cx.assert_editor_state(
27918        "let variable = 42;
27919let another = variable + 1;
27920let result = ˇvariable * 2;",
27921    );
27922
27923    // Go to next highlight - should stay at third "variable" (no wrap-around)
27924    cx.update_editor(|editor, window, cx| {
27925        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27926    });
27927    cx.assert_editor_state(
27928        "let variable = 42;
27929let another = variable + 1;
27930let result = ˇvariable * 2;",
27931    );
27932
27933    // Now test going backwards from third position
27934    cx.update_editor(|editor, window, cx| {
27935        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27936    });
27937    cx.assert_editor_state(
27938        "let variable = 42;
27939let another = ˇvariable + 1;
27940let result = variable * 2;",
27941    );
27942
27943    // Go to previous highlight - should move to first "variable"
27944    cx.update_editor(|editor, window, cx| {
27945        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27946    });
27947    cx.assert_editor_state(
27948        "let ˇvariable = 42;
27949let another = variable + 1;
27950let result = variable * 2;",
27951    );
27952
27953    // Go to previous highlight - should stay on first "variable"
27954    cx.update_editor(|editor, window, cx| {
27955        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27956    });
27957    cx.assert_editor_state(
27958        "let ˇvariable = 42;
27959let another = variable + 1;
27960let result = variable * 2;",
27961    );
27962}
27963
27964#[gpui::test]
27965async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27966    cx: &mut gpui::TestAppContext,
27967) {
27968    init_test(cx, |_| {});
27969
27970    let url = "https://zed.dev";
27971
27972    let markdown_language = Arc::new(Language::new(
27973        LanguageConfig {
27974            name: "Markdown".into(),
27975            ..LanguageConfig::default()
27976        },
27977        None,
27978    ));
27979
27980    let mut cx = EditorTestContext::new(cx).await;
27981    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27982    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27983
27984    cx.update_editor(|editor, window, cx| {
27985        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27986        editor.paste(&Paste, window, cx);
27987    });
27988
27989    cx.assert_editor_state(&format!(
27990        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27991    ));
27992}
27993
27994#[gpui::test]
27995async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27996    init_test(cx, |_| {});
27997
27998    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27999    let mut cx = EditorTestContext::new(cx).await;
28000
28001    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28002
28003    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28004    cx.set_state(&indoc! {"
28005        - [ ] Item 1
28006            - [ ] Item 1.a
28007        - [ˇ] Item 2
28008            - [ˇ] Item 2.a
28009            - [ˇ] Item 2.b
28010        "
28011    });
28012    cx.update_editor(|editor, window, cx| {
28013        editor.handle_input("x", window, cx);
28014    });
28015    cx.run_until_parked();
28016    cx.assert_editor_state(indoc! {"
28017        - [ ] Item 1
28018            - [ ] Item 1.a
28019        - [xˇ] Item 2
28020            - [xˇ] Item 2.a
28021            - [xˇ] Item 2.b
28022        "
28023    });
28024
28025    // Case 2: Test adding new line after nested list continues the list with unchecked task
28026    cx.set_state(&indoc! {"
28027        - [ ] Item 1
28028            - [ ] Item 1.a
28029        - [x] Item 2
28030            - [x] Item 2.a
28031            - [x] Item 2.bˇ"
28032    });
28033    cx.update_editor(|editor, window, cx| {
28034        editor.newline(&Newline, window, cx);
28035    });
28036    cx.assert_editor_state(indoc! {"
28037        - [ ] Item 1
28038            - [ ] Item 1.a
28039        - [x] Item 2
28040            - [x] Item 2.a
28041            - [x] Item 2.b
28042            - [ ] ˇ"
28043    });
28044
28045    // Case 3: Test adding content to continued list item
28046    cx.update_editor(|editor, window, cx| {
28047        editor.handle_input("Item 2.c", window, cx);
28048    });
28049    cx.run_until_parked();
28050    cx.assert_editor_state(indoc! {"
28051        - [ ] Item 1
28052            - [ ] Item 1.a
28053        - [x] Item 2
28054            - [x] Item 2.a
28055            - [x] Item 2.b
28056            - [ ] Item 2.cˇ"
28057    });
28058
28059    // Case 4: Test adding new line after nested ordered list continues with next number
28060    cx.set_state(indoc! {"
28061        1. Item 1
28062            1. Item 1.a
28063        2. Item 2
28064            1. Item 2.a
28065            2. Item 2.bˇ"
28066    });
28067    cx.update_editor(|editor, window, cx| {
28068        editor.newline(&Newline, window, cx);
28069    });
28070    cx.assert_editor_state(indoc! {"
28071        1. Item 1
28072            1. Item 1.a
28073        2. Item 2
28074            1. Item 2.a
28075            2. Item 2.b
28076            3. ˇ"
28077    });
28078
28079    // Case 5: Adding content to continued ordered list item
28080    cx.update_editor(|editor, window, cx| {
28081        editor.handle_input("Item 2.c", window, cx);
28082    });
28083    cx.run_until_parked();
28084    cx.assert_editor_state(indoc! {"
28085        1. Item 1
28086            1. Item 1.a
28087        2. Item 2
28088            1. Item 2.a
28089            2. Item 2.b
28090            3. Item 2.cˇ"
28091    });
28092
28093    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28094    cx.set_state(indoc! {"
28095        - Item 1
28096            - Item 1.a
28097            - Item 1.a
28098        ˇ"});
28099    cx.update_editor(|editor, window, cx| {
28100        editor.handle_input("-", window, cx);
28101    });
28102    cx.run_until_parked();
28103    cx.assert_editor_state(indoc! {"
28104        - Item 1
28105            - Item 1.a
28106            - Item 1.a
28107"});
28108
28109    // Case 7: Test blockquote newline preserves something
28110    cx.set_state(indoc! {"
28111        > Item 1ˇ"
28112    });
28113    cx.update_editor(|editor, window, cx| {
28114        editor.newline(&Newline, window, cx);
28115    });
28116    cx.assert_editor_state(indoc! {"
28117        > Item 1
28118        ˇ"
28119    });
28120}
28121
28122#[gpui::test]
28123async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28124    cx: &mut gpui::TestAppContext,
28125) {
28126    init_test(cx, |_| {});
28127
28128    let url = "https://zed.dev";
28129
28130    let markdown_language = Arc::new(Language::new(
28131        LanguageConfig {
28132            name: "Markdown".into(),
28133            ..LanguageConfig::default()
28134        },
28135        None,
28136    ));
28137
28138    let mut cx = EditorTestContext::new(cx).await;
28139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28140    cx.set_state(&format!(
28141        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28142    ));
28143
28144    cx.update_editor(|editor, window, cx| {
28145        editor.copy(&Copy, window, cx);
28146    });
28147
28148    cx.set_state(&format!(
28149        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28150    ));
28151
28152    cx.update_editor(|editor, window, cx| {
28153        editor.paste(&Paste, window, cx);
28154    });
28155
28156    cx.assert_editor_state(&format!(
28157        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28158    ));
28159}
28160
28161#[gpui::test]
28162async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28163    cx: &mut gpui::TestAppContext,
28164) {
28165    init_test(cx, |_| {});
28166
28167    let url = "https://zed.dev";
28168
28169    let markdown_language = Arc::new(Language::new(
28170        LanguageConfig {
28171            name: "Markdown".into(),
28172            ..LanguageConfig::default()
28173        },
28174        None,
28175    ));
28176
28177    let mut cx = EditorTestContext::new(cx).await;
28178    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28179    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28180
28181    cx.update_editor(|editor, window, cx| {
28182        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28183        editor.paste(&Paste, window, cx);
28184    });
28185
28186    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28187}
28188
28189#[gpui::test]
28190async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28191    cx: &mut gpui::TestAppContext,
28192) {
28193    init_test(cx, |_| {});
28194
28195    let text = "Awesome";
28196
28197    let markdown_language = Arc::new(Language::new(
28198        LanguageConfig {
28199            name: "Markdown".into(),
28200            ..LanguageConfig::default()
28201        },
28202        None,
28203    ));
28204
28205    let mut cx = EditorTestContext::new(cx).await;
28206    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28207    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28208
28209    cx.update_editor(|editor, window, cx| {
28210        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28211        editor.paste(&Paste, window, cx);
28212    });
28213
28214    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28215}
28216
28217#[gpui::test]
28218async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28219    cx: &mut gpui::TestAppContext,
28220) {
28221    init_test(cx, |_| {});
28222
28223    let url = "https://zed.dev";
28224
28225    let markdown_language = Arc::new(Language::new(
28226        LanguageConfig {
28227            name: "Rust".into(),
28228            ..LanguageConfig::default()
28229        },
28230        None,
28231    ));
28232
28233    let mut cx = EditorTestContext::new(cx).await;
28234    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28235    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28236
28237    cx.update_editor(|editor, window, cx| {
28238        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28239        editor.paste(&Paste, window, cx);
28240    });
28241
28242    cx.assert_editor_state(&format!(
28243        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28244    ));
28245}
28246
28247#[gpui::test]
28248async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28249    cx: &mut TestAppContext,
28250) {
28251    init_test(cx, |_| {});
28252
28253    let url = "https://zed.dev";
28254
28255    let markdown_language = Arc::new(Language::new(
28256        LanguageConfig {
28257            name: "Markdown".into(),
28258            ..LanguageConfig::default()
28259        },
28260        None,
28261    ));
28262
28263    let (editor, cx) = cx.add_window_view(|window, cx| {
28264        let multi_buffer = MultiBuffer::build_multi(
28265            [
28266                ("this will embed -> link", vec![Point::row_range(0..1)]),
28267                ("this will replace -> link", vec![Point::row_range(0..1)]),
28268            ],
28269            cx,
28270        );
28271        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28272        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28273            s.select_ranges(vec![
28274                Point::new(0, 19)..Point::new(0, 23),
28275                Point::new(1, 21)..Point::new(1, 25),
28276            ])
28277        });
28278        let first_buffer_id = multi_buffer
28279            .read(cx)
28280            .excerpt_buffer_ids()
28281            .into_iter()
28282            .next()
28283            .unwrap();
28284        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28285        first_buffer.update(cx, |buffer, cx| {
28286            buffer.set_language(Some(markdown_language.clone()), cx);
28287        });
28288
28289        editor
28290    });
28291    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28292
28293    cx.update_editor(|editor, window, cx| {
28294        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28295        editor.paste(&Paste, window, cx);
28296    });
28297
28298    cx.assert_editor_state(&format!(
28299        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28300    ));
28301}
28302
28303#[gpui::test]
28304async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28305    init_test(cx, |_| {});
28306
28307    let fs = FakeFs::new(cx.executor());
28308    fs.insert_tree(
28309        path!("/project"),
28310        json!({
28311            "first.rs": "# First Document\nSome content here.",
28312            "second.rs": "Plain text content for second file.",
28313        }),
28314    )
28315    .await;
28316
28317    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28318    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28319    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28320
28321    let language = rust_lang();
28322    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28323    language_registry.add(language.clone());
28324    let mut fake_servers = language_registry.register_fake_lsp(
28325        "Rust",
28326        FakeLspAdapter {
28327            ..FakeLspAdapter::default()
28328        },
28329    );
28330
28331    let buffer1 = project
28332        .update(cx, |project, cx| {
28333            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28334        })
28335        .await
28336        .unwrap();
28337    let buffer2 = project
28338        .update(cx, |project, cx| {
28339            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28340        })
28341        .await
28342        .unwrap();
28343
28344    let multi_buffer = cx.new(|cx| {
28345        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28346        multi_buffer.set_excerpts_for_path(
28347            PathKey::for_buffer(&buffer1, cx),
28348            buffer1.clone(),
28349            [Point::zero()..buffer1.read(cx).max_point()],
28350            3,
28351            cx,
28352        );
28353        multi_buffer.set_excerpts_for_path(
28354            PathKey::for_buffer(&buffer2, cx),
28355            buffer2.clone(),
28356            [Point::zero()..buffer1.read(cx).max_point()],
28357            3,
28358            cx,
28359        );
28360        multi_buffer
28361    });
28362
28363    let (editor, cx) = cx.add_window_view(|window, cx| {
28364        Editor::new(
28365            EditorMode::full(),
28366            multi_buffer,
28367            Some(project.clone()),
28368            window,
28369            cx,
28370        )
28371    });
28372
28373    let fake_language_server = fake_servers.next().await.unwrap();
28374
28375    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28376
28377    let save = editor.update_in(cx, |editor, window, cx| {
28378        assert!(editor.is_dirty(cx));
28379
28380        editor.save(
28381            SaveOptions {
28382                format: true,
28383                autosave: true,
28384            },
28385            project,
28386            window,
28387            cx,
28388        )
28389    });
28390    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28391    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28392    let mut done_edit_rx = Some(done_edit_rx);
28393    let mut start_edit_tx = Some(start_edit_tx);
28394
28395    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28396        start_edit_tx.take().unwrap().send(()).unwrap();
28397        let done_edit_rx = done_edit_rx.take().unwrap();
28398        async move {
28399            done_edit_rx.await.unwrap();
28400            Ok(None)
28401        }
28402    });
28403
28404    start_edit_rx.await.unwrap();
28405    buffer2
28406        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28407        .unwrap();
28408
28409    done_edit_tx.send(()).unwrap();
28410
28411    save.await.unwrap();
28412    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28413}
28414
28415#[track_caller]
28416fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28417    editor
28418        .all_inlays(cx)
28419        .into_iter()
28420        .filter_map(|inlay| inlay.get_color())
28421        .map(Rgba::from)
28422        .collect()
28423}
28424
28425#[gpui::test]
28426fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28427    init_test(cx, |_| {});
28428
28429    let editor = cx.add_window(|window, cx| {
28430        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28431        build_editor(buffer, window, cx)
28432    });
28433
28434    editor
28435        .update(cx, |editor, window, cx| {
28436            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28437                s.select_display_ranges([
28438                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28439                ])
28440            });
28441
28442            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28443
28444            assert_eq!(
28445                editor.display_text(cx),
28446                "line1\nline2\nline2",
28447                "Duplicating last line upward should create duplicate above, not on same line"
28448            );
28449
28450            assert_eq!(
28451                editor
28452                    .selections
28453                    .display_ranges(&editor.display_snapshot(cx)),
28454                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28455                "Selection should move to the duplicated line"
28456            );
28457        })
28458        .unwrap();
28459}
28460
28461#[gpui::test]
28462async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28463    init_test(cx, |_| {});
28464
28465    let mut cx = EditorTestContext::new(cx).await;
28466
28467    cx.set_state("line1\nline2ˇ");
28468
28469    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28470
28471    let clipboard_text = cx
28472        .read_from_clipboard()
28473        .and_then(|item| item.text().as_deref().map(str::to_string));
28474
28475    assert_eq!(
28476        clipboard_text,
28477        Some("line2\n".to_string()),
28478        "Copying a line without trailing newline should include a newline"
28479    );
28480
28481    cx.set_state("line1\nˇ");
28482
28483    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28484
28485    cx.assert_editor_state("line1\nline2\nˇ");
28486}
28487
28488#[gpui::test]
28489async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28490    init_test(cx, |_| {});
28491
28492    let mut cx = EditorTestContext::new(cx).await;
28493
28494    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28495
28496    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28497
28498    let clipboard_text = cx
28499        .read_from_clipboard()
28500        .and_then(|item| item.text().as_deref().map(str::to_string));
28501
28502    assert_eq!(
28503        clipboard_text,
28504        Some("line1\nline2\nline3\n".to_string()),
28505        "Copying multiple lines should include a single newline between lines"
28506    );
28507
28508    cx.set_state("lineA\nˇ");
28509
28510    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28511
28512    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28513}
28514
28515#[gpui::test]
28516async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28517    init_test(cx, |_| {});
28518
28519    let mut cx = EditorTestContext::new(cx).await;
28520
28521    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28522
28523    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28524
28525    let clipboard_text = cx
28526        .read_from_clipboard()
28527        .and_then(|item| item.text().as_deref().map(str::to_string));
28528
28529    assert_eq!(
28530        clipboard_text,
28531        Some("line1\nline2\nline3\n".to_string()),
28532        "Copying multiple lines should include a single newline between lines"
28533    );
28534
28535    cx.set_state("lineA\nˇ");
28536
28537    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28538
28539    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28540}
28541
28542#[gpui::test]
28543async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28544    init_test(cx, |_| {});
28545
28546    let mut cx = EditorTestContext::new(cx).await;
28547
28548    cx.set_state("line1\nline2ˇ");
28549    cx.update_editor(|e, window, cx| {
28550        e.set_mode(EditorMode::SingleLine);
28551        assert!(e.key_context(window, cx).contains("end_of_input"));
28552    });
28553    cx.set_state("ˇline1\nline2");
28554    cx.update_editor(|e, window, cx| {
28555        assert!(!e.key_context(window, cx).contains("end_of_input"));
28556    });
28557    cx.set_state("line1ˇ\nline2");
28558    cx.update_editor(|e, window, cx| {
28559        assert!(!e.key_context(window, cx).contains("end_of_input"));
28560    });
28561}
28562
28563#[gpui::test]
28564async fn test_sticky_scroll(cx: &mut TestAppContext) {
28565    init_test(cx, |_| {});
28566    let mut cx = EditorTestContext::new(cx).await;
28567
28568    let buffer = indoc! {"
28569            ˇfn foo() {
28570                let abc = 123;
28571            }
28572            struct Bar;
28573            impl Bar {
28574                fn new() -> Self {
28575                    Self
28576                }
28577            }
28578            fn baz() {
28579            }
28580        "};
28581    cx.set_state(&buffer);
28582
28583    cx.update_editor(|e, _, cx| {
28584        e.buffer()
28585            .read(cx)
28586            .as_singleton()
28587            .unwrap()
28588            .update(cx, |buffer, cx| {
28589                buffer.set_language(Some(rust_lang()), cx);
28590            })
28591    });
28592
28593    let mut sticky_headers = |offset: ScrollOffset| {
28594        cx.update_editor(|e, window, cx| {
28595            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28596            let style = e.style(cx).clone();
28597            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28598                .into_iter()
28599                .map(
28600                    |StickyHeader {
28601                         start_point,
28602                         offset,
28603                         ..
28604                     }| { (start_point, offset) },
28605                )
28606                .collect::<Vec<_>>()
28607        })
28608    };
28609
28610    let fn_foo = Point { row: 0, column: 0 };
28611    let impl_bar = Point { row: 4, column: 0 };
28612    let fn_new = Point { row: 5, column: 4 };
28613
28614    assert_eq!(sticky_headers(0.0), vec![]);
28615    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28616    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28617    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28618    assert_eq!(sticky_headers(2.0), vec![]);
28619    assert_eq!(sticky_headers(2.5), vec![]);
28620    assert_eq!(sticky_headers(3.0), vec![]);
28621    assert_eq!(sticky_headers(3.5), vec![]);
28622    assert_eq!(sticky_headers(4.0), vec![]);
28623    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28624    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28625    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28626    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28627    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28628    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28629    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28630    assert_eq!(sticky_headers(8.0), vec![]);
28631    assert_eq!(sticky_headers(8.5), vec![]);
28632    assert_eq!(sticky_headers(9.0), vec![]);
28633    assert_eq!(sticky_headers(9.5), vec![]);
28634    assert_eq!(sticky_headers(10.0), vec![]);
28635}
28636
28637#[gpui::test]
28638fn test_relative_line_numbers(cx: &mut TestAppContext) {
28639    init_test(cx, |_| {});
28640
28641    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
28642    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
28643    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
28644
28645    let multibuffer = cx.new(|cx| {
28646        let mut multibuffer = MultiBuffer::new(ReadWrite);
28647        multibuffer.push_excerpts(
28648            buffer_1.clone(),
28649            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28650            cx,
28651        );
28652        multibuffer.push_excerpts(
28653            buffer_2.clone(),
28654            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28655            cx,
28656        );
28657        multibuffer.push_excerpts(
28658            buffer_3.clone(),
28659            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28660            cx,
28661        );
28662        multibuffer
28663    });
28664
28665    // wrapped contents of multibuffer:
28666    //    aaa
28667    //    aaa
28668    //    aaa
28669    //    a
28670    //    bbb
28671    //
28672    //    ccc
28673    //    ccc
28674    //    ccc
28675    //    c
28676    //    ddd
28677    //
28678    //    eee
28679    //    fff
28680    //    fff
28681    //    fff
28682    //    f
28683
28684    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
28685    editor.update_in(cx, |editor, window, cx| {
28686        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
28687
28688        // includes trailing newlines.
28689        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
28690        let expected_wrapped_line_numbers = [
28691            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
28692        ];
28693
28694        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28695            s.select_ranges([
28696                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
28697            ]);
28698        });
28699
28700        let snapshot = editor.snapshot(window, cx);
28701
28702        // these are all 0-indexed
28703        let base_display_row = DisplayRow(11);
28704        let base_row = 3;
28705        let wrapped_base_row = 7;
28706
28707        // test not counting wrapped lines
28708        let expected_relative_numbers = expected_line_numbers
28709            .into_iter()
28710            .enumerate()
28711            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
28712            .collect_vec();
28713        let actual_relative_numbers = snapshot
28714            .calculate_relative_line_numbers(
28715                &(DisplayRow(0)..DisplayRow(24)),
28716                base_display_row,
28717                false,
28718            )
28719            .into_iter()
28720            .sorted()
28721            .collect_vec();
28722        assert_eq!(expected_relative_numbers, actual_relative_numbers);
28723        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
28724        for (display_row, relative_number) in expected_relative_numbers {
28725            assert_eq!(
28726                relative_number,
28727                snapshot
28728                    .relative_line_delta(display_row, base_display_row, false)
28729                    .unsigned_abs() as u32,
28730            );
28731        }
28732
28733        // test counting wrapped lines
28734        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
28735            .into_iter()
28736            .enumerate()
28737            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
28738            .filter(|(row, _)| *row != base_display_row)
28739            .collect_vec();
28740        let actual_relative_numbers = snapshot
28741            .calculate_relative_line_numbers(
28742                &(DisplayRow(0)..DisplayRow(24)),
28743                base_display_row,
28744                true,
28745            )
28746            .into_iter()
28747            .sorted()
28748            .collect_vec();
28749        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
28750        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
28751        for (display_row, relative_number) in expected_wrapped_relative_numbers {
28752            assert_eq!(
28753                relative_number,
28754                snapshot
28755                    .relative_line_delta(display_row, base_display_row, true)
28756                    .unsigned_abs() as u32,
28757            );
28758        }
28759    });
28760}
28761
28762#[gpui::test]
28763async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28764    init_test(cx, |_| {});
28765    cx.update(|cx| {
28766        SettingsStore::update_global(cx, |store, cx| {
28767            store.update_user_settings(cx, |settings| {
28768                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28769                    enabled: Some(true),
28770                })
28771            });
28772        });
28773    });
28774    let mut cx = EditorTestContext::new(cx).await;
28775
28776    let line_height = cx.update_editor(|editor, window, cx| {
28777        editor
28778            .style(cx)
28779            .text
28780            .line_height_in_pixels(window.rem_size())
28781    });
28782
28783    let buffer = indoc! {"
28784            ˇfn foo() {
28785                let abc = 123;
28786            }
28787            struct Bar;
28788            impl Bar {
28789                fn new() -> Self {
28790                    Self
28791                }
28792            }
28793            fn baz() {
28794            }
28795        "};
28796    cx.set_state(&buffer);
28797
28798    cx.update_editor(|e, _, cx| {
28799        e.buffer()
28800            .read(cx)
28801            .as_singleton()
28802            .unwrap()
28803            .update(cx, |buffer, cx| {
28804                buffer.set_language(Some(rust_lang()), cx);
28805            })
28806    });
28807
28808    let fn_foo = || empty_range(0, 0);
28809    let impl_bar = || empty_range(4, 0);
28810    let fn_new = || empty_range(5, 4);
28811
28812    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28813        cx.update_editor(|e, window, cx| {
28814            e.scroll(
28815                gpui::Point {
28816                    x: 0.,
28817                    y: scroll_offset,
28818                },
28819                None,
28820                window,
28821                cx,
28822            );
28823        });
28824        cx.simulate_click(
28825            gpui::Point {
28826                x: px(0.),
28827                y: click_offset as f32 * line_height,
28828            },
28829            Modifiers::none(),
28830        );
28831        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28832    };
28833
28834    assert_eq!(
28835        scroll_and_click(
28836            4.5, // impl Bar is halfway off the screen
28837            0.0  // click top of screen
28838        ),
28839        // scrolled to impl Bar
28840        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28841    );
28842
28843    assert_eq!(
28844        scroll_and_click(
28845            4.5,  // impl Bar is halfway off the screen
28846            0.25  // click middle of impl Bar
28847        ),
28848        // scrolled to impl Bar
28849        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28850    );
28851
28852    assert_eq!(
28853        scroll_and_click(
28854            4.5, // impl Bar is halfway off the screen
28855            1.5  // click below impl Bar (e.g. fn new())
28856        ),
28857        // scrolled to fn new() - this is below the impl Bar header which has persisted
28858        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28859    );
28860
28861    assert_eq!(
28862        scroll_and_click(
28863            5.5,  // fn new is halfway underneath impl Bar
28864            0.75  // click on the overlap of impl Bar and fn new()
28865        ),
28866        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28867    );
28868
28869    assert_eq!(
28870        scroll_and_click(
28871            5.5,  // fn new is halfway underneath impl Bar
28872            1.25  // click on the visible part of fn new()
28873        ),
28874        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28875    );
28876
28877    assert_eq!(
28878        scroll_and_click(
28879            1.5, // fn foo is halfway off the screen
28880            0.0  // click top of screen
28881        ),
28882        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28883    );
28884
28885    assert_eq!(
28886        scroll_and_click(
28887            1.5,  // fn foo is halfway off the screen
28888            0.75  // click visible part of let abc...
28889        )
28890        .0,
28891        // no change in scroll
28892        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28893        (gpui::Point { x: 0., y: 1.5 })
28894    );
28895}
28896
28897#[gpui::test]
28898async fn test_next_prev_reference(cx: &mut TestAppContext) {
28899    const CYCLE_POSITIONS: &[&'static str] = &[
28900        indoc! {"
28901            fn foo() {
28902                let ˇabc = 123;
28903                let x = abc + 1;
28904                let y = abc + 2;
28905                let z = abc + 2;
28906            }
28907        "},
28908        indoc! {"
28909            fn foo() {
28910                let abc = 123;
28911                let x = ˇabc + 1;
28912                let y = abc + 2;
28913                let z = abc + 2;
28914            }
28915        "},
28916        indoc! {"
28917            fn foo() {
28918                let abc = 123;
28919                let x = abc + 1;
28920                let y = ˇabc + 2;
28921                let z = abc + 2;
28922            }
28923        "},
28924        indoc! {"
28925            fn foo() {
28926                let abc = 123;
28927                let x = abc + 1;
28928                let y = abc + 2;
28929                let z = ˇabc + 2;
28930            }
28931        "},
28932    ];
28933
28934    init_test(cx, |_| {});
28935
28936    let mut cx = EditorLspTestContext::new_rust(
28937        lsp::ServerCapabilities {
28938            references_provider: Some(lsp::OneOf::Left(true)),
28939            ..Default::default()
28940        },
28941        cx,
28942    )
28943    .await;
28944
28945    // importantly, the cursor is in the middle
28946    cx.set_state(indoc! {"
28947        fn foo() {
28948            let aˇbc = 123;
28949            let x = abc + 1;
28950            let y = abc + 2;
28951            let z = abc + 2;
28952        }
28953    "});
28954
28955    let reference_ranges = [
28956        lsp::Position::new(1, 8),
28957        lsp::Position::new(2, 12),
28958        lsp::Position::new(3, 12),
28959        lsp::Position::new(4, 12),
28960    ]
28961    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28962
28963    cx.lsp
28964        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28965            Ok(Some(
28966                reference_ranges
28967                    .map(|range| lsp::Location {
28968                        uri: params.text_document_position.text_document.uri.clone(),
28969                        range,
28970                    })
28971                    .to_vec(),
28972            ))
28973        });
28974
28975    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28976        cx.update_editor(|editor, window, cx| {
28977            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28978        })
28979        .unwrap()
28980        .await
28981        .unwrap()
28982    };
28983
28984    _move(Direction::Next, 1, &mut cx).await;
28985    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28986
28987    _move(Direction::Next, 1, &mut cx).await;
28988    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28989
28990    _move(Direction::Next, 1, &mut cx).await;
28991    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28992
28993    // loops back to the start
28994    _move(Direction::Next, 1, &mut cx).await;
28995    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28996
28997    // loops back to the end
28998    _move(Direction::Prev, 1, &mut cx).await;
28999    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29000
29001    _move(Direction::Prev, 1, &mut cx).await;
29002    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29003
29004    _move(Direction::Prev, 1, &mut cx).await;
29005    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29006
29007    _move(Direction::Prev, 1, &mut cx).await;
29008    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29009
29010    _move(Direction::Next, 3, &mut cx).await;
29011    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29012
29013    _move(Direction::Prev, 2, &mut cx).await;
29014    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29015}
29016
29017#[gpui::test]
29018async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29019    init_test(cx, |_| {});
29020
29021    let (editor, cx) = cx.add_window_view(|window, cx| {
29022        let multi_buffer = MultiBuffer::build_multi(
29023            [
29024                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29025                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29026            ],
29027            cx,
29028        );
29029        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29030    });
29031
29032    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29033    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29034
29035    cx.assert_excerpts_with_selections(indoc! {"
29036        [EXCERPT]
29037        ˇ1
29038        2
29039        3
29040        [EXCERPT]
29041        1
29042        2
29043        3
29044        "});
29045
29046    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29047    cx.update_editor(|editor, window, cx| {
29048        editor.change_selections(None.into(), window, cx, |s| {
29049            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29050        });
29051    });
29052    cx.assert_excerpts_with_selections(indoc! {"
29053        [EXCERPT]
29054        1
2905529056        3
29057        [EXCERPT]
29058        1
29059        2
29060        3
29061        "});
29062
29063    cx.update_editor(|editor, window, cx| {
29064        editor
29065            .select_all_matches(&SelectAllMatches, window, cx)
29066            .unwrap();
29067    });
29068    cx.assert_excerpts_with_selections(indoc! {"
29069        [EXCERPT]
29070        1
2907129072        3
29073        [EXCERPT]
29074        1
2907529076        3
29077        "});
29078
29079    cx.update_editor(|editor, window, cx| {
29080        editor.handle_input("X", window, cx);
29081    });
29082    cx.assert_excerpts_with_selections(indoc! {"
29083        [EXCERPT]
29084        1
2908529086        3
29087        [EXCERPT]
29088        1
2908929090        3
29091        "});
29092
29093    // Scenario 2: Select "2", then fold second buffer before insertion
29094    cx.update_multibuffer(|mb, cx| {
29095        for buffer_id in buffer_ids.iter() {
29096            let buffer = mb.buffer(*buffer_id).unwrap();
29097            buffer.update(cx, |buffer, cx| {
29098                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29099            });
29100        }
29101    });
29102
29103    // Select "2" and select all matches
29104    cx.update_editor(|editor, window, cx| {
29105        editor.change_selections(None.into(), window, cx, |s| {
29106            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29107        });
29108        editor
29109            .select_all_matches(&SelectAllMatches, window, cx)
29110            .unwrap();
29111    });
29112
29113    // Fold second buffer - should remove selections from folded buffer
29114    cx.update_editor(|editor, _, cx| {
29115        editor.fold_buffer(buffer_ids[1], cx);
29116    });
29117    cx.assert_excerpts_with_selections(indoc! {"
29118        [EXCERPT]
29119        1
2912029121        3
29122        [EXCERPT]
29123        [FOLDED]
29124        "});
29125
29126    // Insert text - should only affect first buffer
29127    cx.update_editor(|editor, window, cx| {
29128        editor.handle_input("Y", window, cx);
29129    });
29130    cx.update_editor(|editor, _, cx| {
29131        editor.unfold_buffer(buffer_ids[1], cx);
29132    });
29133    cx.assert_excerpts_with_selections(indoc! {"
29134        [EXCERPT]
29135        1
2913629137        3
29138        [EXCERPT]
29139        1
29140        2
29141        3
29142        "});
29143
29144    // Scenario 3: Select "2", then fold first buffer before insertion
29145    cx.update_multibuffer(|mb, cx| {
29146        for buffer_id in buffer_ids.iter() {
29147            let buffer = mb.buffer(*buffer_id).unwrap();
29148            buffer.update(cx, |buffer, cx| {
29149                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29150            });
29151        }
29152    });
29153
29154    // Select "2" and select all matches
29155    cx.update_editor(|editor, window, cx| {
29156        editor.change_selections(None.into(), window, cx, |s| {
29157            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29158        });
29159        editor
29160            .select_all_matches(&SelectAllMatches, window, cx)
29161            .unwrap();
29162    });
29163
29164    // Fold first buffer - should remove selections from folded buffer
29165    cx.update_editor(|editor, _, cx| {
29166        editor.fold_buffer(buffer_ids[0], cx);
29167    });
29168    cx.assert_excerpts_with_selections(indoc! {"
29169        [EXCERPT]
29170        [FOLDED]
29171        [EXCERPT]
29172        1
2917329174        3
29175        "});
29176
29177    // Insert text - should only affect second buffer
29178    cx.update_editor(|editor, window, cx| {
29179        editor.handle_input("Z", window, cx);
29180    });
29181    cx.update_editor(|editor, _, cx| {
29182        editor.unfold_buffer(buffer_ids[0], cx);
29183    });
29184    cx.assert_excerpts_with_selections(indoc! {"
29185        [EXCERPT]
29186        1
29187        2
29188        3
29189        [EXCERPT]
29190        1
2919129192        3
29193        "});
29194
29195    // Test correct folded header is selected upon fold
29196    cx.update_editor(|editor, _, cx| {
29197        editor.fold_buffer(buffer_ids[0], cx);
29198        editor.fold_buffer(buffer_ids[1], cx);
29199    });
29200    cx.assert_excerpts_with_selections(indoc! {"
29201        [EXCERPT]
29202        [FOLDED]
29203        [EXCERPT]
29204        ˇ[FOLDED]
29205        "});
29206
29207    // Test selection inside folded buffer unfolds it on type
29208    cx.update_editor(|editor, window, cx| {
29209        editor.handle_input("W", window, cx);
29210    });
29211    cx.update_editor(|editor, _, cx| {
29212        editor.unfold_buffer(buffer_ids[0], cx);
29213    });
29214    cx.assert_excerpts_with_selections(indoc! {"
29215        [EXCERPT]
29216        1
29217        2
29218        3
29219        [EXCERPT]
29220        Wˇ1
29221        Z
29222        3
29223        "});
29224}
29225
29226#[gpui::test]
29227async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
29228    init_test(cx, |_| {});
29229    let mut leader_cx = EditorTestContext::new(cx).await;
29230
29231    let diff_base = indoc!(
29232        r#"
29233        one
29234        two
29235        three
29236        four
29237        five
29238        six
29239        "#
29240    );
29241
29242    let initial_state = indoc!(
29243        r#"
29244        ˇone
29245        two
29246        THREE
29247        four
29248        five
29249        six
29250        "#
29251    );
29252
29253    leader_cx.set_state(initial_state);
29254
29255    leader_cx.set_head_text(&diff_base);
29256    leader_cx.run_until_parked();
29257
29258    let follower = leader_cx.update_multibuffer(|leader, cx| {
29259        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29260        leader.set_all_diff_hunks_expanded(cx);
29261        leader.get_or_create_follower(cx)
29262    });
29263    follower.update(cx, |follower, cx| {
29264        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29265        follower.set_all_diff_hunks_expanded(cx);
29266    });
29267
29268    let follower_editor =
29269        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
29270    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
29271
29272    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
29273    cx.run_until_parked();
29274
29275    leader_cx.assert_editor_state(initial_state);
29276    follower_cx.assert_editor_state(indoc! {
29277        r#"
29278        ˇone
29279        two
29280        three
29281        four
29282        five
29283        six
29284        "#
29285    });
29286
29287    follower_cx.editor(|editor, _window, cx| {
29288        assert!(editor.read_only(cx));
29289    });
29290
29291    leader_cx.update_editor(|editor, _window, cx| {
29292        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
29293    });
29294    cx.run_until_parked();
29295
29296    leader_cx.assert_editor_state(indoc! {
29297        r#"
29298        ˇone
29299        two
29300        THREE
29301        four
29302        FIVE
29303        six
29304        "#
29305    });
29306
29307    follower_cx.assert_editor_state(indoc! {
29308        r#"
29309        ˇone
29310        two
29311        three
29312        four
29313        five
29314        six
29315        "#
29316    });
29317
29318    leader_cx.update_editor(|editor, _window, cx| {
29319        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
29320    });
29321    cx.run_until_parked();
29322
29323    leader_cx.assert_editor_state(indoc! {
29324        r#"
29325        ˇone
29326        two
29327        THREE
29328        four
29329        FIVE
29330        six
29331        SEVEN"#
29332    });
29333
29334    follower_cx.assert_editor_state(indoc! {
29335        r#"
29336        ˇone
29337        two
29338        three
29339        four
29340        five
29341        six
29342        "#
29343    });
29344
29345    leader_cx.update_editor(|editor, window, cx| {
29346        editor.move_down(&MoveDown, window, cx);
29347        editor.refresh_selected_text_highlights(true, window, cx);
29348    });
29349    leader_cx.run_until_parked();
29350}
29351
29352#[gpui::test]
29353async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
29354    init_test(cx, |_| {});
29355    let base_text = "base\n";
29356    let buffer_text = "buffer\n";
29357
29358    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
29359    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
29360
29361    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
29362    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
29363    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
29364    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
29365
29366    let leader = cx.new(|cx| {
29367        let mut leader = MultiBuffer::new(Capability::ReadWrite);
29368        leader.set_all_diff_hunks_expanded(cx);
29369        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29370        leader
29371    });
29372    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
29373    follower.update(cx, |follower, _| {
29374        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29375    });
29376
29377    leader.update(cx, |leader, cx| {
29378        leader.insert_excerpts_after(
29379            ExcerptId::min(),
29380            extra_buffer_2.clone(),
29381            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29382            cx,
29383        );
29384        leader.add_diff(extra_diff_2.clone(), cx);
29385
29386        leader.insert_excerpts_after(
29387            ExcerptId::min(),
29388            extra_buffer_1.clone(),
29389            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29390            cx,
29391        );
29392        leader.add_diff(extra_diff_1.clone(), cx);
29393
29394        leader.insert_excerpts_after(
29395            ExcerptId::min(),
29396            buffer1.clone(),
29397            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29398            cx,
29399        );
29400        leader.add_diff(diff1.clone(), cx);
29401    });
29402
29403    cx.run_until_parked();
29404    let mut cx = cx.add_empty_window();
29405
29406    let leader_editor = cx
29407        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29408    let follower_editor = cx.new_window_entity(|window, cx| {
29409        Editor::for_multibuffer(follower.clone(), None, window, cx)
29410    });
29411
29412    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29413    leader_cx.assert_editor_state(indoc! {"
29414       ˇbuffer
29415
29416       dummy text 1
29417
29418       dummy text 2
29419    "});
29420    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29421    follower_cx.assert_editor_state(indoc! {"
29422        ˇbase
29423
29424
29425    "});
29426}
29427
29428#[gpui::test]
29429async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29430    init_test(cx, |_| {});
29431
29432    let (editor, cx) = cx.add_window_view(|window, cx| {
29433        let multi_buffer = MultiBuffer::build_multi(
29434            [
29435                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29436                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29437            ],
29438            cx,
29439        );
29440        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29441    });
29442
29443    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29444
29445    cx.assert_excerpts_with_selections(indoc! {"
29446        [EXCERPT]
29447        ˇ1
29448        2
29449        3
29450        [EXCERPT]
29451        1
29452        2
29453        3
29454        4
29455        5
29456        6
29457        7
29458        8
29459        9
29460        "});
29461
29462    cx.update_editor(|editor, window, cx| {
29463        editor.change_selections(None.into(), window, cx, |s| {
29464            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29465        });
29466    });
29467
29468    cx.assert_excerpts_with_selections(indoc! {"
29469        [EXCERPT]
29470        1
29471        2
29472        3
29473        [EXCERPT]
29474        1
29475        2
29476        3
29477        4
29478        5
29479        6
29480        ˇ7
29481        8
29482        9
29483        "});
29484
29485    cx.update_editor(|editor, _window, cx| {
29486        editor.set_vertical_scroll_margin(0, cx);
29487    });
29488
29489    cx.update_editor(|editor, window, cx| {
29490        assert_eq!(editor.vertical_scroll_margin(), 0);
29491        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29492        assert_eq!(
29493            editor.snapshot(window, cx).scroll_position(),
29494            gpui::Point::new(0., 12.0)
29495        );
29496    });
29497
29498    cx.update_editor(|editor, _window, cx| {
29499        editor.set_vertical_scroll_margin(3, cx);
29500    });
29501
29502    cx.update_editor(|editor, window, cx| {
29503        assert_eq!(editor.vertical_scroll_margin(), 3);
29504        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29505        assert_eq!(
29506            editor.snapshot(window, cx).scroll_position(),
29507            gpui::Point::new(0., 9.0)
29508        );
29509    });
29510}
29511
29512#[gpui::test]
29513async fn test_find_references_single_case(cx: &mut TestAppContext) {
29514    init_test(cx, |_| {});
29515    let mut cx = EditorLspTestContext::new_rust(
29516        lsp::ServerCapabilities {
29517            references_provider: Some(lsp::OneOf::Left(true)),
29518            ..lsp::ServerCapabilities::default()
29519        },
29520        cx,
29521    )
29522    .await;
29523
29524    let before = indoc!(
29525        r#"
29526        fn main() {
29527            let aˇbc = 123;
29528            let xyz = abc;
29529        }
29530        "#
29531    );
29532    let after = indoc!(
29533        r#"
29534        fn main() {
29535            let abc = 123;
29536            let xyz = ˇabc;
29537        }
29538        "#
29539    );
29540
29541    cx.lsp
29542        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29543            Ok(Some(vec![
29544                lsp::Location {
29545                    uri: params.text_document_position.text_document.uri.clone(),
29546                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29547                },
29548                lsp::Location {
29549                    uri: params.text_document_position.text_document.uri,
29550                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29551                },
29552            ]))
29553        });
29554
29555    cx.set_state(before);
29556
29557    let action = FindAllReferences {
29558        always_open_multibuffer: false,
29559    };
29560
29561    let navigated = cx
29562        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29563        .expect("should have spawned a task")
29564        .await
29565        .unwrap();
29566
29567    assert_eq!(navigated, Navigated::No);
29568
29569    cx.run_until_parked();
29570
29571    cx.assert_editor_state(after);
29572}
29573
29574#[gpui::test]
29575async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29576    init_test(cx, |settings| {
29577        settings.defaults.tab_size = Some(2.try_into().unwrap());
29578    });
29579
29580    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29581    let mut cx = EditorTestContext::new(cx).await;
29582    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29583
29584    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29585    cx.set_state(indoc! {"
29586        - [ ] taskˇ
29587    "});
29588    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29589    cx.wait_for_autoindent_applied().await;
29590    cx.assert_editor_state(indoc! {"
29591        - [ ] task
29592        - [ ] ˇ
29593    "});
29594
29595    // Case 2: Works with checked task items too
29596    cx.set_state(indoc! {"
29597        - [x] completed taskˇ
29598    "});
29599    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29600    cx.wait_for_autoindent_applied().await;
29601    cx.assert_editor_state(indoc! {"
29602        - [x] completed task
29603        - [ ] ˇ
29604    "});
29605
29606    // Case 2.1: Works with uppercase checked marker too
29607    cx.set_state(indoc! {"
29608        - [X] completed taskˇ
29609    "});
29610    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29611    cx.wait_for_autoindent_applied().await;
29612    cx.assert_editor_state(indoc! {"
29613        - [X] completed task
29614        - [ ] ˇ
29615    "});
29616
29617    // Case 3: Cursor position doesn't matter - content after marker is what counts
29618    cx.set_state(indoc! {"
29619        - [ ] taˇsk
29620    "});
29621    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29622    cx.wait_for_autoindent_applied().await;
29623    cx.assert_editor_state(indoc! {"
29624        - [ ] ta
29625        - [ ] ˇsk
29626    "});
29627
29628    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29629    cx.set_state(indoc! {"
29630        - [ ]  ˇ
29631    "});
29632    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29633    cx.wait_for_autoindent_applied().await;
29634    cx.assert_editor_state(
29635        indoc! {"
29636        - [ ]$$
29637        ˇ
29638    "}
29639        .replace("$", " ")
29640        .as_str(),
29641    );
29642
29643    // Case 5: Adding newline with content adds marker preserving indentation
29644    cx.set_state(indoc! {"
29645        - [ ] task
29646          - [ ] indentedˇ
29647    "});
29648    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29649    cx.wait_for_autoindent_applied().await;
29650    cx.assert_editor_state(indoc! {"
29651        - [ ] task
29652          - [ ] indented
29653          - [ ] ˇ
29654    "});
29655
29656    // Case 6: Adding newline with cursor right after prefix, unindents
29657    cx.set_state(indoc! {"
29658        - [ ] task
29659          - [ ] sub task
29660            - [ ] ˇ
29661    "});
29662    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29663    cx.wait_for_autoindent_applied().await;
29664    cx.assert_editor_state(indoc! {"
29665        - [ ] task
29666          - [ ] sub task
29667          - [ ] ˇ
29668    "});
29669    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29670    cx.wait_for_autoindent_applied().await;
29671
29672    // Case 7: Adding newline with cursor right after prefix, removes marker
29673    cx.assert_editor_state(indoc! {"
29674        - [ ] task
29675          - [ ] sub task
29676        - [ ] ˇ
29677    "});
29678    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29679    cx.wait_for_autoindent_applied().await;
29680    cx.assert_editor_state(indoc! {"
29681        - [ ] task
29682          - [ ] sub task
29683        ˇ
29684    "});
29685
29686    // Case 8: Cursor before or inside prefix does not add marker
29687    cx.set_state(indoc! {"
29688        ˇ- [ ] task
29689    "});
29690    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29691    cx.wait_for_autoindent_applied().await;
29692    cx.assert_editor_state(indoc! {"
29693
29694        ˇ- [ ] task
29695    "});
29696
29697    cx.set_state(indoc! {"
29698        - [ˇ ] task
29699    "});
29700    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29701    cx.wait_for_autoindent_applied().await;
29702    cx.assert_editor_state(indoc! {"
29703        - [
29704        ˇ
29705        ] task
29706    "});
29707}
29708
29709#[gpui::test]
29710async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29711    init_test(cx, |settings| {
29712        settings.defaults.tab_size = Some(2.try_into().unwrap());
29713    });
29714
29715    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29716    let mut cx = EditorTestContext::new(cx).await;
29717    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29718
29719    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29720    cx.set_state(indoc! {"
29721        - itemˇ
29722    "});
29723    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29724    cx.wait_for_autoindent_applied().await;
29725    cx.assert_editor_state(indoc! {"
29726        - item
29727        - ˇ
29728    "});
29729
29730    // Case 2: Works with different markers
29731    cx.set_state(indoc! {"
29732        * starred itemˇ
29733    "});
29734    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29735    cx.wait_for_autoindent_applied().await;
29736    cx.assert_editor_state(indoc! {"
29737        * starred item
29738        * ˇ
29739    "});
29740
29741    cx.set_state(indoc! {"
29742        + plus itemˇ
29743    "});
29744    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29745    cx.wait_for_autoindent_applied().await;
29746    cx.assert_editor_state(indoc! {"
29747        + plus item
29748        + ˇ
29749    "});
29750
29751    // Case 3: Cursor position doesn't matter - content after marker is what counts
29752    cx.set_state(indoc! {"
29753        - itˇem
29754    "});
29755    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29756    cx.wait_for_autoindent_applied().await;
29757    cx.assert_editor_state(indoc! {"
29758        - it
29759        - ˇem
29760    "});
29761
29762    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29763    cx.set_state(indoc! {"
29764        -  ˇ
29765    "});
29766    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29767    cx.wait_for_autoindent_applied().await;
29768    cx.assert_editor_state(
29769        indoc! {"
29770        - $
29771        ˇ
29772    "}
29773        .replace("$", " ")
29774        .as_str(),
29775    );
29776
29777    // Case 5: Adding newline with content adds marker preserving indentation
29778    cx.set_state(indoc! {"
29779        - item
29780          - indentedˇ
29781    "});
29782    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29783    cx.wait_for_autoindent_applied().await;
29784    cx.assert_editor_state(indoc! {"
29785        - item
29786          - indented
29787          - ˇ
29788    "});
29789
29790    // Case 6: Adding newline with cursor right after marker, unindents
29791    cx.set_state(indoc! {"
29792        - item
29793          - sub item
29794            - ˇ
29795    "});
29796    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29797    cx.wait_for_autoindent_applied().await;
29798    cx.assert_editor_state(indoc! {"
29799        - item
29800          - sub item
29801          - ˇ
29802    "});
29803    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29804    cx.wait_for_autoindent_applied().await;
29805
29806    // Case 7: Adding newline with cursor right after marker, removes marker
29807    cx.assert_editor_state(indoc! {"
29808        - item
29809          - sub item
29810        - ˇ
29811    "});
29812    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29813    cx.wait_for_autoindent_applied().await;
29814    cx.assert_editor_state(indoc! {"
29815        - item
29816          - sub item
29817        ˇ
29818    "});
29819
29820    // Case 8: Cursor before or inside prefix does not add marker
29821    cx.set_state(indoc! {"
29822        ˇ- item
29823    "});
29824    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29825    cx.wait_for_autoindent_applied().await;
29826    cx.assert_editor_state(indoc! {"
29827
29828        ˇ- item
29829    "});
29830
29831    cx.set_state(indoc! {"
29832        -ˇ item
29833    "});
29834    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29835    cx.wait_for_autoindent_applied().await;
29836    cx.assert_editor_state(indoc! {"
29837        -
29838        ˇitem
29839    "});
29840}
29841
29842#[gpui::test]
29843async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
29844    init_test(cx, |settings| {
29845        settings.defaults.tab_size = Some(2.try_into().unwrap());
29846    });
29847
29848    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29849    let mut cx = EditorTestContext::new(cx).await;
29850    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29851
29852    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29853    cx.set_state(indoc! {"
29854        1. first itemˇ
29855    "});
29856    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29857    cx.wait_for_autoindent_applied().await;
29858    cx.assert_editor_state(indoc! {"
29859        1. first item
29860        2. ˇ
29861    "});
29862
29863    // Case 2: Works with larger numbers
29864    cx.set_state(indoc! {"
29865        10. tenth itemˇ
29866    "});
29867    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29868    cx.wait_for_autoindent_applied().await;
29869    cx.assert_editor_state(indoc! {"
29870        10. tenth item
29871        11. ˇ
29872    "});
29873
29874    // Case 3: Cursor position doesn't matter - content after marker is what counts
29875    cx.set_state(indoc! {"
29876        1. itˇem
29877    "});
29878    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29879    cx.wait_for_autoindent_applied().await;
29880    cx.assert_editor_state(indoc! {"
29881        1. it
29882        2. ˇem
29883    "});
29884
29885    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29886    cx.set_state(indoc! {"
29887        1.  ˇ
29888    "});
29889    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29890    cx.wait_for_autoindent_applied().await;
29891    cx.assert_editor_state(
29892        indoc! {"
29893        1. $
29894        ˇ
29895    "}
29896        .replace("$", " ")
29897        .as_str(),
29898    );
29899
29900    // Case 5: Adding newline with content adds marker preserving indentation
29901    cx.set_state(indoc! {"
29902        1. item
29903          2. indentedˇ
29904    "});
29905    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29906    cx.wait_for_autoindent_applied().await;
29907    cx.assert_editor_state(indoc! {"
29908        1. item
29909          2. indented
29910          3. ˇ
29911    "});
29912
29913    // Case 6: Adding newline with cursor right after marker, unindents
29914    cx.set_state(indoc! {"
29915        1. item
29916          2. sub item
29917            3. ˇ
29918    "});
29919    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29920    cx.wait_for_autoindent_applied().await;
29921    cx.assert_editor_state(indoc! {"
29922        1. item
29923          2. sub item
29924          1. ˇ
29925    "});
29926    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29927    cx.wait_for_autoindent_applied().await;
29928
29929    // Case 7: Adding newline with cursor right after marker, removes marker
29930    cx.assert_editor_state(indoc! {"
29931        1. item
29932          2. sub item
29933        1. ˇ
29934    "});
29935    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29936    cx.wait_for_autoindent_applied().await;
29937    cx.assert_editor_state(indoc! {"
29938        1. item
29939          2. sub item
29940        ˇ
29941    "});
29942
29943    // Case 8: Cursor before or inside prefix does not add marker
29944    cx.set_state(indoc! {"
29945        ˇ1. item
29946    "});
29947    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29948    cx.wait_for_autoindent_applied().await;
29949    cx.assert_editor_state(indoc! {"
29950
29951        ˇ1. item
29952    "});
29953
29954    cx.set_state(indoc! {"
29955        1ˇ. item
29956    "});
29957    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29958    cx.wait_for_autoindent_applied().await;
29959    cx.assert_editor_state(indoc! {"
29960        1
29961        ˇ. item
29962    "});
29963}
29964
29965#[gpui::test]
29966async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
29967    init_test(cx, |settings| {
29968        settings.defaults.tab_size = Some(2.try_into().unwrap());
29969    });
29970
29971    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29972    let mut cx = EditorTestContext::new(cx).await;
29973    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29974
29975    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29976    cx.set_state(indoc! {"
29977        1. first item
29978          1. sub first item
29979          2. sub second item
29980          3. ˇ
29981    "});
29982    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29983    cx.wait_for_autoindent_applied().await;
29984    cx.assert_editor_state(indoc! {"
29985        1. first item
29986          1. sub first item
29987          2. sub second item
29988        1. ˇ
29989    "});
29990}
29991
29992#[gpui::test]
29993async fn test_tab_list_indent(cx: &mut TestAppContext) {
29994    init_test(cx, |settings| {
29995        settings.defaults.tab_size = Some(2.try_into().unwrap());
29996    });
29997
29998    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29999    let mut cx = EditorTestContext::new(cx).await;
30000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30001
30002    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30003    cx.set_state(indoc! {"
30004        - ˇitem
30005    "});
30006    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30007    cx.wait_for_autoindent_applied().await;
30008    let expected = indoc! {"
30009        $$- ˇitem
30010    "};
30011    cx.assert_editor_state(expected.replace("$", " ").as_str());
30012
30013    // Case 2: Task list - cursor after prefix
30014    cx.set_state(indoc! {"
30015        - [ ] ˇtask
30016    "});
30017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30018    cx.wait_for_autoindent_applied().await;
30019    let expected = indoc! {"
30020        $$- [ ] ˇtask
30021    "};
30022    cx.assert_editor_state(expected.replace("$", " ").as_str());
30023
30024    // Case 3: Ordered list - cursor after prefix
30025    cx.set_state(indoc! {"
30026        1. ˇfirst
30027    "});
30028    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30029    cx.wait_for_autoindent_applied().await;
30030    let expected = indoc! {"
30031        $$1. ˇfirst
30032    "};
30033    cx.assert_editor_state(expected.replace("$", " ").as_str());
30034
30035    // Case 4: With existing indentation - adds more indent
30036    let initial = indoc! {"
30037        $$- ˇitem
30038    "};
30039    cx.set_state(initial.replace("$", " ").as_str());
30040    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30041    cx.wait_for_autoindent_applied().await;
30042    let expected = indoc! {"
30043        $$$$- ˇitem
30044    "};
30045    cx.assert_editor_state(expected.replace("$", " ").as_str());
30046
30047    // Case 5: Empty list item
30048    cx.set_state(indoc! {"
30049        - ˇ
30050    "});
30051    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30052    cx.wait_for_autoindent_applied().await;
30053    let expected = indoc! {"
30054        $$- ˇ
30055    "};
30056    cx.assert_editor_state(expected.replace("$", " ").as_str());
30057
30058    // Case 6: Cursor at end of line with content
30059    cx.set_state(indoc! {"
30060        - itemˇ
30061    "});
30062    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30063    cx.wait_for_autoindent_applied().await;
30064    let expected = indoc! {"
30065        $$- itemˇ
30066    "};
30067    cx.assert_editor_state(expected.replace("$", " ").as_str());
30068
30069    // Case 7: Cursor at start of list item, indents it
30070    cx.set_state(indoc! {"
30071        - item
30072        ˇ  - sub item
30073    "});
30074    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30075    cx.wait_for_autoindent_applied().await;
30076    let expected = indoc! {"
30077        - item
30078          ˇ  - sub item
30079    "};
30080    cx.assert_editor_state(expected);
30081
30082    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30083    cx.update_editor(|_, _, cx| {
30084        SettingsStore::update_global(cx, |store, cx| {
30085            store.update_user_settings(cx, |settings| {
30086                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30087            });
30088        });
30089    });
30090    cx.set_state(indoc! {"
30091        - item
30092        ˇ  - sub item
30093    "});
30094    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30095    cx.wait_for_autoindent_applied().await;
30096    let expected = indoc! {"
30097        - item
30098          ˇ- sub item
30099    "};
30100    cx.assert_editor_state(expected);
30101}
30102
30103#[gpui::test]
30104async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30105    init_test(cx, |_| {});
30106    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
30107
30108    cx.update(|cx| {
30109        SettingsStore::update_global(cx, |store, cx| {
30110            store.update_user_settings(cx, |settings| {
30111                settings.project.all_languages.defaults.inlay_hints =
30112                    Some(InlayHintSettingsContent {
30113                        enabled: Some(true),
30114                        ..InlayHintSettingsContent::default()
30115                    });
30116            });
30117        });
30118    });
30119
30120    let fs = FakeFs::new(cx.executor());
30121    fs.insert_tree(
30122        path!("/project"),
30123        json!({
30124            ".zed": {
30125                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30126            },
30127            "main.rs": "fn main() {}"
30128        }),
30129    )
30130    .await;
30131
30132    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30133    let server_name = "override-rust-analyzer";
30134    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30135
30136    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30137    language_registry.add(rust_lang());
30138
30139    let capabilities = lsp::ServerCapabilities {
30140        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30141        ..lsp::ServerCapabilities::default()
30142    };
30143    let mut fake_language_servers = language_registry.register_fake_lsp(
30144        "Rust",
30145        FakeLspAdapter {
30146            name: server_name,
30147            capabilities,
30148            initializer: Some(Box::new({
30149                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30150                move |fake_server| {
30151                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30152                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30153                        move |_params, _| {
30154                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30155                            async move {
30156                                Ok(Some(vec![lsp::InlayHint {
30157                                    position: lsp::Position::new(0, 0),
30158                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30159                                    kind: None,
30160                                    text_edits: None,
30161                                    tooltip: None,
30162                                    padding_left: None,
30163                                    padding_right: None,
30164                                    data: None,
30165                                }]))
30166                            }
30167                        },
30168                    );
30169                }
30170            })),
30171            ..FakeLspAdapter::default()
30172        },
30173    );
30174
30175    cx.run_until_parked();
30176
30177    let worktree_id = project.read_with(cx, |project, cx| {
30178        project
30179            .worktrees(cx)
30180            .next()
30181            .map(|wt| wt.read(cx).id())
30182            .expect("should have a worktree")
30183    });
30184
30185    let trusted_worktrees =
30186        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30187
30188    let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
30189    assert!(!can_trust, "worktree should be restricted initially");
30190
30191    let buffer_before_approval = project
30192        .update(cx, |project, cx| {
30193            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30194        })
30195        .await
30196        .unwrap();
30197
30198    let (editor, cx) = cx.add_window_view(|window, cx| {
30199        Editor::new(
30200            EditorMode::full(),
30201            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30202            Some(project.clone()),
30203            window,
30204            cx,
30205        )
30206    });
30207    cx.run_until_parked();
30208    let fake_language_server = fake_language_servers.next();
30209
30210    cx.read(|cx| {
30211        let file = buffer_before_approval.read(cx).file();
30212        assert_eq!(
30213            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30214                .language_servers,
30215            ["...".to_string()],
30216            "local .zed/settings.json must not apply before trust approval"
30217        )
30218    });
30219
30220    editor.update_in(cx, |editor, window, cx| {
30221        editor.handle_input("1", window, cx);
30222    });
30223    cx.run_until_parked();
30224    cx.executor()
30225        .advance_clock(std::time::Duration::from_secs(1));
30226    assert_eq!(
30227        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30228        0,
30229        "inlay hints must not be queried before trust approval"
30230    );
30231
30232    trusted_worktrees.update(cx, |store, cx| {
30233        store.trust(
30234            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30235            None,
30236            cx,
30237        );
30238    });
30239    cx.run_until_parked();
30240
30241    cx.read(|cx| {
30242        let file = buffer_before_approval.read(cx).file();
30243        assert_eq!(
30244            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30245                .language_servers,
30246            ["override-rust-analyzer".to_string()],
30247            "local .zed/settings.json should apply after trust approval"
30248        )
30249    });
30250    let _fake_language_server = fake_language_server.await.unwrap();
30251    editor.update_in(cx, |editor, window, cx| {
30252        editor.handle_input("1", window, cx);
30253    });
30254    cx.run_until_parked();
30255    cx.executor()
30256        .advance_clock(std::time::Duration::from_secs(1));
30257    assert!(
30258        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30259        "inlay hints should be queried after trust approval"
30260    );
30261
30262    let can_trust_after =
30263        trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
30264    assert!(can_trust_after, "worktree should be trusted after trust()");
30265}
30266
30267#[gpui::test]
30268fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30269    // This test reproduces a bug where drawing an editor at a position above the viewport
30270    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30271    // causes an infinite loop in blocks_in_range.
30272    //
30273    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30274    // the content mask intersection produces visible_bounds with origin at the viewport top.
30275    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30276    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30277    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30278    init_test(cx, |_| {});
30279
30280    let window = cx.add_window(|_, _| gpui::Empty);
30281    let mut cx = VisualTestContext::from_window(*window, cx);
30282
30283    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30284    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30285
30286    // Simulate a small viewport (500x500 pixels at origin 0,0)
30287    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30288
30289    // Draw the editor at a very negative Y position, simulating an editor that's been
30290    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30291    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30292    // This should NOT hang - it should just render nothing.
30293    cx.draw(
30294        gpui::point(px(0.), px(-10000.)),
30295        gpui::size(px(500.), px(3000.)),
30296        |_, _| editor.clone(),
30297    );
30298
30299    // If we get here without hanging, the test passes
30300}