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, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  859    let pane = workspace
  860        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  861        .unwrap();
  862
  863    _ = workspace.update(cx, |_v, window, cx| {
  864        cx.new(|cx| {
  865            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  866            let mut editor = build_editor(buffer, window, cx);
  867            let handle = cx.entity();
  868            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  869
  870            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  871                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  872            }
  873
  874            // Move the cursor a small distance.
  875            // Nothing is added to the navigation history.
  876            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  877                s.select_display_ranges([
  878                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  879                ])
  880            });
  881            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  882                s.select_display_ranges([
  883                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  884                ])
  885            });
  886            assert!(pop_history(&mut editor, cx).is_none());
  887
  888            // Move the cursor a large distance.
  889            // The history can jump back to the previous position.
  890            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  891                s.select_display_ranges([
  892                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  893                ])
  894            });
  895            let nav_entry = pop_history(&mut editor, cx).unwrap();
  896            editor.navigate(nav_entry.data.unwrap(), window, cx);
  897            assert_eq!(nav_entry.item.id(), cx.entity_id());
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  903            );
  904            assert!(pop_history(&mut editor, cx).is_none());
  905
  906            // Move the cursor a small distance via the mouse.
  907            // Nothing is added to the navigation history.
  908            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  909            editor.end_selection(window, cx);
  910            assert_eq!(
  911                editor
  912                    .selections
  913                    .display_ranges(&editor.display_snapshot(cx)),
  914                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  915            );
  916            assert!(pop_history(&mut editor, cx).is_none());
  917
  918            // Move the cursor a large distance via the mouse.
  919            // The history can jump back to the previous position.
  920            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  921            editor.end_selection(window, cx);
  922            assert_eq!(
  923                editor
  924                    .selections
  925                    .display_ranges(&editor.display_snapshot(cx)),
  926                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  927            );
  928            let nav_entry = pop_history(&mut editor, cx).unwrap();
  929            editor.navigate(nav_entry.data.unwrap(), window, cx);
  930            assert_eq!(nav_entry.item.id(), cx.entity_id());
  931            assert_eq!(
  932                editor
  933                    .selections
  934                    .display_ranges(&editor.display_snapshot(cx)),
  935                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  936            );
  937            assert!(pop_history(&mut editor, cx).is_none());
  938
  939            // Set scroll position to check later
  940            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  941            let original_scroll_position = editor.scroll_manager.anchor();
  942
  943            // Jump to the end of the document and adjust scroll
  944            editor.move_to_end(&MoveToEnd, window, cx);
  945            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  946            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  947
  948            let nav_entry = pop_history(&mut editor, cx).unwrap();
  949            editor.navigate(nav_entry.data.unwrap(), window, cx);
  950            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  951
  952            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  953            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  954            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  955            let invalid_point = Point::new(9999, 0);
  956            editor.navigate(
  957                Box::new(NavigationData {
  958                    cursor_anchor: invalid_anchor,
  959                    cursor_position: invalid_point,
  960                    scroll_anchor: ScrollAnchor {
  961                        anchor: invalid_anchor,
  962                        offset: Default::default(),
  963                    },
  964                    scroll_top_row: invalid_point.row,
  965                }),
  966                window,
  967                cx,
  968            );
  969            assert_eq!(
  970                editor
  971                    .selections
  972                    .display_ranges(&editor.display_snapshot(cx)),
  973                &[editor.max_point(cx)..editor.max_point(cx)]
  974            );
  975            assert_eq!(
  976                editor.scroll_position(cx),
  977                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  978            );
  979
  980            editor
  981        })
  982    });
  983}
  984
  985#[gpui::test]
  986fn test_cancel(cx: &mut TestAppContext) {
  987    init_test(cx, |_| {});
  988
  989    let editor = cx.add_window(|window, cx| {
  990        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  991        build_editor(buffer, window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  996        editor.update_selection(
  997            DisplayPoint::new(DisplayRow(1), 1),
  998            0,
  999            gpui::Point::<f32>::default(),
 1000            window,
 1001            cx,
 1002        );
 1003        editor.end_selection(window, cx);
 1004
 1005        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1006        editor.update_selection(
 1007            DisplayPoint::new(DisplayRow(0), 3),
 1008            0,
 1009            gpui::Point::<f32>::default(),
 1010            window,
 1011            cx,
 1012        );
 1013        editor.end_selection(window, cx);
 1014        assert_eq!(
 1015            display_ranges(editor, cx),
 1016            [
 1017                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1018                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1019            ]
 1020        );
 1021    });
 1022
 1023    _ = editor.update(cx, |editor, window, cx| {
 1024        editor.cancel(&Cancel, window, cx);
 1025        assert_eq!(
 1026            display_ranges(editor, cx),
 1027            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1028        );
 1029    });
 1030
 1031    _ = editor.update(cx, |editor, window, cx| {
 1032        editor.cancel(&Cancel, window, cx);
 1033        assert_eq!(
 1034            display_ranges(editor, cx),
 1035            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                impl Foo {
 1048                    // Hello!
 1049
 1050                    fn a() {
 1051                        1
 1052                    }
 1053
 1054                    fn b() {
 1055                        2
 1056                    }
 1057
 1058                    fn c() {
 1059                        3
 1060                    }
 1061                }
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer, window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                impl Foo {
 1080                    // Hello!
 1081
 1082                    fn a() {
 1083                        1
 1084                    }
 1085
 1086                    fn b() {⋯
 1087                    }
 1088
 1089                    fn c() {⋯
 1090                    }
 1091                }
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                impl Foo {⋯
 1101                }
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                impl Foo {
 1111                    // Hello!
 1112
 1113                    fn a() {
 1114                        1
 1115                    }
 1116
 1117                    fn b() {⋯
 1118                    }
 1119
 1120                    fn c() {⋯
 1121                    }
 1122                }
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151                    def c():
 1152                        print(3)
 1153            "
 1154            .unindent(),
 1155            cx,
 1156        );
 1157        build_editor(buffer, window, cx)
 1158    });
 1159
 1160    _ = editor.update(cx, |editor, window, cx| {
 1161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1162            s.select_display_ranges([
 1163                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1164            ]);
 1165        });
 1166        editor.fold(&Fold, window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():⋯
 1177
 1178                    def c():⋯
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:⋯
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.unfold_lines(&UnfoldLines, window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:
 1197                    # Hello!
 1198
 1199                    def a():
 1200                        print(1)
 1201
 1202                    def b():⋯
 1203
 1204                    def c():⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            editor.buffer.read(cx).read(cx).text()
 1213        );
 1214    });
 1215}
 1216
 1217#[gpui::test]
 1218fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1219    init_test(cx, |_| {});
 1220
 1221    let editor = cx.add_window(|window, cx| {
 1222        let buffer = MultiBuffer::build_simple(
 1223            &"
 1224                class Foo:
 1225                    # Hello!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234                    def c():
 1235                        print(3)
 1236
 1237
 1238            "
 1239            .unindent(),
 1240            cx,
 1241        );
 1242        build_editor(buffer, window, cx)
 1243    });
 1244
 1245    _ = editor.update(cx, |editor, window, cx| {
 1246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1247            s.select_display_ranges([
 1248                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1249            ]);
 1250        });
 1251        editor.fold(&Fold, window, cx);
 1252        assert_eq!(
 1253            editor.display_text(cx),
 1254            "
 1255                class Foo:
 1256                    # Hello!
 1257
 1258                    def a():
 1259                        print(1)
 1260
 1261                    def b():⋯
 1262
 1263
 1264                    def c():⋯
 1265
 1266
 1267            "
 1268            .unindent(),
 1269        );
 1270
 1271        editor.fold(&Fold, window, cx);
 1272        assert_eq!(
 1273            editor.display_text(cx),
 1274            "
 1275                class Foo:⋯
 1276
 1277
 1278            "
 1279            .unindent(),
 1280        );
 1281
 1282        editor.unfold_lines(&UnfoldLines, window, cx);
 1283        assert_eq!(
 1284            editor.display_text(cx),
 1285            "
 1286                class Foo:
 1287                    # Hello!
 1288
 1289                    def a():
 1290                        print(1)
 1291
 1292                    def b():⋯
 1293
 1294
 1295                    def c():⋯
 1296
 1297
 1298            "
 1299            .unindent(),
 1300        );
 1301
 1302        editor.unfold_lines(&UnfoldLines, window, cx);
 1303        assert_eq!(
 1304            editor.display_text(cx),
 1305            editor.buffer.read(cx).read(cx).text()
 1306        );
 1307    });
 1308}
 1309
 1310#[gpui::test]
 1311fn test_fold_at_level(cx: &mut TestAppContext) {
 1312    init_test(cx, |_| {});
 1313
 1314    let editor = cx.add_window(|window, cx| {
 1315        let buffer = MultiBuffer::build_simple(
 1316            &"
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339            cx,
 1340        );
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:
 1350                    # Hello!
 1351
 1352                    def a():⋯
 1353
 1354                    def b():⋯
 1355
 1356
 1357                class Bar:
 1358                    # World!
 1359
 1360                    def a():⋯
 1361
 1362                    def b():⋯
 1363
 1364
 1365            "
 1366            .unindent(),
 1367        );
 1368
 1369        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1370        assert_eq!(
 1371            editor.display_text(cx),
 1372            "
 1373                class Foo:⋯
 1374
 1375
 1376                class Bar:⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.unfold_all(&UnfoldAll, window, cx);
 1384        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1385        assert_eq!(
 1386            editor.display_text(cx),
 1387            "
 1388                class Foo:
 1389                    # Hello!
 1390
 1391                    def a():
 1392                        print(1)
 1393
 1394                    def b():
 1395                        print(2)
 1396
 1397
 1398                class Bar:
 1399                    # World!
 1400
 1401                    def a():
 1402                        print(1)
 1403
 1404                    def b():
 1405                        print(2)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410        );
 1411
 1412        assert_eq!(
 1413            editor.display_text(cx),
 1414            editor.buffer.read(cx).read(cx).text()
 1415        );
 1416        let (_, positions) = marked_text_ranges(
 1417            &"
 1418                       class Foo:
 1419                           # Hello!
 1420
 1421                           def a():
 1422                              print(1)
 1423
 1424                           def b():
 1425                               p«riˇ»nt(2)
 1426
 1427
 1428                       class Bar:
 1429                           # World!
 1430
 1431                           def a():
 1432                               «ˇprint(1)
 1433
 1434                           def b():
 1435                               print(2)»
 1436
 1437
 1438                   "
 1439            .unindent(),
 1440            true,
 1441        );
 1442
 1443        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1444            s.select_ranges(
 1445                positions
 1446                    .iter()
 1447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1448            )
 1449        });
 1450
 1451        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1452        assert_eq!(
 1453            editor.display_text(cx),
 1454            "
 1455                class Foo:
 1456                    # Hello!
 1457
 1458                    def a():⋯
 1459
 1460                    def b():
 1461                        print(2)
 1462
 1463
 1464                class Bar:
 1465                    # World!
 1466
 1467                    def a():
 1468                        print(1)
 1469
 1470                    def b():
 1471                        print(2)
 1472
 1473
 1474            "
 1475            .unindent(),
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481fn test_move_cursor(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1485    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1486
 1487    buffer.update(cx, |buffer, cx| {
 1488        buffer.edit(
 1489            vec![
 1490                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1491                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1492            ],
 1493            None,
 1494            cx,
 1495        );
 1496    });
 1497    _ = editor.update(cx, |editor, window, cx| {
 1498        assert_eq!(
 1499            display_ranges(editor, cx),
 1500            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            display_ranges(editor, cx),
 1506            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1507        );
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            display_ranges(editor, cx),
 1512            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1513        );
 1514
 1515        editor.move_left(&MoveLeft, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1525        );
 1526
 1527        editor.move_to_end(&MoveToEnd, window, cx);
 1528        assert_eq!(
 1529            display_ranges(editor, cx),
 1530            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1531        );
 1532
 1533        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1534        assert_eq!(
 1535            display_ranges(editor, cx),
 1536            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1537        );
 1538
 1539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1542            ]);
 1543        });
 1544        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1548        );
 1549
 1550        editor.select_to_end(&SelectToEnd, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1554        );
 1555    });
 1556}
 1557
 1558#[gpui::test]
 1559fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1560    init_test(cx, |_| {});
 1561
 1562    let editor = cx.add_window(|window, cx| {
 1563        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1564        build_editor(buffer, window, cx)
 1565    });
 1566
 1567    assert_eq!('🟥'.len_utf8(), 4);
 1568    assert_eq!('α'.len_utf8(), 2);
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.fold_creases(
 1572            vec![
 1573                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1576            ],
 1577            true,
 1578            window,
 1579            cx,
 1580        );
 1581        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1582
 1583        editor.move_right(&MoveRight, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1585        editor.move_right(&MoveRight, window, cx);
 1586        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1587        editor.move_right(&MoveRight, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1589
 1590        editor.move_down(&MoveDown, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1594        editor.move_left(&MoveLeft, window, cx);
 1595        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1596        editor.move_left(&MoveLeft, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1598
 1599        editor.move_down(&MoveDown, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1605        editor.move_right(&MoveRight, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1610        editor.move_down(&MoveDown, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1614
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1617        editor.move_left(&MoveLeft, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1621    });
 1622}
 1623
 1624#[gpui::test]
 1625fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1626    init_test(cx, |_| {});
 1627
 1628    let editor = cx.add_window(|window, cx| {
 1629        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1630        build_editor(buffer, window, cx)
 1631    });
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1634            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1635        });
 1636
 1637        // moving above start of document should move selection to start of document,
 1638        // but the next move down should still be at the original goal_x
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1650
 1651        editor.move_down(&MoveDown, window, cx);
 1652        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1653
 1654        // moving past end of document should not change goal_x
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_up(&MoveUp, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1669    });
 1670}
 1671
 1672#[gpui::test]
 1673fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1674    init_test(cx, |_| {});
 1675    let move_to_beg = MoveToBeginningOfLine {
 1676        stop_at_soft_wraps: true,
 1677        stop_at_indent: true,
 1678    };
 1679
 1680    let delete_to_beg = DeleteToBeginningOfLine {
 1681        stop_at_indent: false,
 1682    };
 1683
 1684    let move_to_end = MoveToEndOfLine {
 1685        stop_at_soft_wraps: true,
 1686    };
 1687
 1688    let editor = cx.add_window(|window, cx| {
 1689        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1690        build_editor(buffer, window, cx)
 1691    });
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1694            s.select_display_ranges([
 1695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1696                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1697            ]);
 1698        });
 1699    });
 1700
 1701    _ = editor.update(cx, |editor, window, cx| {
 1702        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1703        assert_eq!(
 1704            display_ranges(editor, cx),
 1705            &[
 1706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1708            ]
 1709        );
 1710    });
 1711
 1712    _ = editor.update(cx, |editor, window, cx| {
 1713        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1714        assert_eq!(
 1715            display_ranges(editor, cx),
 1716            &[
 1717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1719            ]
 1720        );
 1721    });
 1722
 1723    _ = editor.update(cx, |editor, window, cx| {
 1724        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1725        assert_eq!(
 1726            display_ranges(editor, cx),
 1727            &[
 1728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1729                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1730            ]
 1731        );
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.move_to_end_of_line(&move_to_end, window, cx);
 1736        assert_eq!(
 1737            display_ranges(editor, cx),
 1738            &[
 1739                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1740                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1741            ]
 1742        );
 1743    });
 1744
 1745    // Moving to the end of line again is a no-op.
 1746    _ = editor.update(cx, |editor, window, cx| {
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            display_ranges(editor, cx),
 1750            &[
 1751                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1752                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1753            ]
 1754        );
 1755    });
 1756
 1757    _ = editor.update(cx, |editor, window, cx| {
 1758        editor.move_left(&MoveLeft, window, cx);
 1759        editor.select_to_beginning_of_line(
 1760            &SelectToBeginningOfLine {
 1761                stop_at_soft_wraps: true,
 1762                stop_at_indent: true,
 1763            },
 1764            window,
 1765            cx,
 1766        );
 1767        assert_eq!(
 1768            display_ranges(editor, cx),
 1769            &[
 1770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1771                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1772            ]
 1773        );
 1774    });
 1775
 1776    _ = editor.update(cx, |editor, window, cx| {
 1777        editor.select_to_beginning_of_line(
 1778            &SelectToBeginningOfLine {
 1779                stop_at_soft_wraps: true,
 1780                stop_at_indent: true,
 1781            },
 1782            window,
 1783            cx,
 1784        );
 1785        assert_eq!(
 1786            display_ranges(editor, cx),
 1787            &[
 1788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1789                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1790            ]
 1791        );
 1792    });
 1793
 1794    _ = editor.update(cx, |editor, window, cx| {
 1795        editor.select_to_beginning_of_line(
 1796            &SelectToBeginningOfLine {
 1797                stop_at_soft_wraps: true,
 1798                stop_at_indent: true,
 1799            },
 1800            window,
 1801            cx,
 1802        );
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.select_to_end_of_line(
 1814            &SelectToEndOfLine {
 1815                stop_at_soft_wraps: true,
 1816            },
 1817            window,
 1818            cx,
 1819        );
 1820        assert_eq!(
 1821            display_ranges(editor, cx),
 1822            &[
 1823                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1831        assert_eq!(editor.display_text(cx), "ab\n  de");
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]
 1838        );
 1839    });
 1840
 1841    _ = editor.update(cx, |editor, window, cx| {
 1842        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1843        assert_eq!(editor.display_text(cx), "\n");
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851    });
 1852}
 1853
 1854#[gpui::test]
 1855fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1856    init_test(cx, |_| {});
 1857    let move_to_beg = MoveToBeginningOfLine {
 1858        stop_at_soft_wraps: false,
 1859        stop_at_indent: false,
 1860    };
 1861
 1862    let move_to_end = MoveToEndOfLine {
 1863        stop_at_soft_wraps: false,
 1864    };
 1865
 1866    let editor = cx.add_window(|window, cx| {
 1867        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1868        build_editor(buffer, window, cx)
 1869    });
 1870
 1871    _ = editor.update(cx, |editor, window, cx| {
 1872        editor.set_wrap_width(Some(140.0.into()), cx);
 1873
 1874        // We expect the following lines after wrapping
 1875        // ```
 1876        // thequickbrownfox
 1877        // jumpedoverthelazydo
 1878        // gs
 1879        // ```
 1880        // The final `gs` was soft-wrapped onto a new line.
 1881        assert_eq!(
 1882            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1883            editor.display_text(cx),
 1884        );
 1885
 1886        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1887        // Start the cursor at the `k` on the first line
 1888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1889            s.select_display_ranges([
 1890                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1891            ]);
 1892        });
 1893
 1894        // Moving to the beginning of the line should put us at the beginning of the line.
 1895        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1896        assert_eq!(
 1897            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1898            display_ranges(editor, cx)
 1899        );
 1900
 1901        // Moving to the end of the line should put us at the end of the line.
 1902        editor.move_to_end_of_line(&move_to_end, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1905            display_ranges(editor, cx)
 1906        );
 1907
 1908        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1909        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1911            s.select_display_ranges([
 1912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1913            ]);
 1914        });
 1915
 1916        // Moving to the beginning of the line should put us at the start of the second line of
 1917        // display text, i.e., the `j`.
 1918        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Moving to the beginning of the line again should be a no-op.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            display_ranges(editor, cx)
 1929        );
 1930
 1931        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1932        // next display line.
 1933        editor.move_to_end_of_line(&move_to_end, window, cx);
 1934        assert_eq!(
 1935            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1936            display_ranges(editor, cx)
 1937        );
 1938
 1939        // Moving to the end of the line again should be a no-op.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            display_ranges(editor, cx)
 1944        );
 1945    });
 1946}
 1947
 1948#[gpui::test]
 1949fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1950    init_test(cx, |_| {});
 1951
 1952    let move_to_beg = MoveToBeginningOfLine {
 1953        stop_at_soft_wraps: true,
 1954        stop_at_indent: true,
 1955    };
 1956
 1957    let select_to_beg = SelectToBeginningOfLine {
 1958        stop_at_soft_wraps: true,
 1959        stop_at_indent: true,
 1960    };
 1961
 1962    let delete_to_beg = DeleteToBeginningOfLine {
 1963        stop_at_indent: true,
 1964    };
 1965
 1966    let move_to_end = MoveToEndOfLine {
 1967        stop_at_soft_wraps: false,
 1968    };
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1977            s.select_display_ranges([
 1978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1979                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1984        // and the second cursor at the first non-whitespace character in the line.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            display_ranges(editor, cx),
 1988            &[
 1989                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1990                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1991            ]
 1992        );
 1993
 1994        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1995        // and should move the second cursor to the beginning of the line.
 1996        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1997        assert_eq!(
 1998            display_ranges(editor, cx),
 1999            &[
 2000                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2001                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2002            ]
 2003        );
 2004
 2005        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2006        // and should move the second cursor back to the first non-whitespace character in the line.
 2007        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2008        assert_eq!(
 2009            display_ranges(editor, cx),
 2010            &[
 2011                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2012                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2013            ]
 2014        );
 2015
 2016        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2017        // and to the first non-whitespace character in the line for the second cursor.
 2018        editor.move_to_end_of_line(&move_to_end, window, cx);
 2019        editor.move_left(&MoveLeft, window, cx);
 2020        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2021        assert_eq!(
 2022            display_ranges(editor, cx),
 2023            &[
 2024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2025                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2026            ]
 2027        );
 2028
 2029        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2030        // and should select to the beginning of the line for the second cursor.
 2031        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2032        assert_eq!(
 2033            display_ranges(editor, cx),
 2034            &[
 2035                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2036                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2037            ]
 2038        );
 2039
 2040        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2041        // and should delete to the first non-whitespace character in the line for the second cursor.
 2042        editor.move_to_end_of_line(&move_to_end, window, cx);
 2043        editor.move_left(&MoveLeft, window, cx);
 2044        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2045        assert_eq!(editor.text(cx), "c\n  f");
 2046    });
 2047}
 2048
 2049#[gpui::test]
 2050fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2051    init_test(cx, |_| {});
 2052
 2053    let move_to_beg = MoveToBeginningOfLine {
 2054        stop_at_soft_wraps: true,
 2055        stop_at_indent: true,
 2056    };
 2057
 2058    let editor = cx.add_window(|window, cx| {
 2059        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2060        build_editor(buffer, window, cx)
 2061    });
 2062
 2063    _ = editor.update(cx, |editor, window, cx| {
 2064        // test cursor between line_start and indent_start
 2065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2066            s.select_display_ranges([
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2068            ]);
 2069        });
 2070
 2071        // cursor should move to line_start
 2072        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2073        assert_eq!(
 2074            display_ranges(editor, cx),
 2075            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2076        );
 2077
 2078        // cursor should move to indent_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            display_ranges(editor, cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2083        );
 2084
 2085        // cursor should move to back to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091    });
 2092}
 2093
 2094#[gpui::test]
 2095fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2096    init_test(cx, |_| {});
 2097
 2098    let editor = cx.add_window(|window, cx| {
 2099        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2100        build_editor(buffer, window, cx)
 2101    });
 2102    _ = editor.update(cx, |editor, window, cx| {
 2103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2104            s.select_display_ranges([
 2105                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2106                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2107            ])
 2108        });
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_right(&MoveRight, window, cx);
 2134        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2135        assert_selection_ranges(
 2136            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2137            editor,
 2138            cx,
 2139        );
 2140
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154    });
 2155}
 2156
 2157#[gpui::test]
 2158fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2159    init_test(cx, |_| {});
 2160
 2161    let editor = cx.add_window(|window, cx| {
 2162        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2163        build_editor(buffer, window, cx)
 2164    });
 2165
 2166    _ = editor.update(cx, |editor, window, cx| {
 2167        editor.set_wrap_width(Some(140.0.into()), cx);
 2168        assert_eq!(
 2169            editor.display_text(cx),
 2170            "use one::{\n    two::three::\n    four::five\n};"
 2171        );
 2172
 2173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2174            s.select_display_ranges([
 2175                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2176            ]);
 2177        });
 2178
 2179        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2180        assert_eq!(
 2181            display_ranges(editor, cx),
 2182            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2183        );
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            display_ranges(editor, cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            display_ranges(editor, cx),
 2194            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            display_ranges(editor, cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2201        );
 2202
 2203        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            display_ranges(editor, cx),
 2212            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2213        );
 2214    });
 2215}
 2216
 2217#[gpui::test]
 2218async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2219    init_test(cx, |_| {});
 2220    let mut cx = EditorTestContext::new(cx).await;
 2221
 2222    let line_height = cx.update_editor(|editor, window, cx| {
 2223        editor
 2224            .style(cx)
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.update_editor(|editor, window, cx| {
 2338        editor
 2339            .style(cx)
 2340            .text
 2341            .line_height_in_pixels(window.rem_size())
 2342    });
 2343    let window = cx.window;
 2344    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2345
 2346    cx.set_state(
 2347        r#"ˇone
 2348        two
 2349        three
 2350        four
 2351        five
 2352        six
 2353        seven
 2354        eight
 2355        nine
 2356        ten
 2357        "#,
 2358    );
 2359
 2360    cx.update_editor(|editor, window, cx| {
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 0.)
 2364        );
 2365        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2366        assert_eq!(
 2367            editor.snapshot(window, cx).scroll_position(),
 2368            gpui::Point::new(0., 3.)
 2369        );
 2370        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2371        assert_eq!(
 2372            editor.snapshot(window, cx).scroll_position(),
 2373            gpui::Point::new(0., 6.)
 2374        );
 2375        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2376        assert_eq!(
 2377            editor.snapshot(window, cx).scroll_position(),
 2378            gpui::Point::new(0., 3.)
 2379        );
 2380
 2381        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2382        assert_eq!(
 2383            editor.snapshot(window, cx).scroll_position(),
 2384            gpui::Point::new(0., 1.)
 2385        );
 2386        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2387        assert_eq!(
 2388            editor.snapshot(window, cx).scroll_position(),
 2389            gpui::Point::new(0., 3.)
 2390        );
 2391    });
 2392}
 2393
 2394#[gpui::test]
 2395async fn test_autoscroll(cx: &mut TestAppContext) {
 2396    init_test(cx, |_| {});
 2397    let mut cx = EditorTestContext::new(cx).await;
 2398
 2399    let line_height = cx.update_editor(|editor, window, cx| {
 2400        editor.set_vertical_scroll_margin(2, cx);
 2401        editor
 2402            .style(cx)
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.update_editor(|editor, window, cx| {
 2482        editor
 2483            .style(cx)
 2484            .text
 2485            .line_height_in_pixels(window.rem_size())
 2486    });
 2487    let window = cx.window;
 2488    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2489    cx.set_state(
 2490        &r#"
 2491        ˇone
 2492        two
 2493        threeˇ
 2494        four
 2495        five
 2496        six
 2497        seven
 2498        eight
 2499        nine
 2500        ten
 2501        "#
 2502        .unindent(),
 2503    );
 2504
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.move_page_down(&MovePageDown::default(), window, cx)
 2507    });
 2508    cx.assert_editor_state(
 2509        &r#"
 2510        one
 2511        two
 2512        three
 2513        ˇfour
 2514        five
 2515        sixˇ
 2516        seven
 2517        eight
 2518        nine
 2519        ten
 2520        "#
 2521        .unindent(),
 2522    );
 2523
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.move_page_down(&MovePageDown::default(), window, cx)
 2526    });
 2527    cx.assert_editor_state(
 2528        &r#"
 2529        one
 2530        two
 2531        three
 2532        four
 2533        five
 2534        six
 2535        ˇseven
 2536        eight
 2537        nineˇ
 2538        ten
 2539        "#
 2540        .unindent(),
 2541    );
 2542
 2543    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2561    cx.assert_editor_state(
 2562        &r#"
 2563        ˇone
 2564        two
 2565        threeˇ
 2566        four
 2567        five
 2568        six
 2569        seven
 2570        eight
 2571        nine
 2572        ten
 2573        "#
 2574        .unindent(),
 2575    );
 2576
 2577    // Test select collapsing
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582    });
 2583    cx.assert_editor_state(
 2584        &r#"
 2585        one
 2586        two
 2587        three
 2588        four
 2589        five
 2590        six
 2591        seven
 2592        eight
 2593        nine
 2594        ˇten
 2595        ˇ"#
 2596        .unindent(),
 2597    );
 2598}
 2599
 2600#[gpui::test]
 2601async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2602    init_test(cx, |_| {});
 2603    let mut cx = EditorTestContext::new(cx).await;
 2604    cx.set_state("one «two threeˇ» four");
 2605    cx.update_editor(|editor, window, cx| {
 2606        editor.delete_to_beginning_of_line(
 2607            &DeleteToBeginningOfLine {
 2608                stop_at_indent: false,
 2609            },
 2610            window,
 2611            cx,
 2612        );
 2613        assert_eq!(editor.text(cx), " four");
 2614    });
 2615}
 2616
 2617#[gpui::test]
 2618async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2619    init_test(cx, |_| {});
 2620
 2621    let mut cx = EditorTestContext::new(cx).await;
 2622
 2623    // For an empty selection, the preceding word fragment is deleted.
 2624    // For non-empty selections, only selected characters are deleted.
 2625    cx.set_state("onˇe two t«hreˇ»e four");
 2626    cx.update_editor(|editor, window, cx| {
 2627        editor.delete_to_previous_word_start(
 2628            &DeleteToPreviousWordStart {
 2629                ignore_newlines: false,
 2630                ignore_brackets: false,
 2631            },
 2632            window,
 2633            cx,
 2634        );
 2635    });
 2636    cx.assert_editor_state("ˇe two tˇe four");
 2637
 2638    cx.set_state("e tˇwo te «fˇ»our");
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.delete_to_next_word_end(
 2641            &DeleteToNextWordEnd {
 2642                ignore_newlines: false,
 2643                ignore_brackets: false,
 2644            },
 2645            window,
 2646            cx,
 2647        );
 2648    });
 2649    cx.assert_editor_state("e tˇ te ˇour");
 2650}
 2651
 2652#[gpui::test]
 2653async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2654    init_test(cx, |_| {});
 2655
 2656    let mut cx = EditorTestContext::new(cx).await;
 2657
 2658    cx.set_state("here is some text    ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: false,
 2663                ignore_brackets: true,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2670    cx.assert_editor_state("here is some textˇwith a space");
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: false,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    cx.assert_editor_state("here is some textˇwith a space");
 2684
 2685    cx.set_state("here is some textˇ    with a space");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_next_word_end(
 2688            &DeleteToNextWordEnd {
 2689                ignore_newlines: false,
 2690                ignore_brackets: true,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    // Same happens in the other direction.
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some textˇwith a space");
 2711
 2712    cx.set_state("here is some textˇ    with a space");
 2713    cx.update_editor(|editor, window, cx| {
 2714        editor.delete_to_next_word_end(
 2715            &DeleteToNextWordEnd {
 2716                ignore_newlines: true,
 2717                ignore_brackets: false,
 2718            },
 2719            window,
 2720            cx,
 2721        );
 2722    });
 2723    cx.assert_editor_state("here is some textˇwith a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_previous_word_start(
 2726            &DeleteToPreviousWordStart {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some ˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    // Single whitespaces are removed with the word behind them.
 2746    cx.assert_editor_state("here is ˇwith a space");
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    cx.assert_editor_state("here ˇwith a space");
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    cx.assert_editor_state("ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_next_word_end(
 2782            &DeleteToNextWordEnd {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    // Same happens in the other direction.
 2791    cx.assert_editor_state("ˇ a space");
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_next_word_end(
 2794            &DeleteToNextWordEnd {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state("ˇ space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_previous_word_start(
 2827            &DeleteToPreviousWordStart {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836}
 2837
 2838#[gpui::test]
 2839async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2840    init_test(cx, |_| {});
 2841
 2842    let language = Arc::new(
 2843        Language::new(
 2844            LanguageConfig {
 2845                brackets: BracketPairConfig {
 2846                    pairs: vec![
 2847                        BracketPair {
 2848                            start: "\"".to_string(),
 2849                            end: "\"".to_string(),
 2850                            close: true,
 2851                            surround: true,
 2852                            newline: false,
 2853                        },
 2854                        BracketPair {
 2855                            start: "(".to_string(),
 2856                            end: ")".to_string(),
 2857                            close: true,
 2858                            surround: true,
 2859                            newline: true,
 2860                        },
 2861                    ],
 2862                    ..BracketPairConfig::default()
 2863                },
 2864                ..LanguageConfig::default()
 2865            },
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_brackets_query(
 2869            r#"
 2870                ("(" @open ")" @close)
 2871                ("\"" @open "\"" @close)
 2872            "#,
 2873        )
 2874        .unwrap(),
 2875    );
 2876
 2877    let mut cx = EditorTestContext::new(cx).await;
 2878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2879
 2880    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    // Deletion stops before brackets if asked to not ignore them.
 2892    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    // Deletion has to remove a single bracket and then stop again.
 2904    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_previous_word_start(
 2920            &DeleteToPreviousWordStart {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2953    cx.assert_editor_state(r#"ˇ");"#);
 2954
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_next_word_end(
 2957            &DeleteToNextWordEnd {
 2958                ignore_newlines: true,
 2959                ignore_brackets: false,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"ˇ"#);
 2966
 2967    cx.update_editor(|editor, window, cx| {
 2968        editor.delete_to_next_word_end(
 2969            &DeleteToNextWordEnd {
 2970                ignore_newlines: true,
 2971                ignore_brackets: false,
 2972            },
 2973            window,
 2974            cx,
 2975        );
 2976    });
 2977    cx.assert_editor_state(r#"ˇ"#);
 2978
 2979    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2980    cx.update_editor(|editor, window, cx| {
 2981        editor.delete_to_previous_word_start(
 2982            &DeleteToPreviousWordStart {
 2983                ignore_newlines: true,
 2984                ignore_brackets: true,
 2985            },
 2986            window,
 2987            cx,
 2988        );
 2989    });
 2990    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2991}
 2992
 2993#[gpui::test]
 2994fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2999        build_editor(buffer, window, cx)
 3000    });
 3001    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3002        ignore_newlines: false,
 3003        ignore_brackets: false,
 3004    };
 3005    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3006        ignore_newlines: true,
 3007        ignore_brackets: false,
 3008    };
 3009
 3010    _ = editor.update(cx, |editor, window, cx| {
 3011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3012            s.select_display_ranges([
 3013                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3014            ])
 3015        });
 3016        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3017        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3018        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3020        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3021        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3022        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3023        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3024        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3025        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3028    });
 3029}
 3030
 3031#[gpui::test]
 3032fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3033    init_test(cx, |_| {});
 3034
 3035    let editor = cx.add_window(|window, cx| {
 3036        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3037        build_editor(buffer, window, cx)
 3038    });
 3039    let del_to_next_word_end = DeleteToNextWordEnd {
 3040        ignore_newlines: false,
 3041        ignore_brackets: false,
 3042    };
 3043    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3044        ignore_newlines: true,
 3045        ignore_brackets: false,
 3046    };
 3047
 3048    _ = editor.update(cx, |editor, window, cx| {
 3049        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3050            s.select_display_ranges([
 3051                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3052            ])
 3053        });
 3054        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3055        assert_eq!(
 3056            editor.buffer.read(cx).read(cx).text(),
 3057            "one\n   two\nthree\n   four"
 3058        );
 3059        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3060        assert_eq!(
 3061            editor.buffer.read(cx).read(cx).text(),
 3062            "\n   two\nthree\n   four"
 3063        );
 3064        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3065        assert_eq!(
 3066            editor.buffer.read(cx).read(cx).text(),
 3067            "two\nthree\n   four"
 3068        );
 3069        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3070        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3071        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3072        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3073        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3074        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3075        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3076        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3077    });
 3078}
 3079
 3080#[gpui::test]
 3081fn test_newline(cx: &mut TestAppContext) {
 3082    init_test(cx, |_| {});
 3083
 3084    let editor = cx.add_window(|window, cx| {
 3085        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3086        build_editor(buffer, window, cx)
 3087    });
 3088
 3089    _ = editor.update(cx, |editor, window, cx| {
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_display_ranges([
 3092                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3093                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3094                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3095            ])
 3096        });
 3097
 3098        editor.newline(&Newline, window, cx);
 3099        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3100    });
 3101}
 3102
 3103#[gpui::test]
 3104async fn test_newline_yaml(cx: &mut TestAppContext) {
 3105    init_test(cx, |_| {});
 3106
 3107    let mut cx = EditorTestContext::new(cx).await;
 3108    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3109    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3110
 3111    // Object (between 2 fields)
 3112    cx.set_state(indoc! {"
 3113    test:ˇ
 3114    hello: bye"});
 3115    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117    test:
 3118        ˇ
 3119    hello: bye"});
 3120
 3121    // Object (first and single line)
 3122    cx.set_state(indoc! {"
 3123    test:ˇ"});
 3124    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126    test:
 3127        ˇ"});
 3128
 3129    // Array with objects (after first element)
 3130    cx.set_state(indoc! {"
 3131    test:
 3132        - foo: barˇ"});
 3133    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3134    cx.assert_editor_state(indoc! {"
 3135    test:
 3136        - foo: bar
 3137        ˇ"});
 3138
 3139    // Array with objects and comment
 3140    cx.set_state(indoc! {"
 3141    test:
 3142        - foo: bar
 3143        - bar: # testˇ"});
 3144    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3145    cx.assert_editor_state(indoc! {"
 3146    test:
 3147        - foo: bar
 3148        - bar: # test
 3149            ˇ"});
 3150
 3151    // Array with objects (after second element)
 3152    cx.set_state(indoc! {"
 3153    test:
 3154        - foo: bar
 3155        - bar: fooˇ"});
 3156    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3157    cx.assert_editor_state(indoc! {"
 3158    test:
 3159        - foo: bar
 3160        - bar: foo
 3161        ˇ"});
 3162
 3163    // Array with strings (after first element)
 3164    cx.set_state(indoc! {"
 3165    test:
 3166        - fooˇ"});
 3167    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168    cx.assert_editor_state(indoc! {"
 3169    test:
 3170        - foo
 3171        ˇ"});
 3172}
 3173
 3174#[gpui::test]
 3175fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3176    init_test(cx, |_| {});
 3177
 3178    let editor = cx.add_window(|window, cx| {
 3179        let buffer = MultiBuffer::build_simple(
 3180            "
 3181                a
 3182                b(
 3183                    X
 3184                )
 3185                c(
 3186                    X
 3187                )
 3188            "
 3189            .unindent()
 3190            .as_str(),
 3191            cx,
 3192        );
 3193        let mut editor = build_editor(buffer, window, cx);
 3194        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3195            s.select_ranges([
 3196                Point::new(2, 4)..Point::new(2, 5),
 3197                Point::new(5, 4)..Point::new(5, 5),
 3198            ])
 3199        });
 3200        editor
 3201    });
 3202
 3203    _ = editor.update(cx, |editor, window, cx| {
 3204        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3205        editor.buffer.update(cx, |buffer, cx| {
 3206            buffer.edit(
 3207                [
 3208                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3209                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3210                ],
 3211                None,
 3212                cx,
 3213            );
 3214            assert_eq!(
 3215                buffer.read(cx).text(),
 3216                "
 3217                    a
 3218                    b()
 3219                    c()
 3220                "
 3221                .unindent()
 3222            );
 3223        });
 3224        assert_eq!(
 3225            editor.selections.ranges(&editor.display_snapshot(cx)),
 3226            &[
 3227                Point::new(1, 2)..Point::new(1, 2),
 3228                Point::new(2, 2)..Point::new(2, 2),
 3229            ],
 3230        );
 3231
 3232        editor.newline(&Newline, window, cx);
 3233        assert_eq!(
 3234            editor.text(cx),
 3235            "
 3236                a
 3237                b(
 3238                )
 3239                c(
 3240                )
 3241            "
 3242            .unindent()
 3243        );
 3244
 3245        // The selections are moved after the inserted newlines
 3246        assert_eq!(
 3247            editor.selections.ranges(&editor.display_snapshot(cx)),
 3248            &[
 3249                Point::new(2, 0)..Point::new(2, 0),
 3250                Point::new(4, 0)..Point::new(4, 0),
 3251            ],
 3252        );
 3253    });
 3254}
 3255
 3256#[gpui::test]
 3257async fn test_newline_above(cx: &mut TestAppContext) {
 3258    init_test(cx, |settings| {
 3259        settings.defaults.tab_size = NonZeroU32::new(4)
 3260    });
 3261
 3262    let language = Arc::new(
 3263        Language::new(
 3264            LanguageConfig::default(),
 3265            Some(tree_sitter_rust::LANGUAGE.into()),
 3266        )
 3267        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3268        .unwrap(),
 3269    );
 3270
 3271    let mut cx = EditorTestContext::new(cx).await;
 3272    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3273    cx.set_state(indoc! {"
 3274        const a: ˇA = (
 3275 3276                «const_functionˇ»(ˇ),
 3277                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3278 3279        ˇ);ˇ
 3280    "});
 3281
 3282    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3283    cx.assert_editor_state(indoc! {"
 3284        ˇ
 3285        const a: A = (
 3286            ˇ
 3287            (
 3288                ˇ
 3289                ˇ
 3290                const_function(),
 3291                ˇ
 3292                ˇ
 3293                ˇ
 3294                ˇ
 3295                something_else,
 3296                ˇ
 3297            )
 3298            ˇ
 3299            ˇ
 3300        );
 3301    "});
 3302}
 3303
 3304#[gpui::test]
 3305async fn test_newline_below(cx: &mut TestAppContext) {
 3306    init_test(cx, |settings| {
 3307        settings.defaults.tab_size = NonZeroU32::new(4)
 3308    });
 3309
 3310    let language = Arc::new(
 3311        Language::new(
 3312            LanguageConfig::default(),
 3313            Some(tree_sitter_rust::LANGUAGE.into()),
 3314        )
 3315        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3316        .unwrap(),
 3317    );
 3318
 3319    let mut cx = EditorTestContext::new(cx).await;
 3320    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3321    cx.set_state(indoc! {"
 3322        const a: ˇA = (
 3323 3324                «const_functionˇ»(ˇ),
 3325                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3326 3327        ˇ);ˇ
 3328    "});
 3329
 3330    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3331    cx.assert_editor_state(indoc! {"
 3332        const a: A = (
 3333            ˇ
 3334            (
 3335                ˇ
 3336                const_function(),
 3337                ˇ
 3338                ˇ
 3339                something_else,
 3340                ˇ
 3341                ˇ
 3342                ˇ
 3343                ˇ
 3344            )
 3345            ˇ
 3346        );
 3347        ˇ
 3348        ˇ
 3349    "});
 3350}
 3351
 3352#[gpui::test]
 3353async fn test_newline_comments(cx: &mut TestAppContext) {
 3354    init_test(cx, |settings| {
 3355        settings.defaults.tab_size = NonZeroU32::new(4)
 3356    });
 3357
 3358    let language = Arc::new(Language::new(
 3359        LanguageConfig {
 3360            line_comments: vec!["// ".into()],
 3361            ..LanguageConfig::default()
 3362        },
 3363        None,
 3364    ));
 3365    {
 3366        let mut cx = EditorTestContext::new(cx).await;
 3367        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3368        cx.set_state(indoc! {"
 3369        // Fooˇ
 3370    "});
 3371
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        // Foo
 3375        // ˇ
 3376    "});
 3377        // Ensure that we add comment prefix when existing line contains space
 3378        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3379        cx.assert_editor_state(
 3380            indoc! {"
 3381        // Foo
 3382        //s
 3383        // ˇ
 3384    "}
 3385            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3386            .as_str(),
 3387        );
 3388        // Ensure that we add comment prefix when existing line does not contain space
 3389        cx.set_state(indoc! {"
 3390        // Foo
 3391        //ˇ
 3392    "});
 3393        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3394        cx.assert_editor_state(indoc! {"
 3395        // Foo
 3396        //
 3397        // ˇ
 3398    "});
 3399        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3400        cx.set_state(indoc! {"
 3401        ˇ// Foo
 3402    "});
 3403        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3404        cx.assert_editor_state(indoc! {"
 3405
 3406        ˇ// Foo
 3407    "});
 3408    }
 3409    // Ensure that comment continuations can be disabled.
 3410    update_test_language_settings(cx, |settings| {
 3411        settings.defaults.extend_comment_on_newline = Some(false);
 3412    });
 3413    let mut cx = EditorTestContext::new(cx).await;
 3414    cx.set_state(indoc! {"
 3415        // Fooˇ
 3416    "});
 3417    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        // Foo
 3420        ˇ
 3421    "});
 3422}
 3423
 3424#[gpui::test]
 3425async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3426    init_test(cx, |settings| {
 3427        settings.defaults.tab_size = NonZeroU32::new(4)
 3428    });
 3429
 3430    let language = Arc::new(Language::new(
 3431        LanguageConfig {
 3432            line_comments: vec!["// ".into(), "/// ".into()],
 3433            ..LanguageConfig::default()
 3434        },
 3435        None,
 3436    ));
 3437    {
 3438        let mut cx = EditorTestContext::new(cx).await;
 3439        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3440        cx.set_state(indoc! {"
 3441        //ˇ
 3442    "});
 3443        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3444        cx.assert_editor_state(indoc! {"
 3445        //
 3446        // ˇ
 3447    "});
 3448
 3449        cx.set_state(indoc! {"
 3450        ///ˇ
 3451    "});
 3452        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3453        cx.assert_editor_state(indoc! {"
 3454        ///
 3455        /// ˇ
 3456    "});
 3457    }
 3458}
 3459
 3460#[gpui::test]
 3461async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3462    init_test(cx, |settings| {
 3463        settings.defaults.tab_size = NonZeroU32::new(4)
 3464    });
 3465
 3466    let language = Arc::new(
 3467        Language::new(
 3468            LanguageConfig {
 3469                documentation_comment: Some(language::BlockCommentConfig {
 3470                    start: "/**".into(),
 3471                    end: "*/".into(),
 3472                    prefix: "* ".into(),
 3473                    tab_size: 1,
 3474                }),
 3475
 3476                ..LanguageConfig::default()
 3477            },
 3478            Some(tree_sitter_rust::LANGUAGE.into()),
 3479        )
 3480        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3481        .unwrap(),
 3482    );
 3483
 3484    {
 3485        let mut cx = EditorTestContext::new(cx).await;
 3486        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3487        cx.set_state(indoc! {"
 3488        /**ˇ
 3489    "});
 3490
 3491        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492        cx.assert_editor_state(indoc! {"
 3493        /**
 3494         * ˇ
 3495    "});
 3496        // Ensure that if cursor is before the comment start,
 3497        // we do not actually insert a comment prefix.
 3498        cx.set_state(indoc! {"
 3499        ˇ/**
 3500    "});
 3501        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3502        cx.assert_editor_state(indoc! {"
 3503
 3504        ˇ/**
 3505    "});
 3506        // Ensure that if cursor is between it doesn't add comment prefix.
 3507        cx.set_state(indoc! {"
 3508        /*ˇ*
 3509    "});
 3510        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3511        cx.assert_editor_state(indoc! {"
 3512        /*
 3513        ˇ*
 3514    "});
 3515        // Ensure that if suffix exists on same line after cursor it adds new line.
 3516        cx.set_state(indoc! {"
 3517        /**ˇ*/
 3518    "});
 3519        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3520        cx.assert_editor_state(indoc! {"
 3521        /**
 3522         * ˇ
 3523         */
 3524    "});
 3525        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3526        cx.set_state(indoc! {"
 3527        /**ˇ */
 3528    "});
 3529        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3530        cx.assert_editor_state(indoc! {"
 3531        /**
 3532         * ˇ
 3533         */
 3534    "});
 3535        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3536        cx.set_state(indoc! {"
 3537        /** ˇ*/
 3538    "});
 3539        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3540        cx.assert_editor_state(
 3541            indoc! {"
 3542        /**s
 3543         * ˇ
 3544         */
 3545    "}
 3546            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3547            .as_str(),
 3548        );
 3549        // Ensure that delimiter space is preserved when newline on already
 3550        // spaced delimiter.
 3551        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3552        cx.assert_editor_state(
 3553            indoc! {"
 3554        /**s
 3555         *s
 3556         * ˇ
 3557         */
 3558    "}
 3559            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3560            .as_str(),
 3561        );
 3562        // Ensure that delimiter space is preserved when space is not
 3563        // on existing delimiter.
 3564        cx.set_state(indoc! {"
 3565        /**
 3566 3567         */
 3568    "});
 3569        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3570        cx.assert_editor_state(indoc! {"
 3571        /**
 3572         *
 3573         * ˇ
 3574         */
 3575    "});
 3576        // Ensure that if suffix exists on same line after cursor it
 3577        // doesn't add extra new line if prefix is not on same line.
 3578        cx.set_state(indoc! {"
 3579        /**
 3580        ˇ*/
 3581    "});
 3582        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3583        cx.assert_editor_state(indoc! {"
 3584        /**
 3585
 3586        ˇ*/
 3587    "});
 3588        // Ensure that it detects suffix after existing prefix.
 3589        cx.set_state(indoc! {"
 3590        /**ˇ/
 3591    "});
 3592        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3593        cx.assert_editor_state(indoc! {"
 3594        /**
 3595        ˇ/
 3596    "});
 3597        // Ensure that if suffix exists on same line before
 3598        // cursor it does not add comment prefix.
 3599        cx.set_state(indoc! {"
 3600        /** */ˇ
 3601    "});
 3602        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3603        cx.assert_editor_state(indoc! {"
 3604        /** */
 3605        ˇ
 3606    "});
 3607        // Ensure that if suffix exists on same line before
 3608        // cursor it does not add comment prefix.
 3609        cx.set_state(indoc! {"
 3610        /**
 3611         *
 3612         */ˇ
 3613    "});
 3614        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3615        cx.assert_editor_state(indoc! {"
 3616        /**
 3617         *
 3618         */
 3619         ˇ
 3620    "});
 3621
 3622        // Ensure that inline comment followed by code
 3623        // doesn't add comment prefix on newline
 3624        cx.set_state(indoc! {"
 3625        /** */ textˇ
 3626    "});
 3627        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3628        cx.assert_editor_state(indoc! {"
 3629        /** */ text
 3630        ˇ
 3631    "});
 3632
 3633        // Ensure that text after comment end tag
 3634        // doesn't add comment prefix on newline
 3635        cx.set_state(indoc! {"
 3636        /**
 3637         *
 3638         */ˇtext
 3639    "});
 3640        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3641        cx.assert_editor_state(indoc! {"
 3642        /**
 3643         *
 3644         */
 3645         ˇtext
 3646    "});
 3647
 3648        // Ensure if not comment block it doesn't
 3649        // add comment prefix on newline
 3650        cx.set_state(indoc! {"
 3651        * textˇ
 3652    "});
 3653        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3654        cx.assert_editor_state(indoc! {"
 3655        * text
 3656        ˇ
 3657    "});
 3658    }
 3659    // Ensure that comment continuations can be disabled.
 3660    update_test_language_settings(cx, |settings| {
 3661        settings.defaults.extend_comment_on_newline = Some(false);
 3662    });
 3663    let mut cx = EditorTestContext::new(cx).await;
 3664    cx.set_state(indoc! {"
 3665        /**ˇ
 3666    "});
 3667    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3668    cx.assert_editor_state(indoc! {"
 3669        /**
 3670        ˇ
 3671    "});
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3676    init_test(cx, |settings| {
 3677        settings.defaults.tab_size = NonZeroU32::new(4)
 3678    });
 3679
 3680    let lua_language = Arc::new(Language::new(
 3681        LanguageConfig {
 3682            line_comments: vec!["--".into()],
 3683            block_comment: Some(language::BlockCommentConfig {
 3684                start: "--[[".into(),
 3685                prefix: "".into(),
 3686                end: "]]".into(),
 3687                tab_size: 0,
 3688            }),
 3689            ..LanguageConfig::default()
 3690        },
 3691        None,
 3692    ));
 3693
 3694    let mut cx = EditorTestContext::new(cx).await;
 3695    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3696
 3697    // Line with line comment should extend
 3698    cx.set_state(indoc! {"
 3699        --ˇ
 3700    "});
 3701    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3702    cx.assert_editor_state(indoc! {"
 3703        --
 3704        --ˇ
 3705    "});
 3706
 3707    // Line with block comment that matches line comment should not extend
 3708    cx.set_state(indoc! {"
 3709        --[[ˇ
 3710    "});
 3711    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3712    cx.assert_editor_state(indoc! {"
 3713        --[[
 3714        ˇ
 3715    "});
 3716}
 3717
 3718#[gpui::test]
 3719fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3720    init_test(cx, |_| {});
 3721
 3722    let editor = cx.add_window(|window, cx| {
 3723        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3724        let mut editor = build_editor(buffer, window, cx);
 3725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3726            s.select_ranges([
 3727                MultiBufferOffset(3)..MultiBufferOffset(4),
 3728                MultiBufferOffset(11)..MultiBufferOffset(12),
 3729                MultiBufferOffset(19)..MultiBufferOffset(20),
 3730            ])
 3731        });
 3732        editor
 3733    });
 3734
 3735    _ = editor.update(cx, |editor, window, cx| {
 3736        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3737        editor.buffer.update(cx, |buffer, cx| {
 3738            buffer.edit(
 3739                [
 3740                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3741                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3742                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3743                ],
 3744                None,
 3745                cx,
 3746            );
 3747            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3748        });
 3749        assert_eq!(
 3750            editor.selections.ranges(&editor.display_snapshot(cx)),
 3751            &[
 3752                MultiBufferOffset(2)..MultiBufferOffset(2),
 3753                MultiBufferOffset(7)..MultiBufferOffset(7),
 3754                MultiBufferOffset(12)..MultiBufferOffset(12)
 3755            ],
 3756        );
 3757
 3758        editor.insert("Z", window, cx);
 3759        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3760
 3761        // The selections are moved after the inserted characters
 3762        assert_eq!(
 3763            editor.selections.ranges(&editor.display_snapshot(cx)),
 3764            &[
 3765                MultiBufferOffset(3)..MultiBufferOffset(3),
 3766                MultiBufferOffset(9)..MultiBufferOffset(9),
 3767                MultiBufferOffset(15)..MultiBufferOffset(15)
 3768            ],
 3769        );
 3770    });
 3771}
 3772
 3773#[gpui::test]
 3774async fn test_tab(cx: &mut TestAppContext) {
 3775    init_test(cx, |settings| {
 3776        settings.defaults.tab_size = NonZeroU32::new(3)
 3777    });
 3778
 3779    let mut cx = EditorTestContext::new(cx).await;
 3780    cx.set_state(indoc! {"
 3781        ˇabˇc
 3782        ˇ🏀ˇ🏀ˇefg
 3783 3784    "});
 3785    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3786    cx.assert_editor_state(indoc! {"
 3787           ˇab ˇc
 3788           ˇ🏀  ˇ🏀  ˇefg
 3789        d  ˇ
 3790    "});
 3791
 3792    cx.set_state(indoc! {"
 3793        a
 3794        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3795    "});
 3796    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3797    cx.assert_editor_state(indoc! {"
 3798        a
 3799           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3800    "});
 3801}
 3802
 3803#[gpui::test]
 3804async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3805    init_test(cx, |_| {});
 3806
 3807    let mut cx = EditorTestContext::new(cx).await;
 3808    let language = Arc::new(
 3809        Language::new(
 3810            LanguageConfig::default(),
 3811            Some(tree_sitter_rust::LANGUAGE.into()),
 3812        )
 3813        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3814        .unwrap(),
 3815    );
 3816    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3817
 3818    // test when all cursors are not at suggested indent
 3819    // then simply move to their suggested indent location
 3820    cx.set_state(indoc! {"
 3821        const a: B = (
 3822            c(
 3823        ˇ
 3824        ˇ    )
 3825        );
 3826    "});
 3827    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3828    cx.assert_editor_state(indoc! {"
 3829        const a: B = (
 3830            c(
 3831                ˇ
 3832            ˇ)
 3833        );
 3834    "});
 3835
 3836    // test cursor already at suggested indent not moving when
 3837    // other cursors are yet to reach their suggested indents
 3838    cx.set_state(indoc! {"
 3839        ˇ
 3840        const a: B = (
 3841            c(
 3842                d(
 3843        ˇ
 3844                )
 3845        ˇ
 3846        ˇ    )
 3847        );
 3848    "});
 3849    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3850    cx.assert_editor_state(indoc! {"
 3851        ˇ
 3852        const a: B = (
 3853            c(
 3854                d(
 3855                    ˇ
 3856                )
 3857                ˇ
 3858            ˇ)
 3859        );
 3860    "});
 3861    // test when all cursors are at suggested indent then tab is inserted
 3862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3863    cx.assert_editor_state(indoc! {"
 3864            ˇ
 3865        const a: B = (
 3866            c(
 3867                d(
 3868                        ˇ
 3869                )
 3870                    ˇ
 3871                ˇ)
 3872        );
 3873    "});
 3874
 3875    // test when current indent is less than suggested indent,
 3876    // we adjust line to match suggested indent and move cursor to it
 3877    //
 3878    // when no other cursor is at word boundary, all of them should move
 3879    cx.set_state(indoc! {"
 3880        const a: B = (
 3881            c(
 3882                d(
 3883        ˇ
 3884        ˇ   )
 3885        ˇ   )
 3886        );
 3887    "});
 3888    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3889    cx.assert_editor_state(indoc! {"
 3890        const a: B = (
 3891            c(
 3892                d(
 3893                    ˇ
 3894                ˇ)
 3895            ˇ)
 3896        );
 3897    "});
 3898
 3899    // test when current indent is less than suggested indent,
 3900    // we adjust line to match suggested indent and move cursor to it
 3901    //
 3902    // when some other cursor is at word boundary, it should not move
 3903    cx.set_state(indoc! {"
 3904        const a: B = (
 3905            c(
 3906                d(
 3907        ˇ
 3908        ˇ   )
 3909           ˇ)
 3910        );
 3911    "});
 3912    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3913    cx.assert_editor_state(indoc! {"
 3914        const a: B = (
 3915            c(
 3916                d(
 3917                    ˇ
 3918                ˇ)
 3919            ˇ)
 3920        );
 3921    "});
 3922
 3923    // test when current indent is more than suggested indent,
 3924    // we just move cursor to current indent instead of suggested indent
 3925    //
 3926    // when no other cursor is at word boundary, all of them should move
 3927    cx.set_state(indoc! {"
 3928        const a: B = (
 3929            c(
 3930                d(
 3931        ˇ
 3932        ˇ                )
 3933        ˇ   )
 3934        );
 3935    "});
 3936    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3937    cx.assert_editor_state(indoc! {"
 3938        const a: B = (
 3939            c(
 3940                d(
 3941                    ˇ
 3942                        ˇ)
 3943            ˇ)
 3944        );
 3945    "});
 3946    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3947    cx.assert_editor_state(indoc! {"
 3948        const a: B = (
 3949            c(
 3950                d(
 3951                        ˇ
 3952                            ˇ)
 3953                ˇ)
 3954        );
 3955    "});
 3956
 3957    // test when current indent is more than suggested indent,
 3958    // we just move cursor to current indent instead of suggested indent
 3959    //
 3960    // when some other cursor is at word boundary, it doesn't move
 3961    cx.set_state(indoc! {"
 3962        const a: B = (
 3963            c(
 3964                d(
 3965        ˇ
 3966        ˇ                )
 3967            ˇ)
 3968        );
 3969    "});
 3970    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3971    cx.assert_editor_state(indoc! {"
 3972        const a: B = (
 3973            c(
 3974                d(
 3975                    ˇ
 3976                        ˇ)
 3977            ˇ)
 3978        );
 3979    "});
 3980
 3981    // handle auto-indent when there are multiple cursors on the same line
 3982    cx.set_state(indoc! {"
 3983        const a: B = (
 3984            c(
 3985        ˇ    ˇ
 3986        ˇ    )
 3987        );
 3988    "});
 3989    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3990    cx.assert_editor_state(indoc! {"
 3991        const a: B = (
 3992            c(
 3993                ˇ
 3994            ˇ)
 3995        );
 3996    "});
 3997}
 3998
 3999#[gpui::test]
 4000async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4001    init_test(cx, |settings| {
 4002        settings.defaults.tab_size = NonZeroU32::new(3)
 4003    });
 4004
 4005    let mut cx = EditorTestContext::new(cx).await;
 4006    cx.set_state(indoc! {"
 4007         ˇ
 4008        \t ˇ
 4009        \t  ˇ
 4010        \t   ˇ
 4011         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4012    "});
 4013
 4014    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4015    cx.assert_editor_state(indoc! {"
 4016           ˇ
 4017        \t   ˇ
 4018        \t   ˇ
 4019        \t      ˇ
 4020         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4021    "});
 4022}
 4023
 4024#[gpui::test]
 4025async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4026    init_test(cx, |settings| {
 4027        settings.defaults.tab_size = NonZeroU32::new(4)
 4028    });
 4029
 4030    let language = Arc::new(
 4031        Language::new(
 4032            LanguageConfig::default(),
 4033            Some(tree_sitter_rust::LANGUAGE.into()),
 4034        )
 4035        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4036        .unwrap(),
 4037    );
 4038
 4039    let mut cx = EditorTestContext::new(cx).await;
 4040    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4041    cx.set_state(indoc! {"
 4042        fn a() {
 4043            if b {
 4044        \t ˇc
 4045            }
 4046        }
 4047    "});
 4048
 4049    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4050    cx.assert_editor_state(indoc! {"
 4051        fn a() {
 4052            if b {
 4053                ˇc
 4054            }
 4055        }
 4056    "});
 4057}
 4058
 4059#[gpui::test]
 4060async fn test_indent_outdent(cx: &mut TestAppContext) {
 4061    init_test(cx, |settings| {
 4062        settings.defaults.tab_size = NonZeroU32::new(4);
 4063    });
 4064
 4065    let mut cx = EditorTestContext::new(cx).await;
 4066
 4067    cx.set_state(indoc! {"
 4068          «oneˇ» «twoˇ»
 4069        three
 4070         four
 4071    "});
 4072    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4073    cx.assert_editor_state(indoc! {"
 4074            «oneˇ» «twoˇ»
 4075        three
 4076         four
 4077    "});
 4078
 4079    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4080    cx.assert_editor_state(indoc! {"
 4081        «oneˇ» «twoˇ»
 4082        three
 4083         four
 4084    "});
 4085
 4086    // select across line ending
 4087    cx.set_state(indoc! {"
 4088        one two
 4089        t«hree
 4090        ˇ» four
 4091    "});
 4092    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4093    cx.assert_editor_state(indoc! {"
 4094        one two
 4095            t«hree
 4096        ˇ» four
 4097    "});
 4098
 4099    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4100    cx.assert_editor_state(indoc! {"
 4101        one two
 4102        t«hree
 4103        ˇ» four
 4104    "});
 4105
 4106    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4107    cx.set_state(indoc! {"
 4108        one two
 4109        ˇthree
 4110            four
 4111    "});
 4112    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4113    cx.assert_editor_state(indoc! {"
 4114        one two
 4115            ˇthree
 4116            four
 4117    "});
 4118
 4119    cx.set_state(indoc! {"
 4120        one two
 4121        ˇ    three
 4122            four
 4123    "});
 4124    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4125    cx.assert_editor_state(indoc! {"
 4126        one two
 4127        ˇthree
 4128            four
 4129    "});
 4130}
 4131
 4132#[gpui::test]
 4133async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4134    // This is a regression test for issue #33761
 4135    init_test(cx, |_| {});
 4136
 4137    let mut cx = EditorTestContext::new(cx).await;
 4138    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4140
 4141    cx.set_state(
 4142        r#"ˇ#     ingress:
 4143ˇ#         api:
 4144ˇ#             enabled: false
 4145ˇ#             pathType: Prefix
 4146ˇ#           console:
 4147ˇ#               enabled: false
 4148ˇ#               pathType: Prefix
 4149"#,
 4150    );
 4151
 4152    // Press tab to indent all lines
 4153    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4154
 4155    cx.assert_editor_state(
 4156        r#"    ˇ#     ingress:
 4157    ˇ#         api:
 4158    ˇ#             enabled: false
 4159    ˇ#             pathType: Prefix
 4160    ˇ#           console:
 4161    ˇ#               enabled: false
 4162    ˇ#               pathType: Prefix
 4163"#,
 4164    );
 4165}
 4166
 4167#[gpui::test]
 4168async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4169    // This is a test to make sure our fix for issue #33761 didn't break anything
 4170    init_test(cx, |_| {});
 4171
 4172    let mut cx = EditorTestContext::new(cx).await;
 4173    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4175
 4176    cx.set_state(
 4177        r#"ˇingress:
 4178ˇ  api:
 4179ˇ    enabled: false
 4180ˇ    pathType: Prefix
 4181"#,
 4182    );
 4183
 4184    // Press tab to indent all lines
 4185    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4186
 4187    cx.assert_editor_state(
 4188        r#"ˇingress:
 4189    ˇapi:
 4190        ˇenabled: false
 4191        ˇpathType: Prefix
 4192"#,
 4193    );
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4198    init_test(cx, |settings| {
 4199        settings.defaults.hard_tabs = Some(true);
 4200    });
 4201
 4202    let mut cx = EditorTestContext::new(cx).await;
 4203
 4204    // select two ranges on one line
 4205    cx.set_state(indoc! {"
 4206        «oneˇ» «twoˇ»
 4207        three
 4208        four
 4209    "});
 4210    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4211    cx.assert_editor_state(indoc! {"
 4212        \t«oneˇ» «twoˇ»
 4213        three
 4214        four
 4215    "});
 4216    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4217    cx.assert_editor_state(indoc! {"
 4218        \t\t«oneˇ» «twoˇ»
 4219        three
 4220        four
 4221    "});
 4222    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4223    cx.assert_editor_state(indoc! {"
 4224        \t«oneˇ» «twoˇ»
 4225        three
 4226        four
 4227    "});
 4228    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4229    cx.assert_editor_state(indoc! {"
 4230        «oneˇ» «twoˇ»
 4231        three
 4232        four
 4233    "});
 4234
 4235    // select across a line ending
 4236    cx.set_state(indoc! {"
 4237        one two
 4238        t«hree
 4239        ˇ»four
 4240    "});
 4241    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4242    cx.assert_editor_state(indoc! {"
 4243        one two
 4244        \tt«hree
 4245        ˇ»four
 4246    "});
 4247    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4248    cx.assert_editor_state(indoc! {"
 4249        one two
 4250        \t\tt«hree
 4251        ˇ»four
 4252    "});
 4253    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4254    cx.assert_editor_state(indoc! {"
 4255        one two
 4256        \tt«hree
 4257        ˇ»four
 4258    "});
 4259    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4260    cx.assert_editor_state(indoc! {"
 4261        one two
 4262        t«hree
 4263        ˇ»four
 4264    "});
 4265
 4266    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4267    cx.set_state(indoc! {"
 4268        one two
 4269        ˇthree
 4270        four
 4271    "});
 4272    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4273    cx.assert_editor_state(indoc! {"
 4274        one two
 4275        ˇthree
 4276        four
 4277    "});
 4278    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4279    cx.assert_editor_state(indoc! {"
 4280        one two
 4281        \tˇthree
 4282        four
 4283    "});
 4284    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4285    cx.assert_editor_state(indoc! {"
 4286        one two
 4287        ˇthree
 4288        four
 4289    "});
 4290}
 4291
 4292#[gpui::test]
 4293fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4294    init_test(cx, |settings| {
 4295        settings.languages.0.extend([
 4296            (
 4297                "TOML".into(),
 4298                LanguageSettingsContent {
 4299                    tab_size: NonZeroU32::new(2),
 4300                    ..Default::default()
 4301                },
 4302            ),
 4303            (
 4304                "Rust".into(),
 4305                LanguageSettingsContent {
 4306                    tab_size: NonZeroU32::new(4),
 4307                    ..Default::default()
 4308                },
 4309            ),
 4310        ]);
 4311    });
 4312
 4313    let toml_language = Arc::new(Language::new(
 4314        LanguageConfig {
 4315            name: "TOML".into(),
 4316            ..Default::default()
 4317        },
 4318        None,
 4319    ));
 4320    let rust_language = Arc::new(Language::new(
 4321        LanguageConfig {
 4322            name: "Rust".into(),
 4323            ..Default::default()
 4324        },
 4325        None,
 4326    ));
 4327
 4328    let toml_buffer =
 4329        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4330    let rust_buffer =
 4331        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4332    let multibuffer = cx.new(|cx| {
 4333        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4334        multibuffer.push_excerpts(
 4335            toml_buffer.clone(),
 4336            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4337            cx,
 4338        );
 4339        multibuffer.push_excerpts(
 4340            rust_buffer.clone(),
 4341            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4342            cx,
 4343        );
 4344        multibuffer
 4345    });
 4346
 4347    cx.add_window(|window, cx| {
 4348        let mut editor = build_editor(multibuffer, window, cx);
 4349
 4350        assert_eq!(
 4351            editor.text(cx),
 4352            indoc! {"
 4353                a = 1
 4354                b = 2
 4355
 4356                const c: usize = 3;
 4357            "}
 4358        );
 4359
 4360        select_ranges(
 4361            &mut editor,
 4362            indoc! {"
 4363                «aˇ» = 1
 4364                b = 2
 4365
 4366                «const c:ˇ» usize = 3;
 4367            "},
 4368            window,
 4369            cx,
 4370        );
 4371
 4372        editor.tab(&Tab, window, cx);
 4373        assert_text_with_selections(
 4374            &mut editor,
 4375            indoc! {"
 4376                  «aˇ» = 1
 4377                b = 2
 4378
 4379                    «const c:ˇ» usize = 3;
 4380            "},
 4381            cx,
 4382        );
 4383        editor.backtab(&Backtab, window, cx);
 4384        assert_text_with_selections(
 4385            &mut editor,
 4386            indoc! {"
 4387                «aˇ» = 1
 4388                b = 2
 4389
 4390                «const c:ˇ» usize = 3;
 4391            "},
 4392            cx,
 4393        );
 4394
 4395        editor
 4396    });
 4397}
 4398
 4399#[gpui::test]
 4400async fn test_backspace(cx: &mut TestAppContext) {
 4401    init_test(cx, |_| {});
 4402
 4403    let mut cx = EditorTestContext::new(cx).await;
 4404
 4405    // Basic backspace
 4406    cx.set_state(indoc! {"
 4407        onˇe two three
 4408        fou«rˇ» five six
 4409        seven «ˇeight nine
 4410        »ten
 4411    "});
 4412    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4413    cx.assert_editor_state(indoc! {"
 4414        oˇe two three
 4415        fouˇ five six
 4416        seven ˇten
 4417    "});
 4418
 4419    // Test backspace inside and around indents
 4420    cx.set_state(indoc! {"
 4421        zero
 4422            ˇone
 4423                ˇtwo
 4424            ˇ ˇ ˇ  three
 4425        ˇ  ˇ  four
 4426    "});
 4427    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4428    cx.assert_editor_state(indoc! {"
 4429        zero
 4430        ˇone
 4431            ˇtwo
 4432        ˇ  threeˇ  four
 4433    "});
 4434}
 4435
 4436#[gpui::test]
 4437async fn test_delete(cx: &mut TestAppContext) {
 4438    init_test(cx, |_| {});
 4439
 4440    let mut cx = EditorTestContext::new(cx).await;
 4441    cx.set_state(indoc! {"
 4442        onˇe two three
 4443        fou«rˇ» five six
 4444        seven «ˇeight nine
 4445        »ten
 4446    "});
 4447    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4448    cx.assert_editor_state(indoc! {"
 4449        onˇ two three
 4450        fouˇ five six
 4451        seven ˇten
 4452    "});
 4453}
 4454
 4455#[gpui::test]
 4456fn test_delete_line(cx: &mut TestAppContext) {
 4457    init_test(cx, |_| {});
 4458
 4459    let editor = cx.add_window(|window, cx| {
 4460        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4461        build_editor(buffer, window, cx)
 4462    });
 4463    _ = editor.update(cx, |editor, window, cx| {
 4464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4465            s.select_display_ranges([
 4466                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4467                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4468                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4469            ])
 4470        });
 4471        editor.delete_line(&DeleteLine, window, cx);
 4472        assert_eq!(editor.display_text(cx), "ghi");
 4473        assert_eq!(
 4474            display_ranges(editor, cx),
 4475            vec![
 4476                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4477                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4478            ]
 4479        );
 4480    });
 4481
 4482    let editor = cx.add_window(|window, cx| {
 4483        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4484        build_editor(buffer, window, cx)
 4485    });
 4486    _ = editor.update(cx, |editor, window, cx| {
 4487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4488            s.select_display_ranges([
 4489                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4490            ])
 4491        });
 4492        editor.delete_line(&DeleteLine, window, cx);
 4493        assert_eq!(editor.display_text(cx), "ghi\n");
 4494        assert_eq!(
 4495            display_ranges(editor, cx),
 4496            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4497        );
 4498    });
 4499
 4500    let editor = cx.add_window(|window, cx| {
 4501        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4502        build_editor(buffer, window, cx)
 4503    });
 4504    _ = editor.update(cx, |editor, window, cx| {
 4505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4506            s.select_display_ranges([
 4507                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4508            ])
 4509        });
 4510        editor.delete_line(&DeleteLine, window, cx);
 4511        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4512        assert_eq!(
 4513            display_ranges(editor, cx),
 4514            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4515        );
 4516    });
 4517}
 4518
 4519#[gpui::test]
 4520fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4521    init_test(cx, |_| {});
 4522
 4523    cx.add_window(|window, cx| {
 4524        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4525        let mut editor = build_editor(buffer.clone(), window, cx);
 4526        let buffer = buffer.read(cx).as_singleton().unwrap();
 4527
 4528        assert_eq!(
 4529            editor
 4530                .selections
 4531                .ranges::<Point>(&editor.display_snapshot(cx)),
 4532            &[Point::new(0, 0)..Point::new(0, 0)]
 4533        );
 4534
 4535        // When on single line, replace newline at end by space
 4536        editor.join_lines(&JoinLines, window, cx);
 4537        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4538        assert_eq!(
 4539            editor
 4540                .selections
 4541                .ranges::<Point>(&editor.display_snapshot(cx)),
 4542            &[Point::new(0, 3)..Point::new(0, 3)]
 4543        );
 4544
 4545        // When multiple lines are selected, remove newlines that are spanned by the selection
 4546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4547            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4548        });
 4549        editor.join_lines(&JoinLines, window, cx);
 4550        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4551        assert_eq!(
 4552            editor
 4553                .selections
 4554                .ranges::<Point>(&editor.display_snapshot(cx)),
 4555            &[Point::new(0, 11)..Point::new(0, 11)]
 4556        );
 4557
 4558        // Undo should be transactional
 4559        editor.undo(&Undo, window, cx);
 4560        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4561        assert_eq!(
 4562            editor
 4563                .selections
 4564                .ranges::<Point>(&editor.display_snapshot(cx)),
 4565            &[Point::new(0, 5)..Point::new(2, 2)]
 4566        );
 4567
 4568        // When joining an empty line don't insert a space
 4569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4570            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4571        });
 4572        editor.join_lines(&JoinLines, window, cx);
 4573        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4574        assert_eq!(
 4575            editor
 4576                .selections
 4577                .ranges::<Point>(&editor.display_snapshot(cx)),
 4578            [Point::new(2, 3)..Point::new(2, 3)]
 4579        );
 4580
 4581        // We can remove trailing newlines
 4582        editor.join_lines(&JoinLines, window, cx);
 4583        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4584        assert_eq!(
 4585            editor
 4586                .selections
 4587                .ranges::<Point>(&editor.display_snapshot(cx)),
 4588            [Point::new(2, 3)..Point::new(2, 3)]
 4589        );
 4590
 4591        // We don't blow up on the last line
 4592        editor.join_lines(&JoinLines, window, cx);
 4593        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4594        assert_eq!(
 4595            editor
 4596                .selections
 4597                .ranges::<Point>(&editor.display_snapshot(cx)),
 4598            [Point::new(2, 3)..Point::new(2, 3)]
 4599        );
 4600
 4601        // reset to test indentation
 4602        editor.buffer.update(cx, |buffer, cx| {
 4603            buffer.edit(
 4604                [
 4605                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4606                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4607                ],
 4608                None,
 4609                cx,
 4610            )
 4611        });
 4612
 4613        // We remove any leading spaces
 4614        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4616            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4617        });
 4618        editor.join_lines(&JoinLines, window, cx);
 4619        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4620
 4621        // We don't insert a space for a line containing only spaces
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4624
 4625        // We ignore any leading tabs
 4626        editor.join_lines(&JoinLines, window, cx);
 4627        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4628
 4629        editor
 4630    });
 4631}
 4632
 4633#[gpui::test]
 4634fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4635    init_test(cx, |_| {});
 4636
 4637    cx.add_window(|window, cx| {
 4638        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4639        let mut editor = build_editor(buffer.clone(), window, cx);
 4640        let buffer = buffer.read(cx).as_singleton().unwrap();
 4641
 4642        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4643            s.select_ranges([
 4644                Point::new(0, 2)..Point::new(1, 1),
 4645                Point::new(1, 2)..Point::new(1, 2),
 4646                Point::new(3, 1)..Point::new(3, 2),
 4647            ])
 4648        });
 4649
 4650        editor.join_lines(&JoinLines, window, cx);
 4651        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4652
 4653        assert_eq!(
 4654            editor
 4655                .selections
 4656                .ranges::<Point>(&editor.display_snapshot(cx)),
 4657            [
 4658                Point::new(0, 7)..Point::new(0, 7),
 4659                Point::new(1, 3)..Point::new(1, 3)
 4660            ]
 4661        );
 4662        editor
 4663    });
 4664}
 4665
 4666#[gpui::test]
 4667async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4668    init_test(cx, |_| {});
 4669
 4670    let mut cx = EditorTestContext::new(cx).await;
 4671
 4672    let diff_base = r#"
 4673        Line 0
 4674        Line 1
 4675        Line 2
 4676        Line 3
 4677        "#
 4678    .unindent();
 4679
 4680    cx.set_state(
 4681        &r#"
 4682        ˇLine 0
 4683        Line 1
 4684        Line 2
 4685        Line 3
 4686        "#
 4687        .unindent(),
 4688    );
 4689
 4690    cx.set_head_text(&diff_base);
 4691    executor.run_until_parked();
 4692
 4693    // Join lines
 4694    cx.update_editor(|editor, window, cx| {
 4695        editor.join_lines(&JoinLines, window, cx);
 4696    });
 4697    executor.run_until_parked();
 4698
 4699    cx.assert_editor_state(
 4700        &r#"
 4701        Line 0ˇ Line 1
 4702        Line 2
 4703        Line 3
 4704        "#
 4705        .unindent(),
 4706    );
 4707    // Join again
 4708    cx.update_editor(|editor, window, cx| {
 4709        editor.join_lines(&JoinLines, window, cx);
 4710    });
 4711    executor.run_until_parked();
 4712
 4713    cx.assert_editor_state(
 4714        &r#"
 4715        Line 0 Line 1ˇ Line 2
 4716        Line 3
 4717        "#
 4718        .unindent(),
 4719    );
 4720}
 4721
 4722#[gpui::test]
 4723async fn test_custom_newlines_cause_no_false_positive_diffs(
 4724    executor: BackgroundExecutor,
 4725    cx: &mut TestAppContext,
 4726) {
 4727    init_test(cx, |_| {});
 4728    let mut cx = EditorTestContext::new(cx).await;
 4729    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4730    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4731    executor.run_until_parked();
 4732
 4733    cx.update_editor(|editor, window, cx| {
 4734        let snapshot = editor.snapshot(window, cx);
 4735        assert_eq!(
 4736            snapshot
 4737                .buffer_snapshot()
 4738                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4739                .collect::<Vec<_>>(),
 4740            Vec::new(),
 4741            "Should not have any diffs for files with custom newlines"
 4742        );
 4743    });
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    // Test sort_lines_case_insensitive()
 4753    cx.set_state(indoc! {"
 4754        «z
 4755        y
 4756        x
 4757        Z
 4758        Y
 4759        Xˇ»
 4760    "});
 4761    cx.update_editor(|e, window, cx| {
 4762        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4763    });
 4764    cx.assert_editor_state(indoc! {"
 4765        «x
 4766        X
 4767        y
 4768        Y
 4769        z
 4770        Zˇ»
 4771    "});
 4772
 4773    // Test sort_lines_by_length()
 4774    //
 4775    // Demonstrates:
 4776    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4777    // - sort is stable
 4778    cx.set_state(indoc! {"
 4779        «123
 4780        æ
 4781        12
 4782 4783        1
 4784        æˇ»
 4785    "});
 4786    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4787    cx.assert_editor_state(indoc! {"
 4788        «æ
 4789 4790        1
 4791        æ
 4792        12
 4793        123ˇ»
 4794    "});
 4795
 4796    // Test reverse_lines()
 4797    cx.set_state(indoc! {"
 4798        «5
 4799        4
 4800        3
 4801        2
 4802        1ˇ»
 4803    "});
 4804    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4805    cx.assert_editor_state(indoc! {"
 4806        «1
 4807        2
 4808        3
 4809        4
 4810        5ˇ»
 4811    "});
 4812
 4813    // Skip testing shuffle_line()
 4814
 4815    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4816    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4817
 4818    // Don't manipulate when cursor is on single line, but expand the selection
 4819    cx.set_state(indoc! {"
 4820        ddˇdd
 4821        ccc
 4822        bb
 4823        a
 4824    "});
 4825    cx.update_editor(|e, window, cx| {
 4826        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4827    });
 4828    cx.assert_editor_state(indoc! {"
 4829        «ddddˇ»
 4830        ccc
 4831        bb
 4832        a
 4833    "});
 4834
 4835    // Basic manipulate case
 4836    // Start selection moves to column 0
 4837    // End of selection shrinks to fit shorter line
 4838    cx.set_state(indoc! {"
 4839        dd«d
 4840        ccc
 4841        bb
 4842        aaaaaˇ»
 4843    "});
 4844    cx.update_editor(|e, window, cx| {
 4845        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4846    });
 4847    cx.assert_editor_state(indoc! {"
 4848        «aaaaa
 4849        bb
 4850        ccc
 4851        dddˇ»
 4852    "});
 4853
 4854    // Manipulate case with newlines
 4855    cx.set_state(indoc! {"
 4856        dd«d
 4857        ccc
 4858
 4859        bb
 4860        aaaaa
 4861
 4862        ˇ»
 4863    "});
 4864    cx.update_editor(|e, window, cx| {
 4865        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4866    });
 4867    cx.assert_editor_state(indoc! {"
 4868        «
 4869
 4870        aaaaa
 4871        bb
 4872        ccc
 4873        dddˇ»
 4874
 4875    "});
 4876
 4877    // Adding new line
 4878    cx.set_state(indoc! {"
 4879        aa«a
 4880        bbˇ»b
 4881    "});
 4882    cx.update_editor(|e, window, cx| {
 4883        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4884    });
 4885    cx.assert_editor_state(indoc! {"
 4886        «aaa
 4887        bbb
 4888        added_lineˇ»
 4889    "});
 4890
 4891    // Removing line
 4892    cx.set_state(indoc! {"
 4893        aa«a
 4894        bbbˇ»
 4895    "});
 4896    cx.update_editor(|e, window, cx| {
 4897        e.manipulate_immutable_lines(window, cx, |lines| {
 4898            lines.pop();
 4899        })
 4900    });
 4901    cx.assert_editor_state(indoc! {"
 4902        «aaaˇ»
 4903    "});
 4904
 4905    // Removing all lines
 4906    cx.set_state(indoc! {"
 4907        aa«a
 4908        bbbˇ»
 4909    "});
 4910    cx.update_editor(|e, window, cx| {
 4911        e.manipulate_immutable_lines(window, cx, |lines| {
 4912            lines.drain(..);
 4913        })
 4914    });
 4915    cx.assert_editor_state(indoc! {"
 4916        ˇ
 4917    "});
 4918}
 4919
 4920#[gpui::test]
 4921async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4922    init_test(cx, |_| {});
 4923
 4924    let mut cx = EditorTestContext::new(cx).await;
 4925
 4926    // Consider continuous selection as single selection
 4927    cx.set_state(indoc! {"
 4928        Aaa«aa
 4929        cˇ»c«c
 4930        bb
 4931        aaaˇ»aa
 4932    "});
 4933    cx.update_editor(|e, window, cx| {
 4934        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4935    });
 4936    cx.assert_editor_state(indoc! {"
 4937        «Aaaaa
 4938        ccc
 4939        bb
 4940        aaaaaˇ»
 4941    "});
 4942
 4943    cx.set_state(indoc! {"
 4944        Aaa«aa
 4945        cˇ»c«c
 4946        bb
 4947        aaaˇ»aa
 4948    "});
 4949    cx.update_editor(|e, window, cx| {
 4950        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4951    });
 4952    cx.assert_editor_state(indoc! {"
 4953        «Aaaaa
 4954        ccc
 4955        bbˇ»
 4956    "});
 4957
 4958    // Consider non continuous selection as distinct dedup operations
 4959    cx.set_state(indoc! {"
 4960        «aaaaa
 4961        bb
 4962        aaaaa
 4963        aaaaaˇ»
 4964
 4965        aaa«aaˇ»
 4966    "});
 4967    cx.update_editor(|e, window, cx| {
 4968        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4969    });
 4970    cx.assert_editor_state(indoc! {"
 4971        «aaaaa
 4972        bbˇ»
 4973
 4974        «aaaaaˇ»
 4975    "});
 4976}
 4977
 4978#[gpui::test]
 4979async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4980    init_test(cx, |_| {});
 4981
 4982    let mut cx = EditorTestContext::new(cx).await;
 4983
 4984    cx.set_state(indoc! {"
 4985        «Aaa
 4986        aAa
 4987        Aaaˇ»
 4988    "});
 4989    cx.update_editor(|e, window, cx| {
 4990        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4991    });
 4992    cx.assert_editor_state(indoc! {"
 4993        «Aaa
 4994        aAaˇ»
 4995    "});
 4996
 4997    cx.set_state(indoc! {"
 4998        «Aaa
 4999        aAa
 5000        aaAˇ»
 5001    "});
 5002    cx.update_editor(|e, window, cx| {
 5003        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5004    });
 5005    cx.assert_editor_state(indoc! {"
 5006        «Aaaˇ»
 5007    "});
 5008}
 5009
 5010#[gpui::test]
 5011async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5012    init_test(cx, |_| {});
 5013
 5014    let mut cx = EditorTestContext::new(cx).await;
 5015
 5016    let js_language = Arc::new(Language::new(
 5017        LanguageConfig {
 5018            name: "JavaScript".into(),
 5019            wrap_characters: Some(language::WrapCharactersConfig {
 5020                start_prefix: "<".into(),
 5021                start_suffix: ">".into(),
 5022                end_prefix: "</".into(),
 5023                end_suffix: ">".into(),
 5024            }),
 5025            ..LanguageConfig::default()
 5026        },
 5027        None,
 5028    ));
 5029
 5030    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5031
 5032    cx.set_state(indoc! {"
 5033        «testˇ»
 5034    "});
 5035    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5036    cx.assert_editor_state(indoc! {"
 5037        <«ˇ»>test</«ˇ»>
 5038    "});
 5039
 5040    cx.set_state(indoc! {"
 5041        «test
 5042         testˇ»
 5043    "});
 5044    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5045    cx.assert_editor_state(indoc! {"
 5046        <«ˇ»>test
 5047         test</«ˇ»>
 5048    "});
 5049
 5050    cx.set_state(indoc! {"
 5051        teˇst
 5052    "});
 5053    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5054    cx.assert_editor_state(indoc! {"
 5055        te<«ˇ»></«ˇ»>st
 5056    "});
 5057}
 5058
 5059#[gpui::test]
 5060async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5061    init_test(cx, |_| {});
 5062
 5063    let mut cx = EditorTestContext::new(cx).await;
 5064
 5065    let js_language = Arc::new(Language::new(
 5066        LanguageConfig {
 5067            name: "JavaScript".into(),
 5068            wrap_characters: Some(language::WrapCharactersConfig {
 5069                start_prefix: "<".into(),
 5070                start_suffix: ">".into(),
 5071                end_prefix: "</".into(),
 5072                end_suffix: ">".into(),
 5073            }),
 5074            ..LanguageConfig::default()
 5075        },
 5076        None,
 5077    ));
 5078
 5079    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5080
 5081    cx.set_state(indoc! {"
 5082        «testˇ»
 5083        «testˇ» «testˇ»
 5084        «testˇ»
 5085    "});
 5086    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5087    cx.assert_editor_state(indoc! {"
 5088        <«ˇ»>test</«ˇ»>
 5089        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5090        <«ˇ»>test</«ˇ»>
 5091    "});
 5092
 5093    cx.set_state(indoc! {"
 5094        «test
 5095         testˇ»
 5096        «test
 5097         testˇ»
 5098    "});
 5099    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5100    cx.assert_editor_state(indoc! {"
 5101        <«ˇ»>test
 5102         test</«ˇ»>
 5103        <«ˇ»>test
 5104         test</«ˇ»>
 5105    "});
 5106}
 5107
 5108#[gpui::test]
 5109async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5110    init_test(cx, |_| {});
 5111
 5112    let mut cx = EditorTestContext::new(cx).await;
 5113
 5114    let plaintext_language = Arc::new(Language::new(
 5115        LanguageConfig {
 5116            name: "Plain Text".into(),
 5117            ..LanguageConfig::default()
 5118        },
 5119        None,
 5120    ));
 5121
 5122    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5123
 5124    cx.set_state(indoc! {"
 5125        «testˇ»
 5126    "});
 5127    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5128    cx.assert_editor_state(indoc! {"
 5129      «testˇ»
 5130    "});
 5131}
 5132
 5133#[gpui::test]
 5134async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5135    init_test(cx, |_| {});
 5136
 5137    let mut cx = EditorTestContext::new(cx).await;
 5138
 5139    // Manipulate with multiple selections on a single line
 5140    cx.set_state(indoc! {"
 5141        dd«dd
 5142        cˇ»c«c
 5143        bb
 5144        aaaˇ»aa
 5145    "});
 5146    cx.update_editor(|e, window, cx| {
 5147        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5148    });
 5149    cx.assert_editor_state(indoc! {"
 5150        «aaaaa
 5151        bb
 5152        ccc
 5153        ddddˇ»
 5154    "});
 5155
 5156    // Manipulate with multiple disjoin selections
 5157    cx.set_state(indoc! {"
 5158 5159        4
 5160        3
 5161        2
 5162        1ˇ»
 5163
 5164        dd«dd
 5165        ccc
 5166        bb
 5167        aaaˇ»aa
 5168    "});
 5169    cx.update_editor(|e, window, cx| {
 5170        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5171    });
 5172    cx.assert_editor_state(indoc! {"
 5173        «1
 5174        2
 5175        3
 5176        4
 5177        5ˇ»
 5178
 5179        «aaaaa
 5180        bb
 5181        ccc
 5182        ddddˇ»
 5183    "});
 5184
 5185    // Adding lines on each selection
 5186    cx.set_state(indoc! {"
 5187 5188        1ˇ»
 5189
 5190        bb«bb
 5191        aaaˇ»aa
 5192    "});
 5193    cx.update_editor(|e, window, cx| {
 5194        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5195    });
 5196    cx.assert_editor_state(indoc! {"
 5197        «2
 5198        1
 5199        added lineˇ»
 5200
 5201        «bbbb
 5202        aaaaa
 5203        added lineˇ»
 5204    "});
 5205
 5206    // Removing lines on each selection
 5207    cx.set_state(indoc! {"
 5208 5209        1ˇ»
 5210
 5211        bb«bb
 5212        aaaˇ»aa
 5213    "});
 5214    cx.update_editor(|e, window, cx| {
 5215        e.manipulate_immutable_lines(window, cx, |lines| {
 5216            lines.pop();
 5217        })
 5218    });
 5219    cx.assert_editor_state(indoc! {"
 5220        «2ˇ»
 5221
 5222        «bbbbˇ»
 5223    "});
 5224}
 5225
 5226#[gpui::test]
 5227async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5228    init_test(cx, |settings| {
 5229        settings.defaults.tab_size = NonZeroU32::new(3)
 5230    });
 5231
 5232    let mut cx = EditorTestContext::new(cx).await;
 5233
 5234    // MULTI SELECTION
 5235    // Ln.1 "«" tests empty lines
 5236    // Ln.9 tests just leading whitespace
 5237    cx.set_state(indoc! {"
 5238        «
 5239        abc                 // No indentationˇ»
 5240        «\tabc              // 1 tabˇ»
 5241        \t\tabc «      ˇ»   // 2 tabs
 5242        \t ab«c             // Tab followed by space
 5243         \tabc              // Space followed by tab (3 spaces should be the result)
 5244        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5245           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5246        \t
 5247        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5248    "});
 5249    cx.update_editor(|e, window, cx| {
 5250        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5251    });
 5252    cx.assert_editor_state(
 5253        indoc! {"
 5254            «
 5255            abc                 // No indentation
 5256               abc              // 1 tab
 5257                  abc          // 2 tabs
 5258                abc             // Tab followed by space
 5259               abc              // Space followed by tab (3 spaces should be the result)
 5260                           abc   // Mixed indentation (tab conversion depends on the column)
 5261               abc         // Already space indented
 5262               ·
 5263               abc\tdef          // Only the leading tab is manipulatedˇ»
 5264        "}
 5265        .replace("·", "")
 5266        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5267    );
 5268
 5269    // Test on just a few lines, the others should remain unchanged
 5270    // Only lines (3, 5, 10, 11) should change
 5271    cx.set_state(
 5272        indoc! {"
 5273            ·
 5274            abc                 // No indentation
 5275            \tabcˇ               // 1 tab
 5276            \t\tabc             // 2 tabs
 5277            \t abcˇ              // Tab followed by space
 5278             \tabc              // Space followed by tab (3 spaces should be the result)
 5279            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5280               abc              // Already space indented
 5281            «\t
 5282            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5283        "}
 5284        .replace("·", "")
 5285        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5286    );
 5287    cx.update_editor(|e, window, cx| {
 5288        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5289    });
 5290    cx.assert_editor_state(
 5291        indoc! {"
 5292            ·
 5293            abc                 // No indentation
 5294            «   abc               // 1 tabˇ»
 5295            \t\tabc             // 2 tabs
 5296            «    abc              // Tab followed by spaceˇ»
 5297             \tabc              // Space followed by tab (3 spaces should be the result)
 5298            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5299               abc              // Already space indented
 5300            «   ·
 5301               abc\tdef          // Only the leading tab is manipulatedˇ»
 5302        "}
 5303        .replace("·", "")
 5304        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5305    );
 5306
 5307    // SINGLE SELECTION
 5308    // Ln.1 "«" tests empty lines
 5309    // Ln.9 tests just leading whitespace
 5310    cx.set_state(indoc! {"
 5311        «
 5312        abc                 // No indentation
 5313        \tabc               // 1 tab
 5314        \t\tabc             // 2 tabs
 5315        \t abc              // Tab followed by space
 5316         \tabc              // Space followed by tab (3 spaces should be the result)
 5317        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5318           abc              // Already space indented
 5319        \t
 5320        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5321    "});
 5322    cx.update_editor(|e, window, cx| {
 5323        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5324    });
 5325    cx.assert_editor_state(
 5326        indoc! {"
 5327            «
 5328            abc                 // No indentation
 5329               abc               // 1 tab
 5330                  abc             // 2 tabs
 5331                abc              // Tab followed by space
 5332               abc              // Space followed by tab (3 spaces should be the result)
 5333                           abc   // Mixed indentation (tab conversion depends on the column)
 5334               abc              // Already space indented
 5335               ·
 5336               abc\tdef          // Only the leading tab is manipulatedˇ»
 5337        "}
 5338        .replace("·", "")
 5339        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5340    );
 5341}
 5342
 5343#[gpui::test]
 5344async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5345    init_test(cx, |settings| {
 5346        settings.defaults.tab_size = NonZeroU32::new(3)
 5347    });
 5348
 5349    let mut cx = EditorTestContext::new(cx).await;
 5350
 5351    // MULTI SELECTION
 5352    // Ln.1 "«" tests empty lines
 5353    // Ln.11 tests just leading whitespace
 5354    cx.set_state(indoc! {"
 5355        «
 5356        abˇ»ˇc                 // No indentation
 5357         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5358          abc  «             // 2 spaces (< 3 so dont convert)
 5359           abc              // 3 spaces (convert)
 5360             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5361        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5362        «\t abc              // Tab followed by space
 5363         \tabc              // Space followed by tab (should be consumed due to tab)
 5364        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5365           \tˇ»  «\t
 5366           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5367    "});
 5368    cx.update_editor(|e, window, cx| {
 5369        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5370    });
 5371    cx.assert_editor_state(indoc! {"
 5372        «
 5373        abc                 // No indentation
 5374         abc                // 1 space (< 3 so dont convert)
 5375          abc               // 2 spaces (< 3 so dont convert)
 5376        \tabc              // 3 spaces (convert)
 5377        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5378        \t\t\tabc           // Already tab indented
 5379        \t abc              // Tab followed by space
 5380        \tabc              // Space followed by tab (should be consumed due to tab)
 5381        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5382        \t\t\t
 5383        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5384    "});
 5385
 5386    // Test on just a few lines, the other should remain unchanged
 5387    // Only lines (4, 8, 11, 12) should change
 5388    cx.set_state(
 5389        indoc! {"
 5390            ·
 5391            abc                 // No indentation
 5392             abc                // 1 space (< 3 so dont convert)
 5393              abc               // 2 spaces (< 3 so dont convert)
 5394            «   abc              // 3 spaces (convert)ˇ»
 5395                 abc            // 5 spaces (1 tab + 2 spaces)
 5396            \t\t\tabc           // Already tab indented
 5397            \t abc              // Tab followed by space
 5398             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5399               \t\t  \tabc      // Mixed indentation
 5400            \t \t  \t   \tabc   // Mixed indentation
 5401               \t  \tˇ
 5402            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5403        "}
 5404        .replace("·", "")
 5405        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5406    );
 5407    cx.update_editor(|e, window, cx| {
 5408        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5409    });
 5410    cx.assert_editor_state(
 5411        indoc! {"
 5412            ·
 5413            abc                 // No indentation
 5414             abc                // 1 space (< 3 so dont convert)
 5415              abc               // 2 spaces (< 3 so dont convert)
 5416            «\tabc              // 3 spaces (convert)ˇ»
 5417                 abc            // 5 spaces (1 tab + 2 spaces)
 5418            \t\t\tabc           // Already tab indented
 5419            \t abc              // Tab followed by space
 5420            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5421               \t\t  \tabc      // Mixed indentation
 5422            \t \t  \t   \tabc   // Mixed indentation
 5423            «\t\t\t
 5424            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5425        "}
 5426        .replace("·", "")
 5427        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5428    );
 5429
 5430    // SINGLE SELECTION
 5431    // Ln.1 "«" tests empty lines
 5432    // Ln.11 tests just leading whitespace
 5433    cx.set_state(indoc! {"
 5434        «
 5435        abc                 // No indentation
 5436         abc                // 1 space (< 3 so dont convert)
 5437          abc               // 2 spaces (< 3 so dont convert)
 5438           abc              // 3 spaces (convert)
 5439             abc            // 5 spaces (1 tab + 2 spaces)
 5440        \t\t\tabc           // Already tab indented
 5441        \t abc              // Tab followed by space
 5442         \tabc              // Space followed by tab (should be consumed due to tab)
 5443        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5444           \t  \t
 5445           abc   \t         // Only the leading spaces should be convertedˇ»
 5446    "});
 5447    cx.update_editor(|e, window, cx| {
 5448        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5449    });
 5450    cx.assert_editor_state(indoc! {"
 5451        «
 5452        abc                 // No indentation
 5453         abc                // 1 space (< 3 so dont convert)
 5454          abc               // 2 spaces (< 3 so dont convert)
 5455        \tabc              // 3 spaces (convert)
 5456        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5457        \t\t\tabc           // Already tab indented
 5458        \t abc              // Tab followed by space
 5459        \tabc              // Space followed by tab (should be consumed due to tab)
 5460        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5461        \t\t\t
 5462        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5463    "});
 5464}
 5465
 5466#[gpui::test]
 5467async fn test_toggle_case(cx: &mut TestAppContext) {
 5468    init_test(cx, |_| {});
 5469
 5470    let mut cx = EditorTestContext::new(cx).await;
 5471
 5472    // If all lower case -> upper case
 5473    cx.set_state(indoc! {"
 5474        «hello worldˇ»
 5475    "});
 5476    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5477    cx.assert_editor_state(indoc! {"
 5478        «HELLO WORLDˇ»
 5479    "});
 5480
 5481    // If all upper case -> lower case
 5482    cx.set_state(indoc! {"
 5483        «HELLO WORLDˇ»
 5484    "});
 5485    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5486    cx.assert_editor_state(indoc! {"
 5487        «hello worldˇ»
 5488    "});
 5489
 5490    // If any upper case characters are identified -> lower case
 5491    // This matches JetBrains IDEs
 5492    cx.set_state(indoc! {"
 5493        «hEllo worldˇ»
 5494    "});
 5495    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5496    cx.assert_editor_state(indoc! {"
 5497        «hello worldˇ»
 5498    "});
 5499}
 5500
 5501#[gpui::test]
 5502async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5503    init_test(cx, |_| {});
 5504
 5505    let mut cx = EditorTestContext::new(cx).await;
 5506
 5507    cx.set_state(indoc! {"
 5508        «implement-windows-supportˇ»
 5509    "});
 5510    cx.update_editor(|e, window, cx| {
 5511        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5512    });
 5513    cx.assert_editor_state(indoc! {"
 5514        «Implement windows supportˇ»
 5515    "});
 5516}
 5517
 5518#[gpui::test]
 5519async fn test_manipulate_text(cx: &mut TestAppContext) {
 5520    init_test(cx, |_| {});
 5521
 5522    let mut cx = EditorTestContext::new(cx).await;
 5523
 5524    // Test convert_to_upper_case()
 5525    cx.set_state(indoc! {"
 5526        «hello worldˇ»
 5527    "});
 5528    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5529    cx.assert_editor_state(indoc! {"
 5530        «HELLO WORLDˇ»
 5531    "});
 5532
 5533    // Test convert_to_lower_case()
 5534    cx.set_state(indoc! {"
 5535        «HELLO WORLDˇ»
 5536    "});
 5537    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5538    cx.assert_editor_state(indoc! {"
 5539        «hello worldˇ»
 5540    "});
 5541
 5542    // Test multiple line, single selection case
 5543    cx.set_state(indoc! {"
 5544        «The quick brown
 5545        fox jumps over
 5546        the lazy dogˇ»
 5547    "});
 5548    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5549    cx.assert_editor_state(indoc! {"
 5550        «The Quick Brown
 5551        Fox Jumps Over
 5552        The Lazy Dogˇ»
 5553    "});
 5554
 5555    // Test multiple line, single selection case
 5556    cx.set_state(indoc! {"
 5557        «The quick brown
 5558        fox jumps over
 5559        the lazy dogˇ»
 5560    "});
 5561    cx.update_editor(|e, window, cx| {
 5562        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5563    });
 5564    cx.assert_editor_state(indoc! {"
 5565        «TheQuickBrown
 5566        FoxJumpsOver
 5567        TheLazyDogˇ»
 5568    "});
 5569
 5570    // From here on out, test more complex cases of manipulate_text()
 5571
 5572    // Test no selection case - should affect words cursors are in
 5573    // Cursor at beginning, middle, and end of word
 5574    cx.set_state(indoc! {"
 5575        ˇhello big beauˇtiful worldˇ
 5576    "});
 5577    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5578    cx.assert_editor_state(indoc! {"
 5579        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5580    "});
 5581
 5582    // Test multiple selections on a single line and across multiple lines
 5583    cx.set_state(indoc! {"
 5584        «Theˇ» quick «brown
 5585        foxˇ» jumps «overˇ»
 5586        the «lazyˇ» dog
 5587    "});
 5588    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5589    cx.assert_editor_state(indoc! {"
 5590        «THEˇ» quick «BROWN
 5591        FOXˇ» jumps «OVERˇ»
 5592        the «LAZYˇ» dog
 5593    "});
 5594
 5595    // Test case where text length grows
 5596    cx.set_state(indoc! {"
 5597        «tschüߡ»
 5598    "});
 5599    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5600    cx.assert_editor_state(indoc! {"
 5601        «TSCHÜSSˇ»
 5602    "});
 5603
 5604    // Test to make sure we don't crash when text shrinks
 5605    cx.set_state(indoc! {"
 5606        aaa_bbbˇ
 5607    "});
 5608    cx.update_editor(|e, window, cx| {
 5609        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5610    });
 5611    cx.assert_editor_state(indoc! {"
 5612        «aaaBbbˇ»
 5613    "});
 5614
 5615    // Test to make sure we all aware of the fact that each word can grow and shrink
 5616    // Final selections should be aware of this fact
 5617    cx.set_state(indoc! {"
 5618        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5619    "});
 5620    cx.update_editor(|e, window, cx| {
 5621        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5622    });
 5623    cx.assert_editor_state(indoc! {"
 5624        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5625    "});
 5626
 5627    cx.set_state(indoc! {"
 5628        «hElLo, WoRld!ˇ»
 5629    "});
 5630    cx.update_editor(|e, window, cx| {
 5631        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5632    });
 5633    cx.assert_editor_state(indoc! {"
 5634        «HeLlO, wOrLD!ˇ»
 5635    "});
 5636
 5637    // Test selections with `line_mode() = true`.
 5638    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5639    cx.set_state(indoc! {"
 5640        «The quick brown
 5641        fox jumps over
 5642        tˇ»he lazy dog
 5643    "});
 5644    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5645    cx.assert_editor_state(indoc! {"
 5646        «THE QUICK BROWN
 5647        FOX JUMPS OVER
 5648        THE LAZY DOGˇ»
 5649    "});
 5650}
 5651
 5652#[gpui::test]
 5653fn test_duplicate_line(cx: &mut TestAppContext) {
 5654    init_test(cx, |_| {});
 5655
 5656    let editor = cx.add_window(|window, cx| {
 5657        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5658        build_editor(buffer, window, cx)
 5659    });
 5660    _ = editor.update(cx, |editor, window, cx| {
 5661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5662            s.select_display_ranges([
 5663                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5664                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5665                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5666                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5667            ])
 5668        });
 5669        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5670        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5671        assert_eq!(
 5672            display_ranges(editor, cx),
 5673            vec![
 5674                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5675                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5676                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5677                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5678            ]
 5679        );
 5680    });
 5681
 5682    let editor = cx.add_window(|window, cx| {
 5683        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5684        build_editor(buffer, window, cx)
 5685    });
 5686    _ = editor.update(cx, |editor, window, cx| {
 5687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5688            s.select_display_ranges([
 5689                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5690                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5691            ])
 5692        });
 5693        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5694        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5695        assert_eq!(
 5696            display_ranges(editor, cx),
 5697            vec![
 5698                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5699                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5700            ]
 5701        );
 5702    });
 5703
 5704    // With `duplicate_line_up` the selections move to the duplicated lines,
 5705    // which are inserted above the original lines
 5706    let editor = cx.add_window(|window, cx| {
 5707        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5708        build_editor(buffer, window, cx)
 5709    });
 5710    _ = editor.update(cx, |editor, window, cx| {
 5711        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5712            s.select_display_ranges([
 5713                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5714                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5715                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5716                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5717            ])
 5718        });
 5719        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5720        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5721        assert_eq!(
 5722            display_ranges(editor, cx),
 5723            vec![
 5724                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5725                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5726                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5727                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5728            ]
 5729        );
 5730    });
 5731
 5732    let editor = cx.add_window(|window, cx| {
 5733        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5734        build_editor(buffer, window, cx)
 5735    });
 5736    _ = editor.update(cx, |editor, window, cx| {
 5737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5738            s.select_display_ranges([
 5739                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5740                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5741            ])
 5742        });
 5743        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5744        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5745        assert_eq!(
 5746            display_ranges(editor, cx),
 5747            vec![
 5748                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5749                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5750            ]
 5751        );
 5752    });
 5753
 5754    let editor = cx.add_window(|window, cx| {
 5755        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5756        build_editor(buffer, window, cx)
 5757    });
 5758    _ = editor.update(cx, |editor, window, cx| {
 5759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5760            s.select_display_ranges([
 5761                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5762                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5763            ])
 5764        });
 5765        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5766        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5767        assert_eq!(
 5768            display_ranges(editor, cx),
 5769            vec![
 5770                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5771                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5772            ]
 5773        );
 5774    });
 5775}
 5776
 5777#[gpui::test]
 5778async fn test_rotate_selections(cx: &mut TestAppContext) {
 5779    init_test(cx, |_| {});
 5780
 5781    let mut cx = EditorTestContext::new(cx).await;
 5782
 5783    // Rotate text selections (horizontal)
 5784    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5785    cx.update_editor(|e, window, cx| {
 5786        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5787    });
 5788    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5789    cx.update_editor(|e, window, cx| {
 5790        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5791    });
 5792    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5793
 5794    // Rotate text selections (vertical)
 5795    cx.set_state(indoc! {"
 5796        x=«1ˇ»
 5797        y=«2ˇ»
 5798        z=«3ˇ»
 5799    "});
 5800    cx.update_editor(|e, window, cx| {
 5801        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5802    });
 5803    cx.assert_editor_state(indoc! {"
 5804        x=«3ˇ»
 5805        y=«1ˇ»
 5806        z=«2ˇ»
 5807    "});
 5808    cx.update_editor(|e, window, cx| {
 5809        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5810    });
 5811    cx.assert_editor_state(indoc! {"
 5812        x=«1ˇ»
 5813        y=«2ˇ»
 5814        z=«3ˇ»
 5815    "});
 5816
 5817    // Rotate text selections (vertical, different lengths)
 5818    cx.set_state(indoc! {"
 5819        x=\"«ˇ»\"
 5820        y=\"«aˇ»\"
 5821        z=\"«aaˇ»\"
 5822    "});
 5823    cx.update_editor(|e, window, cx| {
 5824        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5825    });
 5826    cx.assert_editor_state(indoc! {"
 5827        x=\"«aaˇ»\"
 5828        y=\"«ˇ»\"
 5829        z=\"«aˇ»\"
 5830    "});
 5831    cx.update_editor(|e, window, cx| {
 5832        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5833    });
 5834    cx.assert_editor_state(indoc! {"
 5835        x=\"«ˇ»\"
 5836        y=\"«aˇ»\"
 5837        z=\"«aaˇ»\"
 5838    "});
 5839
 5840    // Rotate whole lines (cursor positions preserved)
 5841    cx.set_state(indoc! {"
 5842        ˇline123
 5843        liˇne23
 5844        line3ˇ
 5845    "});
 5846    cx.update_editor(|e, window, cx| {
 5847        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5848    });
 5849    cx.assert_editor_state(indoc! {"
 5850        line3ˇ
 5851        ˇline123
 5852        liˇne23
 5853    "});
 5854    cx.update_editor(|e, window, cx| {
 5855        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5856    });
 5857    cx.assert_editor_state(indoc! {"
 5858        ˇline123
 5859        liˇne23
 5860        line3ˇ
 5861    "});
 5862
 5863    // Rotate whole lines, multiple cursors per line (positions preserved)
 5864    cx.set_state(indoc! {"
 5865        ˇliˇne123
 5866        ˇline23
 5867        ˇline3
 5868    "});
 5869    cx.update_editor(|e, window, cx| {
 5870        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5871    });
 5872    cx.assert_editor_state(indoc! {"
 5873        ˇline3
 5874        ˇliˇne123
 5875        ˇline23
 5876    "});
 5877    cx.update_editor(|e, window, cx| {
 5878        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5879    });
 5880    cx.assert_editor_state(indoc! {"
 5881        ˇliˇne123
 5882        ˇline23
 5883        ˇline3
 5884    "});
 5885}
 5886
 5887#[gpui::test]
 5888fn test_move_line_up_down(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890
 5891    let editor = cx.add_window(|window, cx| {
 5892        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5893        build_editor(buffer, window, cx)
 5894    });
 5895    _ = editor.update(cx, |editor, window, cx| {
 5896        editor.fold_creases(
 5897            vec![
 5898                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5899                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5900                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5901            ],
 5902            true,
 5903            window,
 5904            cx,
 5905        );
 5906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5907            s.select_display_ranges([
 5908                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5909                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5910                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5911                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5912            ])
 5913        });
 5914        assert_eq!(
 5915            editor.display_text(cx),
 5916            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5917        );
 5918
 5919        editor.move_line_up(&MoveLineUp, window, cx);
 5920        assert_eq!(
 5921            editor.display_text(cx),
 5922            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5923        );
 5924        assert_eq!(
 5925            display_ranges(editor, cx),
 5926            vec![
 5927                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5928                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5929                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5930                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5931            ]
 5932        );
 5933    });
 5934
 5935    _ = editor.update(cx, |editor, window, cx| {
 5936        editor.move_line_down(&MoveLineDown, window, cx);
 5937        assert_eq!(
 5938            editor.display_text(cx),
 5939            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5940        );
 5941        assert_eq!(
 5942            display_ranges(editor, cx),
 5943            vec![
 5944                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5945                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5946                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5947                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5948            ]
 5949        );
 5950    });
 5951
 5952    _ = editor.update(cx, |editor, window, cx| {
 5953        editor.move_line_down(&MoveLineDown, window, cx);
 5954        assert_eq!(
 5955            editor.display_text(cx),
 5956            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5957        );
 5958        assert_eq!(
 5959            display_ranges(editor, cx),
 5960            vec![
 5961                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5962                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5963                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5964                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5965            ]
 5966        );
 5967    });
 5968
 5969    _ = editor.update(cx, |editor, window, cx| {
 5970        editor.move_line_up(&MoveLineUp, window, cx);
 5971        assert_eq!(
 5972            editor.display_text(cx),
 5973            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5974        );
 5975        assert_eq!(
 5976            display_ranges(editor, cx),
 5977            vec![
 5978                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5979                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5980                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5981                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5982            ]
 5983        );
 5984    });
 5985}
 5986
 5987#[gpui::test]
 5988fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5989    init_test(cx, |_| {});
 5990    let editor = cx.add_window(|window, cx| {
 5991        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5992        build_editor(buffer, window, cx)
 5993    });
 5994    _ = editor.update(cx, |editor, window, cx| {
 5995        editor.fold_creases(
 5996            vec![Crease::simple(
 5997                Point::new(6, 4)..Point::new(7, 4),
 5998                FoldPlaceholder::test(),
 5999            )],
 6000            true,
 6001            window,
 6002            cx,
 6003        );
 6004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6005            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6006        });
 6007        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6008        editor.move_line_up(&MoveLineUp, window, cx);
 6009        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6010        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6011    });
 6012}
 6013
 6014#[gpui::test]
 6015fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6016    init_test(cx, |_| {});
 6017
 6018    let editor = cx.add_window(|window, cx| {
 6019        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6020        build_editor(buffer, window, cx)
 6021    });
 6022    _ = editor.update(cx, |editor, window, cx| {
 6023        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6024        editor.insert_blocks(
 6025            [BlockProperties {
 6026                style: BlockStyle::Fixed,
 6027                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6028                height: Some(1),
 6029                render: Arc::new(|_| div().into_any()),
 6030                priority: 0,
 6031            }],
 6032            Some(Autoscroll::fit()),
 6033            cx,
 6034        );
 6035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6036            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6037        });
 6038        editor.move_line_down(&MoveLineDown, window, cx);
 6039    });
 6040}
 6041
 6042#[gpui::test]
 6043async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6044    init_test(cx, |_| {});
 6045
 6046    let mut cx = EditorTestContext::new(cx).await;
 6047    cx.set_state(
 6048        &"
 6049            ˇzero
 6050            one
 6051            two
 6052            three
 6053            four
 6054            five
 6055        "
 6056        .unindent(),
 6057    );
 6058
 6059    // Create a four-line block that replaces three lines of text.
 6060    cx.update_editor(|editor, window, cx| {
 6061        let snapshot = editor.snapshot(window, cx);
 6062        let snapshot = &snapshot.buffer_snapshot();
 6063        let placement = BlockPlacement::Replace(
 6064            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6065        );
 6066        editor.insert_blocks(
 6067            [BlockProperties {
 6068                placement,
 6069                height: Some(4),
 6070                style: BlockStyle::Sticky,
 6071                render: Arc::new(|_| gpui::div().into_any_element()),
 6072                priority: 0,
 6073            }],
 6074            None,
 6075            cx,
 6076        );
 6077    });
 6078
 6079    // Move down so that the cursor touches the block.
 6080    cx.update_editor(|editor, window, cx| {
 6081        editor.move_down(&Default::default(), window, cx);
 6082    });
 6083    cx.assert_editor_state(
 6084        &"
 6085            zero
 6086            «one
 6087            two
 6088            threeˇ»
 6089            four
 6090            five
 6091        "
 6092        .unindent(),
 6093    );
 6094
 6095    // Move down past the block.
 6096    cx.update_editor(|editor, window, cx| {
 6097        editor.move_down(&Default::default(), window, cx);
 6098    });
 6099    cx.assert_editor_state(
 6100        &"
 6101            zero
 6102            one
 6103            two
 6104            three
 6105            ˇfour
 6106            five
 6107        "
 6108        .unindent(),
 6109    );
 6110}
 6111
 6112#[gpui::test]
 6113fn test_transpose(cx: &mut TestAppContext) {
 6114    init_test(cx, |_| {});
 6115
 6116    _ = cx.add_window(|window, cx| {
 6117        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6118        editor.set_style(EditorStyle::default(), window, cx);
 6119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6120            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6121        });
 6122        editor.transpose(&Default::default(), window, cx);
 6123        assert_eq!(editor.text(cx), "bac");
 6124        assert_eq!(
 6125            editor.selections.ranges(&editor.display_snapshot(cx)),
 6126            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6127        );
 6128
 6129        editor.transpose(&Default::default(), window, cx);
 6130        assert_eq!(editor.text(cx), "bca");
 6131        assert_eq!(
 6132            editor.selections.ranges(&editor.display_snapshot(cx)),
 6133            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6134        );
 6135
 6136        editor.transpose(&Default::default(), window, cx);
 6137        assert_eq!(editor.text(cx), "bac");
 6138        assert_eq!(
 6139            editor.selections.ranges(&editor.display_snapshot(cx)),
 6140            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6141        );
 6142
 6143        editor
 6144    });
 6145
 6146    _ = cx.add_window(|window, cx| {
 6147        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6148        editor.set_style(EditorStyle::default(), window, cx);
 6149        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6150            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6151        });
 6152        editor.transpose(&Default::default(), window, cx);
 6153        assert_eq!(editor.text(cx), "acb\nde");
 6154        assert_eq!(
 6155            editor.selections.ranges(&editor.display_snapshot(cx)),
 6156            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6157        );
 6158
 6159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6160            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6161        });
 6162        editor.transpose(&Default::default(), window, cx);
 6163        assert_eq!(editor.text(cx), "acbd\ne");
 6164        assert_eq!(
 6165            editor.selections.ranges(&editor.display_snapshot(cx)),
 6166            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6167        );
 6168
 6169        editor.transpose(&Default::default(), window, cx);
 6170        assert_eq!(editor.text(cx), "acbde\n");
 6171        assert_eq!(
 6172            editor.selections.ranges(&editor.display_snapshot(cx)),
 6173            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6174        );
 6175
 6176        editor.transpose(&Default::default(), window, cx);
 6177        assert_eq!(editor.text(cx), "acbd\ne");
 6178        assert_eq!(
 6179            editor.selections.ranges(&editor.display_snapshot(cx)),
 6180            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6181        );
 6182
 6183        editor
 6184    });
 6185
 6186    _ = cx.add_window(|window, cx| {
 6187        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6188        editor.set_style(EditorStyle::default(), window, cx);
 6189        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6190            s.select_ranges([
 6191                MultiBufferOffset(1)..MultiBufferOffset(1),
 6192                MultiBufferOffset(2)..MultiBufferOffset(2),
 6193                MultiBufferOffset(4)..MultiBufferOffset(4),
 6194            ])
 6195        });
 6196        editor.transpose(&Default::default(), window, cx);
 6197        assert_eq!(editor.text(cx), "bacd\ne");
 6198        assert_eq!(
 6199            editor.selections.ranges(&editor.display_snapshot(cx)),
 6200            [
 6201                MultiBufferOffset(2)..MultiBufferOffset(2),
 6202                MultiBufferOffset(3)..MultiBufferOffset(3),
 6203                MultiBufferOffset(5)..MultiBufferOffset(5)
 6204            ]
 6205        );
 6206
 6207        editor.transpose(&Default::default(), window, cx);
 6208        assert_eq!(editor.text(cx), "bcade\n");
 6209        assert_eq!(
 6210            editor.selections.ranges(&editor.display_snapshot(cx)),
 6211            [
 6212                MultiBufferOffset(3)..MultiBufferOffset(3),
 6213                MultiBufferOffset(4)..MultiBufferOffset(4),
 6214                MultiBufferOffset(6)..MultiBufferOffset(6)
 6215            ]
 6216        );
 6217
 6218        editor.transpose(&Default::default(), window, cx);
 6219        assert_eq!(editor.text(cx), "bcda\ne");
 6220        assert_eq!(
 6221            editor.selections.ranges(&editor.display_snapshot(cx)),
 6222            [
 6223                MultiBufferOffset(4)..MultiBufferOffset(4),
 6224                MultiBufferOffset(6)..MultiBufferOffset(6)
 6225            ]
 6226        );
 6227
 6228        editor.transpose(&Default::default(), window, cx);
 6229        assert_eq!(editor.text(cx), "bcade\n");
 6230        assert_eq!(
 6231            editor.selections.ranges(&editor.display_snapshot(cx)),
 6232            [
 6233                MultiBufferOffset(4)..MultiBufferOffset(4),
 6234                MultiBufferOffset(6)..MultiBufferOffset(6)
 6235            ]
 6236        );
 6237
 6238        editor.transpose(&Default::default(), window, cx);
 6239        assert_eq!(editor.text(cx), "bcaed\n");
 6240        assert_eq!(
 6241            editor.selections.ranges(&editor.display_snapshot(cx)),
 6242            [
 6243                MultiBufferOffset(5)..MultiBufferOffset(5),
 6244                MultiBufferOffset(6)..MultiBufferOffset(6)
 6245            ]
 6246        );
 6247
 6248        editor
 6249    });
 6250
 6251    _ = cx.add_window(|window, cx| {
 6252        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6253        editor.set_style(EditorStyle::default(), window, cx);
 6254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6255            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6256        });
 6257        editor.transpose(&Default::default(), window, cx);
 6258        assert_eq!(editor.text(cx), "🏀🍐✋");
 6259        assert_eq!(
 6260            editor.selections.ranges(&editor.display_snapshot(cx)),
 6261            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6262        );
 6263
 6264        editor.transpose(&Default::default(), window, cx);
 6265        assert_eq!(editor.text(cx), "🏀✋🍐");
 6266        assert_eq!(
 6267            editor.selections.ranges(&editor.display_snapshot(cx)),
 6268            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6269        );
 6270
 6271        editor.transpose(&Default::default(), window, cx);
 6272        assert_eq!(editor.text(cx), "🏀🍐✋");
 6273        assert_eq!(
 6274            editor.selections.ranges(&editor.display_snapshot(cx)),
 6275            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6276        );
 6277
 6278        editor
 6279    });
 6280}
 6281
 6282#[gpui::test]
 6283async fn test_rewrap(cx: &mut TestAppContext) {
 6284    init_test(cx, |settings| {
 6285        settings.languages.0.extend([
 6286            (
 6287                "Markdown".into(),
 6288                LanguageSettingsContent {
 6289                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6290                    preferred_line_length: Some(40),
 6291                    ..Default::default()
 6292                },
 6293            ),
 6294            (
 6295                "Plain Text".into(),
 6296                LanguageSettingsContent {
 6297                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6298                    preferred_line_length: Some(40),
 6299                    ..Default::default()
 6300                },
 6301            ),
 6302            (
 6303                "C++".into(),
 6304                LanguageSettingsContent {
 6305                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6306                    preferred_line_length: Some(40),
 6307                    ..Default::default()
 6308                },
 6309            ),
 6310            (
 6311                "Python".into(),
 6312                LanguageSettingsContent {
 6313                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6314                    preferred_line_length: Some(40),
 6315                    ..Default::default()
 6316                },
 6317            ),
 6318            (
 6319                "Rust".into(),
 6320                LanguageSettingsContent {
 6321                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6322                    preferred_line_length: Some(40),
 6323                    ..Default::default()
 6324                },
 6325            ),
 6326        ])
 6327    });
 6328
 6329    let mut cx = EditorTestContext::new(cx).await;
 6330
 6331    let cpp_language = Arc::new(Language::new(
 6332        LanguageConfig {
 6333            name: "C++".into(),
 6334            line_comments: vec!["// ".into()],
 6335            ..LanguageConfig::default()
 6336        },
 6337        None,
 6338    ));
 6339    let python_language = Arc::new(Language::new(
 6340        LanguageConfig {
 6341            name: "Python".into(),
 6342            line_comments: vec!["# ".into()],
 6343            ..LanguageConfig::default()
 6344        },
 6345        None,
 6346    ));
 6347    let markdown_language = Arc::new(Language::new(
 6348        LanguageConfig {
 6349            name: "Markdown".into(),
 6350            rewrap_prefixes: vec![
 6351                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6352                regex::Regex::new("[-*+]\\s+").unwrap(),
 6353            ],
 6354            ..LanguageConfig::default()
 6355        },
 6356        None,
 6357    ));
 6358    let rust_language = Arc::new(
 6359        Language::new(
 6360            LanguageConfig {
 6361                name: "Rust".into(),
 6362                line_comments: vec!["// ".into(), "/// ".into()],
 6363                ..LanguageConfig::default()
 6364            },
 6365            Some(tree_sitter_rust::LANGUAGE.into()),
 6366        )
 6367        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6368        .unwrap(),
 6369    );
 6370
 6371    let plaintext_language = Arc::new(Language::new(
 6372        LanguageConfig {
 6373            name: "Plain Text".into(),
 6374            ..LanguageConfig::default()
 6375        },
 6376        None,
 6377    ));
 6378
 6379    // Test basic rewrapping of a long line with a cursor
 6380    assert_rewrap(
 6381        indoc! {"
 6382            // ˇThis is a long comment that needs to be wrapped.
 6383        "},
 6384        indoc! {"
 6385            // ˇThis is a long comment that needs to
 6386            // be wrapped.
 6387        "},
 6388        cpp_language.clone(),
 6389        &mut cx,
 6390    );
 6391
 6392    // Test rewrapping a full selection
 6393    assert_rewrap(
 6394        indoc! {"
 6395            «// This selected long comment needs to be wrapped.ˇ»"
 6396        },
 6397        indoc! {"
 6398            «// This selected long comment needs to
 6399            // be wrapped.ˇ»"
 6400        },
 6401        cpp_language.clone(),
 6402        &mut cx,
 6403    );
 6404
 6405    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6406    assert_rewrap(
 6407        indoc! {"
 6408            // ˇThis is the first line.
 6409            // Thisˇ is the second line.
 6410            // This is the thirdˇ line, all part of one paragraph.
 6411         "},
 6412        indoc! {"
 6413            // ˇThis is the first line. Thisˇ is the
 6414            // second line. This is the thirdˇ line,
 6415            // all part of one paragraph.
 6416         "},
 6417        cpp_language.clone(),
 6418        &mut cx,
 6419    );
 6420
 6421    // Test multiple cursors in different paragraphs trigger separate rewraps
 6422    assert_rewrap(
 6423        indoc! {"
 6424            // ˇThis is the first paragraph, first line.
 6425            // ˇThis is the first paragraph, second line.
 6426
 6427            // ˇThis is the second paragraph, first line.
 6428            // ˇThis is the second paragraph, second line.
 6429        "},
 6430        indoc! {"
 6431            // ˇThis is the first paragraph, first
 6432            // line. ˇThis is the first paragraph,
 6433            // second line.
 6434
 6435            // ˇThis is the second paragraph, first
 6436            // line. ˇThis is the second paragraph,
 6437            // second line.
 6438        "},
 6439        cpp_language.clone(),
 6440        &mut cx,
 6441    );
 6442
 6443    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6444    assert_rewrap(
 6445        indoc! {"
 6446            «// A regular long long comment to be wrapped.
 6447            /// A documentation long comment to be wrapped.ˇ»
 6448          "},
 6449        indoc! {"
 6450            «// A regular long long comment to be
 6451            // wrapped.
 6452            /// A documentation long comment to be
 6453            /// wrapped.ˇ»
 6454          "},
 6455        rust_language.clone(),
 6456        &mut cx,
 6457    );
 6458
 6459    // Test that change in indentation level trigger seperate rewraps
 6460    assert_rewrap(
 6461        indoc! {"
 6462            fn foo() {
 6463                «// This is a long comment at the base indent.
 6464                    // This is a long comment at the next indent.ˇ»
 6465            }
 6466        "},
 6467        indoc! {"
 6468            fn foo() {
 6469                «// This is a long comment at the
 6470                // base indent.
 6471                    // This is a long comment at the
 6472                    // next indent.ˇ»
 6473            }
 6474        "},
 6475        rust_language.clone(),
 6476        &mut cx,
 6477    );
 6478
 6479    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6480    assert_rewrap(
 6481        indoc! {"
 6482            # ˇThis is a long comment using a pound sign.
 6483        "},
 6484        indoc! {"
 6485            # ˇThis is a long comment using a pound
 6486            # sign.
 6487        "},
 6488        python_language,
 6489        &mut cx,
 6490    );
 6491
 6492    // Test rewrapping only affects comments, not code even when selected
 6493    assert_rewrap(
 6494        indoc! {"
 6495            «/// This doc comment is long and should be wrapped.
 6496            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6497        "},
 6498        indoc! {"
 6499            «/// This doc comment is long and should
 6500            /// be wrapped.
 6501            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6502        "},
 6503        rust_language.clone(),
 6504        &mut cx,
 6505    );
 6506
 6507    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6508    assert_rewrap(
 6509        indoc! {"
 6510            # Header
 6511
 6512            A long long long line of markdown text to wrap.ˇ
 6513         "},
 6514        indoc! {"
 6515            # Header
 6516
 6517            A long long long line of markdown text
 6518            to wrap.ˇ
 6519         "},
 6520        markdown_language.clone(),
 6521        &mut cx,
 6522    );
 6523
 6524    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6525    assert_rewrap(
 6526        indoc! {"
 6527            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6528            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6529            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6530        "},
 6531        indoc! {"
 6532            «1. This is a numbered list item that is
 6533               very long and needs to be wrapped
 6534               properly.
 6535            2. This is a numbered list item that is
 6536               very long and needs to be wrapped
 6537               properly.
 6538            - This is an unordered list item that is
 6539              also very long and should not merge
 6540              with the numbered item.ˇ»
 6541        "},
 6542        markdown_language.clone(),
 6543        &mut cx,
 6544    );
 6545
 6546    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6547    assert_rewrap(
 6548        indoc! {"
 6549            «1. This is a numbered list item that is
 6550            very long and needs to be wrapped
 6551            properly.
 6552            2. This is a numbered list item that is
 6553            very long and needs to be wrapped
 6554            properly.
 6555            - This is an unordered list item that is
 6556            also very long and should not merge with
 6557            the numbered item.ˇ»
 6558        "},
 6559        indoc! {"
 6560            «1. This is a numbered list item that is
 6561               very long and needs to be wrapped
 6562               properly.
 6563            2. This is a numbered list item that is
 6564               very long and needs to be wrapped
 6565               properly.
 6566            - This is an unordered list item that is
 6567              also very long and should not merge
 6568              with the numbered item.ˇ»
 6569        "},
 6570        markdown_language.clone(),
 6571        &mut cx,
 6572    );
 6573
 6574    // Test that rewrapping maintain indents even when they already exists.
 6575    assert_rewrap(
 6576        indoc! {"
 6577            «1. This is a numbered list
 6578               item that is very long and needs to be wrapped properly.
 6579            2. This is a numbered list
 6580               item that is very long and needs to be wrapped properly.
 6581            - This is an unordered list item that is also very long and
 6582              should not merge with the numbered item.ˇ»
 6583        "},
 6584        indoc! {"
 6585            «1. This is a numbered list item that is
 6586               very long and needs to be wrapped
 6587               properly.
 6588            2. This is a numbered list item that is
 6589               very long and needs to be wrapped
 6590               properly.
 6591            - This is an unordered list item that is
 6592              also very long and should not merge
 6593              with the numbered item.ˇ»
 6594        "},
 6595        markdown_language,
 6596        &mut cx,
 6597    );
 6598
 6599    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6600    assert_rewrap(
 6601        indoc! {"
 6602            ˇThis is a very long line of plain text that will be wrapped.
 6603        "},
 6604        indoc! {"
 6605            ˇThis is a very long line of plain text
 6606            that will be wrapped.
 6607        "},
 6608        plaintext_language.clone(),
 6609        &mut cx,
 6610    );
 6611
 6612    // Test that non-commented code acts as a paragraph boundary within a selection
 6613    assert_rewrap(
 6614        indoc! {"
 6615               «// This is the first long comment block to be wrapped.
 6616               fn my_func(a: u32);
 6617               // This is the second long comment block to be wrapped.ˇ»
 6618           "},
 6619        indoc! {"
 6620               «// This is the first long comment block
 6621               // to be wrapped.
 6622               fn my_func(a: u32);
 6623               // This is the second long comment block
 6624               // to be wrapped.ˇ»
 6625           "},
 6626        rust_language,
 6627        &mut cx,
 6628    );
 6629
 6630    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6631    assert_rewrap(
 6632        indoc! {"
 6633            «ˇThis is a very long line that will be wrapped.
 6634
 6635            This is another paragraph in the same selection.»
 6636
 6637            «\tThis is a very long indented line that will be wrapped.ˇ»
 6638         "},
 6639        indoc! {"
 6640            «ˇThis is a very long line that will be
 6641            wrapped.
 6642
 6643            This is another paragraph in the same
 6644            selection.»
 6645
 6646            «\tThis is a very long indented line
 6647            \tthat will be wrapped.ˇ»
 6648         "},
 6649        plaintext_language,
 6650        &mut cx,
 6651    );
 6652
 6653    // Test that an empty comment line acts as a paragraph boundary
 6654    assert_rewrap(
 6655        indoc! {"
 6656            // ˇThis is a long comment that will be wrapped.
 6657            //
 6658            // And this is another long comment that will also be wrapped.ˇ
 6659         "},
 6660        indoc! {"
 6661            // ˇThis is a long comment that will be
 6662            // wrapped.
 6663            //
 6664            // And this is another long comment that
 6665            // will also be wrapped.ˇ
 6666         "},
 6667        cpp_language,
 6668        &mut cx,
 6669    );
 6670
 6671    #[track_caller]
 6672    fn assert_rewrap(
 6673        unwrapped_text: &str,
 6674        wrapped_text: &str,
 6675        language: Arc<Language>,
 6676        cx: &mut EditorTestContext,
 6677    ) {
 6678        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6679        cx.set_state(unwrapped_text);
 6680        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6681        cx.assert_editor_state(wrapped_text);
 6682    }
 6683}
 6684
 6685#[gpui::test]
 6686async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6687    init_test(cx, |settings| {
 6688        settings.languages.0.extend([(
 6689            "Rust".into(),
 6690            LanguageSettingsContent {
 6691                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6692                preferred_line_length: Some(40),
 6693                ..Default::default()
 6694            },
 6695        )])
 6696    });
 6697
 6698    let mut cx = EditorTestContext::new(cx).await;
 6699
 6700    let rust_lang = Arc::new(
 6701        Language::new(
 6702            LanguageConfig {
 6703                name: "Rust".into(),
 6704                line_comments: vec!["// ".into()],
 6705                block_comment: Some(BlockCommentConfig {
 6706                    start: "/*".into(),
 6707                    end: "*/".into(),
 6708                    prefix: "* ".into(),
 6709                    tab_size: 1,
 6710                }),
 6711                documentation_comment: Some(BlockCommentConfig {
 6712                    start: "/**".into(),
 6713                    end: "*/".into(),
 6714                    prefix: "* ".into(),
 6715                    tab_size: 1,
 6716                }),
 6717
 6718                ..LanguageConfig::default()
 6719            },
 6720            Some(tree_sitter_rust::LANGUAGE.into()),
 6721        )
 6722        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6723        .unwrap(),
 6724    );
 6725
 6726    // regular block comment
 6727    assert_rewrap(
 6728        indoc! {"
 6729            /*
 6730             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6731             */
 6732            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6733        "},
 6734        indoc! {"
 6735            /*
 6736             *ˇ Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */
 6739            /*
 6740             *ˇ Lorem ipsum dolor sit amet,
 6741             * consectetur adipiscing elit.
 6742             */
 6743        "},
 6744        rust_lang.clone(),
 6745        &mut cx,
 6746    );
 6747
 6748    // indent is respected
 6749    assert_rewrap(
 6750        indoc! {"
 6751            {}
 6752                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6753        "},
 6754        indoc! {"
 6755            {}
 6756                /*
 6757                 *ˇ Lorem ipsum dolor sit amet,
 6758                 * consectetur adipiscing elit.
 6759                 */
 6760        "},
 6761        rust_lang.clone(),
 6762        &mut cx,
 6763    );
 6764
 6765    // short block comments with inline delimiters
 6766    assert_rewrap(
 6767        indoc! {"
 6768            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6769            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6770             */
 6771            /*
 6772             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6773        "},
 6774        indoc! {"
 6775            /*
 6776             *ˇ Lorem ipsum dolor sit amet,
 6777             * consectetur adipiscing elit.
 6778             */
 6779            /*
 6780             *ˇ Lorem ipsum dolor sit amet,
 6781             * consectetur adipiscing elit.
 6782             */
 6783            /*
 6784             *ˇ Lorem ipsum dolor sit amet,
 6785             * consectetur adipiscing elit.
 6786             */
 6787        "},
 6788        rust_lang.clone(),
 6789        &mut cx,
 6790    );
 6791
 6792    // multiline block comment with inline start/end delimiters
 6793    assert_rewrap(
 6794        indoc! {"
 6795            /*ˇ Lorem ipsum dolor sit amet,
 6796             * consectetur adipiscing elit. */
 6797        "},
 6798        indoc! {"
 6799            /*
 6800             *ˇ Lorem ipsum dolor sit amet,
 6801             * consectetur adipiscing elit.
 6802             */
 6803        "},
 6804        rust_lang.clone(),
 6805        &mut cx,
 6806    );
 6807
 6808    // block comment rewrap still respects paragraph bounds
 6809    assert_rewrap(
 6810        indoc! {"
 6811            /*
 6812             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6813             *
 6814             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6815             */
 6816        "},
 6817        indoc! {"
 6818            /*
 6819             *ˇ Lorem ipsum dolor sit amet,
 6820             * consectetur adipiscing elit.
 6821             *
 6822             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6823             */
 6824        "},
 6825        rust_lang.clone(),
 6826        &mut cx,
 6827    );
 6828
 6829    // documentation comments
 6830    assert_rewrap(
 6831        indoc! {"
 6832            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6833            /**
 6834             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6835             */
 6836        "},
 6837        indoc! {"
 6838            /**
 6839             *ˇ Lorem ipsum dolor sit amet,
 6840             * consectetur adipiscing elit.
 6841             */
 6842            /**
 6843             *ˇ Lorem ipsum dolor sit amet,
 6844             * consectetur adipiscing elit.
 6845             */
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // different, adjacent comments
 6852    assert_rewrap(
 6853        indoc! {"
 6854            /**
 6855             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6856             */
 6857            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6858            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6859        "},
 6860        indoc! {"
 6861            /**
 6862             *ˇ Lorem ipsum dolor sit amet,
 6863             * consectetur adipiscing elit.
 6864             */
 6865            /*
 6866             *ˇ Lorem ipsum dolor sit amet,
 6867             * consectetur adipiscing elit.
 6868             */
 6869            //ˇ Lorem ipsum dolor sit amet,
 6870            // consectetur adipiscing elit.
 6871        "},
 6872        rust_lang.clone(),
 6873        &mut cx,
 6874    );
 6875
 6876    // selection w/ single short block comment
 6877    assert_rewrap(
 6878        indoc! {"
 6879            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6880        "},
 6881        indoc! {"
 6882            «/*
 6883             * Lorem ipsum dolor sit amet,
 6884             * consectetur adipiscing elit.
 6885             */ˇ»
 6886        "},
 6887        rust_lang.clone(),
 6888        &mut cx,
 6889    );
 6890
 6891    // rewrapping a single comment w/ abutting comments
 6892    assert_rewrap(
 6893        indoc! {"
 6894            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6895            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6896        "},
 6897        indoc! {"
 6898            /*
 6899             * ˇLorem ipsum dolor sit amet,
 6900             * consectetur adipiscing elit.
 6901             */
 6902            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6903        "},
 6904        rust_lang.clone(),
 6905        &mut cx,
 6906    );
 6907
 6908    // selection w/ non-abutting short block comments
 6909    assert_rewrap(
 6910        indoc! {"
 6911            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6912
 6913            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6914        "},
 6915        indoc! {"
 6916            «/*
 6917             * Lorem ipsum dolor sit amet,
 6918             * consectetur adipiscing elit.
 6919             */
 6920
 6921            /*
 6922             * Lorem ipsum dolor sit amet,
 6923             * consectetur adipiscing elit.
 6924             */ˇ»
 6925        "},
 6926        rust_lang.clone(),
 6927        &mut cx,
 6928    );
 6929
 6930    // selection of multiline block comments
 6931    assert_rewrap(
 6932        indoc! {"
 6933            «/* Lorem ipsum dolor sit amet,
 6934             * consectetur adipiscing elit. */ˇ»
 6935        "},
 6936        indoc! {"
 6937            «/*
 6938             * Lorem ipsum dolor sit amet,
 6939             * consectetur adipiscing elit.
 6940             */ˇ»
 6941        "},
 6942        rust_lang.clone(),
 6943        &mut cx,
 6944    );
 6945
 6946    // partial selection of multiline block comments
 6947    assert_rewrap(
 6948        indoc! {"
 6949            «/* Lorem ipsum dolor sit amet,ˇ»
 6950             * consectetur adipiscing elit. */
 6951            /* Lorem ipsum dolor sit amet,
 6952             «* consectetur adipiscing elit. */ˇ»
 6953        "},
 6954        indoc! {"
 6955            «/*
 6956             * Lorem ipsum dolor sit amet,ˇ»
 6957             * consectetur adipiscing elit. */
 6958            /* Lorem ipsum dolor sit amet,
 6959             «* consectetur adipiscing elit.
 6960             */ˇ»
 6961        "},
 6962        rust_lang.clone(),
 6963        &mut cx,
 6964    );
 6965
 6966    // selection w/ abutting short block comments
 6967    // TODO: should not be combined; should rewrap as 2 comments
 6968    assert_rewrap(
 6969        indoc! {"
 6970            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6971            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6972        "},
 6973        // desired behavior:
 6974        // indoc! {"
 6975        //     «/*
 6976        //      * Lorem ipsum dolor sit amet,
 6977        //      * consectetur adipiscing elit.
 6978        //      */
 6979        //     /*
 6980        //      * Lorem ipsum dolor sit amet,
 6981        //      * consectetur adipiscing elit.
 6982        //      */ˇ»
 6983        // "},
 6984        // actual behaviour:
 6985        indoc! {"
 6986            «/*
 6987             * Lorem ipsum dolor sit amet,
 6988             * consectetur adipiscing elit. Lorem
 6989             * ipsum dolor sit amet, consectetur
 6990             * adipiscing elit.
 6991             */ˇ»
 6992        "},
 6993        rust_lang.clone(),
 6994        &mut cx,
 6995    );
 6996
 6997    // TODO: same as above, but with delimiters on separate line
 6998    // assert_rewrap(
 6999    //     indoc! {"
 7000    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7001    //          */
 7002    //         /*
 7003    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7004    //     "},
 7005    //     // desired:
 7006    //     // indoc! {"
 7007    //     //     «/*
 7008    //     //      * Lorem ipsum dolor sit amet,
 7009    //     //      * consectetur adipiscing elit.
 7010    //     //      */
 7011    //     //     /*
 7012    //     //      * Lorem ipsum dolor sit amet,
 7013    //     //      * consectetur adipiscing elit.
 7014    //     //      */ˇ»
 7015    //     // "},
 7016    //     // actual: (but with trailing w/s on the empty lines)
 7017    //     indoc! {"
 7018    //         «/*
 7019    //          * Lorem ipsum dolor sit amet,
 7020    //          * consectetur adipiscing elit.
 7021    //          *
 7022    //          */
 7023    //         /*
 7024    //          *
 7025    //          * Lorem ipsum dolor sit amet,
 7026    //          * consectetur adipiscing elit.
 7027    //          */ˇ»
 7028    //     "},
 7029    //     rust_lang.clone(),
 7030    //     &mut cx,
 7031    // );
 7032
 7033    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7034    assert_rewrap(
 7035        indoc! {"
 7036            /*
 7037             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7038             */
 7039            /*
 7040             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7041            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7042        "},
 7043        // desired:
 7044        // indoc! {"
 7045        //     /*
 7046        //      *ˇ Lorem ipsum dolor sit amet,
 7047        //      * consectetur adipiscing elit.
 7048        //      */
 7049        //     /*
 7050        //      *ˇ Lorem ipsum dolor sit amet,
 7051        //      * consectetur adipiscing elit.
 7052        //      */
 7053        //     /*
 7054        //      *ˇ Lorem ipsum dolor sit amet
 7055        //      */ /* consectetur adipiscing elit. */
 7056        // "},
 7057        // actual:
 7058        indoc! {"
 7059            /*
 7060             //ˇ Lorem ipsum dolor sit amet,
 7061             // consectetur adipiscing elit.
 7062             */
 7063            /*
 7064             * //ˇ Lorem ipsum dolor sit amet,
 7065             * consectetur adipiscing elit.
 7066             */
 7067            /*
 7068             *ˇ Lorem ipsum dolor sit amet */ /*
 7069             * consectetur adipiscing elit.
 7070             */
 7071        "},
 7072        rust_lang,
 7073        &mut cx,
 7074    );
 7075
 7076    #[track_caller]
 7077    fn assert_rewrap(
 7078        unwrapped_text: &str,
 7079        wrapped_text: &str,
 7080        language: Arc<Language>,
 7081        cx: &mut EditorTestContext,
 7082    ) {
 7083        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7084        cx.set_state(unwrapped_text);
 7085        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7086        cx.assert_editor_state(wrapped_text);
 7087    }
 7088}
 7089
 7090#[gpui::test]
 7091async fn test_hard_wrap(cx: &mut TestAppContext) {
 7092    init_test(cx, |_| {});
 7093    let mut cx = EditorTestContext::new(cx).await;
 7094
 7095    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7096    cx.update_editor(|editor, _, cx| {
 7097        editor.set_hard_wrap(Some(14), cx);
 7098    });
 7099
 7100    cx.set_state(indoc!(
 7101        "
 7102        one two three ˇ
 7103        "
 7104    ));
 7105    cx.simulate_input("four");
 7106    cx.run_until_parked();
 7107
 7108    cx.assert_editor_state(indoc!(
 7109        "
 7110        one two three
 7111        fourˇ
 7112        "
 7113    ));
 7114
 7115    cx.update_editor(|editor, window, cx| {
 7116        editor.newline(&Default::default(), window, cx);
 7117    });
 7118    cx.run_until_parked();
 7119    cx.assert_editor_state(indoc!(
 7120        "
 7121        one two three
 7122        four
 7123        ˇ
 7124        "
 7125    ));
 7126
 7127    cx.simulate_input("five");
 7128    cx.run_until_parked();
 7129    cx.assert_editor_state(indoc!(
 7130        "
 7131        one two three
 7132        four
 7133        fiveˇ
 7134        "
 7135    ));
 7136
 7137    cx.update_editor(|editor, window, cx| {
 7138        editor.newline(&Default::default(), window, cx);
 7139    });
 7140    cx.run_until_parked();
 7141    cx.simulate_input("# ");
 7142    cx.run_until_parked();
 7143    cx.assert_editor_state(indoc!(
 7144        "
 7145        one two three
 7146        four
 7147        five
 7148        # ˇ
 7149        "
 7150    ));
 7151
 7152    cx.update_editor(|editor, window, cx| {
 7153        editor.newline(&Default::default(), window, cx);
 7154    });
 7155    cx.run_until_parked();
 7156    cx.assert_editor_state(indoc!(
 7157        "
 7158        one two three
 7159        four
 7160        five
 7161        #\x20
 7162 7163        "
 7164    ));
 7165
 7166    cx.simulate_input(" 6");
 7167    cx.run_until_parked();
 7168    cx.assert_editor_state(indoc!(
 7169        "
 7170        one two three
 7171        four
 7172        five
 7173        #
 7174        # 6ˇ
 7175        "
 7176    ));
 7177}
 7178
 7179#[gpui::test]
 7180async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7181    init_test(cx, |_| {});
 7182
 7183    let mut cx = EditorTestContext::new(cx).await;
 7184
 7185    cx.set_state(indoc! {"The quick brownˇ"});
 7186    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7187    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7188
 7189    cx.set_state(indoc! {"The emacs foxˇ"});
 7190    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7191    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7192
 7193    cx.set_state(indoc! {"
 7194        The quick« brownˇ»
 7195        fox jumps overˇ
 7196        the lazy dog"});
 7197    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7198    cx.assert_editor_state(indoc! {"
 7199        The quickˇ
 7200        ˇthe lazy dog"});
 7201
 7202    cx.set_state(indoc! {"
 7203        The quick« brownˇ»
 7204        fox jumps overˇ
 7205        the lazy dog"});
 7206    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7207    cx.assert_editor_state(indoc! {"
 7208        The quickˇ
 7209        fox jumps overˇthe lazy dog"});
 7210
 7211    cx.set_state(indoc! {"
 7212        The quick« brownˇ»
 7213        fox jumps overˇ
 7214        the lazy dog"});
 7215    cx.update_editor(|e, window, cx| {
 7216        e.cut_to_end_of_line(
 7217            &CutToEndOfLine {
 7218                stop_at_newlines: true,
 7219            },
 7220            window,
 7221            cx,
 7222        )
 7223    });
 7224    cx.assert_editor_state(indoc! {"
 7225        The quickˇ
 7226        fox jumps overˇ
 7227        the lazy dog"});
 7228
 7229    cx.set_state(indoc! {"
 7230        The quick« brownˇ»
 7231        fox jumps overˇ
 7232        the lazy dog"});
 7233    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7234    cx.assert_editor_state(indoc! {"
 7235        The quickˇ
 7236        fox jumps overˇthe lazy dog"});
 7237}
 7238
 7239#[gpui::test]
 7240async fn test_clipboard(cx: &mut TestAppContext) {
 7241    init_test(cx, |_| {});
 7242
 7243    let mut cx = EditorTestContext::new(cx).await;
 7244
 7245    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7246    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7247    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7248
 7249    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7250    cx.set_state("two ˇfour ˇsix ˇ");
 7251    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7252    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7253
 7254    // Paste again but with only two cursors. Since the number of cursors doesn't
 7255    // match the number of slices in the clipboard, the entire clipboard text
 7256    // is pasted at each cursor.
 7257    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7258    cx.update_editor(|e, window, cx| {
 7259        e.handle_input("( ", window, cx);
 7260        e.paste(&Paste, window, cx);
 7261        e.handle_input(") ", window, cx);
 7262    });
 7263    cx.assert_editor_state(
 7264        &([
 7265            "( one✅ ",
 7266            "three ",
 7267            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7268            "three ",
 7269            "five ) ˇ",
 7270        ]
 7271        .join("\n")),
 7272    );
 7273
 7274    // Cut with three selections, one of which is full-line.
 7275    cx.set_state(indoc! {"
 7276        1«2ˇ»3
 7277        4ˇ567
 7278        «8ˇ»9"});
 7279    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7280    cx.assert_editor_state(indoc! {"
 7281        1ˇ3
 7282        ˇ9"});
 7283
 7284    // Paste with three selections, noticing how the copied selection that was full-line
 7285    // gets inserted before the second cursor.
 7286    cx.set_state(indoc! {"
 7287        1ˇ3
 7288 7289        «oˇ»ne"});
 7290    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7291    cx.assert_editor_state(indoc! {"
 7292        12ˇ3
 7293        4567
 7294 7295        8ˇne"});
 7296
 7297    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7298    cx.set_state(indoc! {"
 7299        The quick brown
 7300        fox juˇmps over
 7301        the lazy dog"});
 7302    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7303    assert_eq!(
 7304        cx.read_from_clipboard()
 7305            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7306        Some("fox jumps over\n".to_string())
 7307    );
 7308
 7309    // Paste with three selections, noticing how the copied full-line selection is inserted
 7310    // before the empty selections but replaces the selection that is non-empty.
 7311    cx.set_state(indoc! {"
 7312        Tˇhe quick brown
 7313        «foˇ»x jumps over
 7314        tˇhe lazy dog"});
 7315    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7316    cx.assert_editor_state(indoc! {"
 7317        fox jumps over
 7318        Tˇhe quick brown
 7319        fox jumps over
 7320        ˇx jumps over
 7321        fox jumps over
 7322        tˇhe lazy dog"});
 7323}
 7324
 7325#[gpui::test]
 7326async fn test_copy_trim(cx: &mut TestAppContext) {
 7327    init_test(cx, |_| {});
 7328
 7329    let mut cx = EditorTestContext::new(cx).await;
 7330    cx.set_state(
 7331        r#"            «for selection in selections.iter() {
 7332            let mut start = selection.start;
 7333            let mut end = selection.end;
 7334            let is_entire_line = selection.is_empty();
 7335            if is_entire_line {
 7336                start = Point::new(start.row, 0);ˇ»
 7337                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7338            }
 7339        "#,
 7340    );
 7341    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7342    assert_eq!(
 7343        cx.read_from_clipboard()
 7344            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7345        Some(
 7346            "for selection in selections.iter() {
 7347            let mut start = selection.start;
 7348            let mut end = selection.end;
 7349            let is_entire_line = selection.is_empty();
 7350            if is_entire_line {
 7351                start = Point::new(start.row, 0);"
 7352                .to_string()
 7353        ),
 7354        "Regular copying preserves all indentation selected",
 7355    );
 7356    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7357    assert_eq!(
 7358        cx.read_from_clipboard()
 7359            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7360        Some(
 7361            "for selection in selections.iter() {
 7362let mut start = selection.start;
 7363let mut end = selection.end;
 7364let is_entire_line = selection.is_empty();
 7365if is_entire_line {
 7366    start = Point::new(start.row, 0);"
 7367                .to_string()
 7368        ),
 7369        "Copying with stripping should strip all leading whitespaces"
 7370    );
 7371
 7372    cx.set_state(
 7373        r#"       «     for selection in selections.iter() {
 7374            let mut start = selection.start;
 7375            let mut end = selection.end;
 7376            let is_entire_line = selection.is_empty();
 7377            if is_entire_line {
 7378                start = Point::new(start.row, 0);ˇ»
 7379                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7380            }
 7381        "#,
 7382    );
 7383    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7384    assert_eq!(
 7385        cx.read_from_clipboard()
 7386            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7387        Some(
 7388            "     for selection in selections.iter() {
 7389            let mut start = selection.start;
 7390            let mut end = selection.end;
 7391            let is_entire_line = selection.is_empty();
 7392            if is_entire_line {
 7393                start = Point::new(start.row, 0);"
 7394                .to_string()
 7395        ),
 7396        "Regular copying preserves all indentation selected",
 7397    );
 7398    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7399    assert_eq!(
 7400        cx.read_from_clipboard()
 7401            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7402        Some(
 7403            "for selection in selections.iter() {
 7404let mut start = selection.start;
 7405let mut end = selection.end;
 7406let is_entire_line = selection.is_empty();
 7407if is_entire_line {
 7408    start = Point::new(start.row, 0);"
 7409                .to_string()
 7410        ),
 7411        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7412    );
 7413
 7414    cx.set_state(
 7415        r#"       «ˇ     for selection in selections.iter() {
 7416            let mut start = selection.start;
 7417            let mut end = selection.end;
 7418            let is_entire_line = selection.is_empty();
 7419            if is_entire_line {
 7420                start = Point::new(start.row, 0);»
 7421                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7422            }
 7423        "#,
 7424    );
 7425    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7426    assert_eq!(
 7427        cx.read_from_clipboard()
 7428            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7429        Some(
 7430            "     for selection in selections.iter() {
 7431            let mut start = selection.start;
 7432            let mut end = selection.end;
 7433            let is_entire_line = selection.is_empty();
 7434            if is_entire_line {
 7435                start = Point::new(start.row, 0);"
 7436                .to_string()
 7437        ),
 7438        "Regular copying for reverse selection works the same",
 7439    );
 7440    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7441    assert_eq!(
 7442        cx.read_from_clipboard()
 7443            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7444        Some(
 7445            "for selection in selections.iter() {
 7446let mut start = selection.start;
 7447let mut end = selection.end;
 7448let is_entire_line = selection.is_empty();
 7449if is_entire_line {
 7450    start = Point::new(start.row, 0);"
 7451                .to_string()
 7452        ),
 7453        "Copying with stripping for reverse selection works the same"
 7454    );
 7455
 7456    cx.set_state(
 7457        r#"            for selection «in selections.iter() {
 7458            let mut start = selection.start;
 7459            let mut end = selection.end;
 7460            let is_entire_line = selection.is_empty();
 7461            if is_entire_line {
 7462                start = Point::new(start.row, 0);ˇ»
 7463                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7464            }
 7465        "#,
 7466    );
 7467    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7468    assert_eq!(
 7469        cx.read_from_clipboard()
 7470            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7471        Some(
 7472            "in selections.iter() {
 7473            let mut start = selection.start;
 7474            let mut end = selection.end;
 7475            let is_entire_line = selection.is_empty();
 7476            if is_entire_line {
 7477                start = Point::new(start.row, 0);"
 7478                .to_string()
 7479        ),
 7480        "When selecting past the indent, the copying works as usual",
 7481    );
 7482    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7483    assert_eq!(
 7484        cx.read_from_clipboard()
 7485            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7486        Some(
 7487            "in selections.iter() {
 7488            let mut start = selection.start;
 7489            let mut end = selection.end;
 7490            let is_entire_line = selection.is_empty();
 7491            if is_entire_line {
 7492                start = Point::new(start.row, 0);"
 7493                .to_string()
 7494        ),
 7495        "When selecting past the indent, nothing is trimmed"
 7496    );
 7497
 7498    cx.set_state(
 7499        r#"            «for selection in selections.iter() {
 7500            let mut start = selection.start;
 7501
 7502            let mut end = selection.end;
 7503            let is_entire_line = selection.is_empty();
 7504            if is_entire_line {
 7505                start = Point::new(start.row, 0);
 7506ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7507            }
 7508        "#,
 7509    );
 7510    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7511    assert_eq!(
 7512        cx.read_from_clipboard()
 7513            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7514        Some(
 7515            "for selection in selections.iter() {
 7516let mut start = selection.start;
 7517
 7518let mut end = selection.end;
 7519let is_entire_line = selection.is_empty();
 7520if is_entire_line {
 7521    start = Point::new(start.row, 0);
 7522"
 7523            .to_string()
 7524        ),
 7525        "Copying with stripping should ignore empty lines"
 7526    );
 7527}
 7528
 7529#[gpui::test]
 7530async fn test_paste_multiline(cx: &mut TestAppContext) {
 7531    init_test(cx, |_| {});
 7532
 7533    let mut cx = EditorTestContext::new(cx).await;
 7534    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7535
 7536    // Cut an indented block, without the leading whitespace.
 7537    cx.set_state(indoc! {"
 7538        const a: B = (
 7539            c(),
 7540            «d(
 7541                e,
 7542                f
 7543            )ˇ»
 7544        );
 7545    "});
 7546    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7547    cx.assert_editor_state(indoc! {"
 7548        const a: B = (
 7549            c(),
 7550            ˇ
 7551        );
 7552    "});
 7553
 7554    // Paste it at the same position.
 7555    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7556    cx.assert_editor_state(indoc! {"
 7557        const a: B = (
 7558            c(),
 7559            d(
 7560                e,
 7561                f
 7562 7563        );
 7564    "});
 7565
 7566    // Paste it at a line with a lower indent level.
 7567    cx.set_state(indoc! {"
 7568        ˇ
 7569        const a: B = (
 7570            c(),
 7571        );
 7572    "});
 7573    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7574    cx.assert_editor_state(indoc! {"
 7575        d(
 7576            e,
 7577            f
 7578 7579        const a: B = (
 7580            c(),
 7581        );
 7582    "});
 7583
 7584    // Cut an indented block, with the leading whitespace.
 7585    cx.set_state(indoc! {"
 7586        const a: B = (
 7587            c(),
 7588        «    d(
 7589                e,
 7590                f
 7591            )
 7592        ˇ»);
 7593    "});
 7594    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7595    cx.assert_editor_state(indoc! {"
 7596        const a: B = (
 7597            c(),
 7598        ˇ);
 7599    "});
 7600
 7601    // Paste it at the same position.
 7602    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7603    cx.assert_editor_state(indoc! {"
 7604        const a: B = (
 7605            c(),
 7606            d(
 7607                e,
 7608                f
 7609            )
 7610        ˇ);
 7611    "});
 7612
 7613    // Paste it at a line with a higher indent level.
 7614    cx.set_state(indoc! {"
 7615        const a: B = (
 7616            c(),
 7617            d(
 7618                e,
 7619 7620            )
 7621        );
 7622    "});
 7623    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7624    cx.assert_editor_state(indoc! {"
 7625        const a: B = (
 7626            c(),
 7627            d(
 7628                e,
 7629                f    d(
 7630                    e,
 7631                    f
 7632                )
 7633        ˇ
 7634            )
 7635        );
 7636    "});
 7637
 7638    // Copy an indented block, starting mid-line
 7639    cx.set_state(indoc! {"
 7640        const a: B = (
 7641            c(),
 7642            somethin«g(
 7643                e,
 7644                f
 7645            )ˇ»
 7646        );
 7647    "});
 7648    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7649
 7650    // Paste it on a line with a lower indent level
 7651    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7652    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7653    cx.assert_editor_state(indoc! {"
 7654        const a: B = (
 7655            c(),
 7656            something(
 7657                e,
 7658                f
 7659            )
 7660        );
 7661        g(
 7662            e,
 7663            f
 7664"});
 7665}
 7666
 7667#[gpui::test]
 7668async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7669    init_test(cx, |_| {});
 7670
 7671    cx.write_to_clipboard(ClipboardItem::new_string(
 7672        "    d(\n        e\n    );\n".into(),
 7673    ));
 7674
 7675    let mut cx = EditorTestContext::new(cx).await;
 7676    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7677
 7678    cx.set_state(indoc! {"
 7679        fn a() {
 7680            b();
 7681            if c() {
 7682                ˇ
 7683            }
 7684        }
 7685    "});
 7686
 7687    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7688    cx.assert_editor_state(indoc! {"
 7689        fn a() {
 7690            b();
 7691            if c() {
 7692                d(
 7693                    e
 7694                );
 7695        ˇ
 7696            }
 7697        }
 7698    "});
 7699
 7700    cx.set_state(indoc! {"
 7701        fn a() {
 7702            b();
 7703            ˇ
 7704        }
 7705    "});
 7706
 7707    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7708    cx.assert_editor_state(indoc! {"
 7709        fn a() {
 7710            b();
 7711            d(
 7712                e
 7713            );
 7714        ˇ
 7715        }
 7716    "});
 7717}
 7718
 7719#[gpui::test]
 7720fn test_select_all(cx: &mut TestAppContext) {
 7721    init_test(cx, |_| {});
 7722
 7723    let editor = cx.add_window(|window, cx| {
 7724        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7725        build_editor(buffer, window, cx)
 7726    });
 7727    _ = editor.update(cx, |editor, window, cx| {
 7728        editor.select_all(&SelectAll, window, cx);
 7729        assert_eq!(
 7730            display_ranges(editor, cx),
 7731            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7732        );
 7733    });
 7734}
 7735
 7736#[gpui::test]
 7737fn test_select_line(cx: &mut TestAppContext) {
 7738    init_test(cx, |_| {});
 7739
 7740    let editor = cx.add_window(|window, cx| {
 7741        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7742        build_editor(buffer, window, cx)
 7743    });
 7744    _ = editor.update(cx, |editor, window, cx| {
 7745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7746            s.select_display_ranges([
 7747                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7748                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7749                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7750                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7751            ])
 7752        });
 7753        editor.select_line(&SelectLine, window, cx);
 7754        // Adjacent line selections should NOT merge (only overlapping ones do)
 7755        assert_eq!(
 7756            display_ranges(editor, cx),
 7757            vec![
 7758                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7759                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7760                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7761            ]
 7762        );
 7763    });
 7764
 7765    _ = editor.update(cx, |editor, window, cx| {
 7766        editor.select_line(&SelectLine, window, cx);
 7767        assert_eq!(
 7768            display_ranges(editor, cx),
 7769            vec![
 7770                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7771                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7772            ]
 7773        );
 7774    });
 7775
 7776    _ = editor.update(cx, |editor, window, cx| {
 7777        editor.select_line(&SelectLine, window, cx);
 7778        // Adjacent but not overlapping, so they stay separate
 7779        assert_eq!(
 7780            display_ranges(editor, cx),
 7781            vec![
 7782                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7783                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7784            ]
 7785        );
 7786    });
 7787}
 7788
 7789#[gpui::test]
 7790async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7791    init_test(cx, |_| {});
 7792    let mut cx = EditorTestContext::new(cx).await;
 7793
 7794    #[track_caller]
 7795    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7796        cx.set_state(initial_state);
 7797        cx.update_editor(|e, window, cx| {
 7798            e.split_selection_into_lines(&Default::default(), window, cx)
 7799        });
 7800        cx.assert_editor_state(expected_state);
 7801    }
 7802
 7803    // Selection starts and ends at the middle of lines, left-to-right
 7804    test(
 7805        &mut cx,
 7806        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7807        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7808    );
 7809    // Same thing, right-to-left
 7810    test(
 7811        &mut cx,
 7812        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7813        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7814    );
 7815
 7816    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7817    test(
 7818        &mut cx,
 7819        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7820        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7821    );
 7822    // Same thing, right-to-left
 7823    test(
 7824        &mut cx,
 7825        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7826        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7827    );
 7828
 7829    // Whole buffer, left-to-right, last line ends with newline
 7830    test(
 7831        &mut cx,
 7832        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7833        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7834    );
 7835    // Same thing, right-to-left
 7836    test(
 7837        &mut cx,
 7838        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7839        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7840    );
 7841
 7842    // Starts at the end of a line, ends at the start of another
 7843    test(
 7844        &mut cx,
 7845        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7846        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7847    );
 7848}
 7849
 7850#[gpui::test]
 7851async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7852    init_test(cx, |_| {});
 7853
 7854    let editor = cx.add_window(|window, cx| {
 7855        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7856        build_editor(buffer, window, cx)
 7857    });
 7858
 7859    // setup
 7860    _ = editor.update(cx, |editor, window, cx| {
 7861        editor.fold_creases(
 7862            vec![
 7863                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7864                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7865                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7866            ],
 7867            true,
 7868            window,
 7869            cx,
 7870        );
 7871        assert_eq!(
 7872            editor.display_text(cx),
 7873            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7874        );
 7875    });
 7876
 7877    _ = editor.update(cx, |editor, window, cx| {
 7878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7879            s.select_display_ranges([
 7880                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7882                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7883                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7884            ])
 7885        });
 7886        editor.split_selection_into_lines(&Default::default(), window, cx);
 7887        assert_eq!(
 7888            editor.display_text(cx),
 7889            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7890        );
 7891    });
 7892    EditorTestContext::for_editor(editor, cx)
 7893        .await
 7894        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7895
 7896    _ = editor.update(cx, |editor, window, cx| {
 7897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7898            s.select_display_ranges([
 7899                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7900            ])
 7901        });
 7902        editor.split_selection_into_lines(&Default::default(), window, cx);
 7903        assert_eq!(
 7904            editor.display_text(cx),
 7905            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7906        );
 7907        assert_eq!(
 7908            display_ranges(editor, cx),
 7909            [
 7910                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7911                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7912                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7913                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7914                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7915                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7916                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7917            ]
 7918        );
 7919    });
 7920    EditorTestContext::for_editor(editor, cx)
 7921        .await
 7922        .assert_editor_state(
 7923            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7924        );
 7925}
 7926
 7927#[gpui::test]
 7928async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7929    init_test(cx, |_| {});
 7930
 7931    let mut cx = EditorTestContext::new(cx).await;
 7932
 7933    cx.set_state(indoc!(
 7934        r#"abc
 7935           defˇghi
 7936
 7937           jk
 7938           nlmo
 7939           "#
 7940    ));
 7941
 7942    cx.update_editor(|editor, window, cx| {
 7943        editor.add_selection_above(&Default::default(), window, cx);
 7944    });
 7945
 7946    cx.assert_editor_state(indoc!(
 7947        r#"abcˇ
 7948           defˇghi
 7949
 7950           jk
 7951           nlmo
 7952           "#
 7953    ));
 7954
 7955    cx.update_editor(|editor, window, cx| {
 7956        editor.add_selection_above(&Default::default(), window, cx);
 7957    });
 7958
 7959    cx.assert_editor_state(indoc!(
 7960        r#"abcˇ
 7961            defˇghi
 7962
 7963            jk
 7964            nlmo
 7965            "#
 7966    ));
 7967
 7968    cx.update_editor(|editor, window, cx| {
 7969        editor.add_selection_below(&Default::default(), window, cx);
 7970    });
 7971
 7972    cx.assert_editor_state(indoc!(
 7973        r#"abc
 7974           defˇghi
 7975
 7976           jk
 7977           nlmo
 7978           "#
 7979    ));
 7980
 7981    cx.update_editor(|editor, window, cx| {
 7982        editor.undo_selection(&Default::default(), window, cx);
 7983    });
 7984
 7985    cx.assert_editor_state(indoc!(
 7986        r#"abcˇ
 7987           defˇghi
 7988
 7989           jk
 7990           nlmo
 7991           "#
 7992    ));
 7993
 7994    cx.update_editor(|editor, window, cx| {
 7995        editor.redo_selection(&Default::default(), window, cx);
 7996    });
 7997
 7998    cx.assert_editor_state(indoc!(
 7999        r#"abc
 8000           defˇghi
 8001
 8002           jk
 8003           nlmo
 8004           "#
 8005    ));
 8006
 8007    cx.update_editor(|editor, window, cx| {
 8008        editor.add_selection_below(&Default::default(), window, cx);
 8009    });
 8010
 8011    cx.assert_editor_state(indoc!(
 8012        r#"abc
 8013           defˇghi
 8014           ˇ
 8015           jk
 8016           nlmo
 8017           "#
 8018    ));
 8019
 8020    cx.update_editor(|editor, window, cx| {
 8021        editor.add_selection_below(&Default::default(), window, cx);
 8022    });
 8023
 8024    cx.assert_editor_state(indoc!(
 8025        r#"abc
 8026           defˇghi
 8027           ˇ
 8028           jkˇ
 8029           nlmo
 8030           "#
 8031    ));
 8032
 8033    cx.update_editor(|editor, window, cx| {
 8034        editor.add_selection_below(&Default::default(), window, cx);
 8035    });
 8036
 8037    cx.assert_editor_state(indoc!(
 8038        r#"abc
 8039           defˇghi
 8040           ˇ
 8041           jkˇ
 8042           nlmˇo
 8043           "#
 8044    ));
 8045
 8046    cx.update_editor(|editor, window, cx| {
 8047        editor.add_selection_below(&Default::default(), window, cx);
 8048    });
 8049
 8050    cx.assert_editor_state(indoc!(
 8051        r#"abc
 8052           defˇghi
 8053           ˇ
 8054           jkˇ
 8055           nlmˇo
 8056           ˇ"#
 8057    ));
 8058
 8059    // change selections
 8060    cx.set_state(indoc!(
 8061        r#"abc
 8062           def«ˇg»hi
 8063
 8064           jk
 8065           nlmo
 8066           "#
 8067    ));
 8068
 8069    cx.update_editor(|editor, window, cx| {
 8070        editor.add_selection_below(&Default::default(), window, cx);
 8071    });
 8072
 8073    cx.assert_editor_state(indoc!(
 8074        r#"abc
 8075           def«ˇg»hi
 8076
 8077           jk
 8078           nlm«ˇo»
 8079           "#
 8080    ));
 8081
 8082    cx.update_editor(|editor, window, cx| {
 8083        editor.add_selection_below(&Default::default(), window, cx);
 8084    });
 8085
 8086    cx.assert_editor_state(indoc!(
 8087        r#"abc
 8088           def«ˇg»hi
 8089
 8090           jk
 8091           nlm«ˇo»
 8092           "#
 8093    ));
 8094
 8095    cx.update_editor(|editor, window, cx| {
 8096        editor.add_selection_above(&Default::default(), window, cx);
 8097    });
 8098
 8099    cx.assert_editor_state(indoc!(
 8100        r#"abc
 8101           def«ˇg»hi
 8102
 8103           jk
 8104           nlmo
 8105           "#
 8106    ));
 8107
 8108    cx.update_editor(|editor, window, cx| {
 8109        editor.add_selection_above(&Default::default(), window, cx);
 8110    });
 8111
 8112    cx.assert_editor_state(indoc!(
 8113        r#"abc
 8114           def«ˇg»hi
 8115
 8116           jk
 8117           nlmo
 8118           "#
 8119    ));
 8120
 8121    // Change selections again
 8122    cx.set_state(indoc!(
 8123        r#"a«bc
 8124           defgˇ»hi
 8125
 8126           jk
 8127           nlmo
 8128           "#
 8129    ));
 8130
 8131    cx.update_editor(|editor, window, cx| {
 8132        editor.add_selection_below(&Default::default(), window, cx);
 8133    });
 8134
 8135    cx.assert_editor_state(indoc!(
 8136        r#"a«bcˇ»
 8137           d«efgˇ»hi
 8138
 8139           j«kˇ»
 8140           nlmo
 8141           "#
 8142    ));
 8143
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.add_selection_below(&Default::default(), window, cx);
 8146    });
 8147    cx.assert_editor_state(indoc!(
 8148        r#"a«bcˇ»
 8149           d«efgˇ»hi
 8150
 8151           j«kˇ»
 8152           n«lmoˇ»
 8153           "#
 8154    ));
 8155    cx.update_editor(|editor, window, cx| {
 8156        editor.add_selection_above(&Default::default(), window, cx);
 8157    });
 8158
 8159    cx.assert_editor_state(indoc!(
 8160        r#"a«bcˇ»
 8161           d«efgˇ»hi
 8162
 8163           j«kˇ»
 8164           nlmo
 8165           "#
 8166    ));
 8167
 8168    // Change selections again
 8169    cx.set_state(indoc!(
 8170        r#"abc
 8171           d«ˇefghi
 8172
 8173           jk
 8174           nlm»o
 8175           "#
 8176    ));
 8177
 8178    cx.update_editor(|editor, window, cx| {
 8179        editor.add_selection_above(&Default::default(), window, cx);
 8180    });
 8181
 8182    cx.assert_editor_state(indoc!(
 8183        r#"a«ˇbc»
 8184           d«ˇef»ghi
 8185
 8186           j«ˇk»
 8187           n«ˇlm»o
 8188           "#
 8189    ));
 8190
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.add_selection_below(&Default::default(), window, cx);
 8193    });
 8194
 8195    cx.assert_editor_state(indoc!(
 8196        r#"abc
 8197           d«ˇef»ghi
 8198
 8199           j«ˇk»
 8200           n«ˇlm»o
 8201           "#
 8202    ));
 8203}
 8204
 8205#[gpui::test]
 8206async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8207    init_test(cx, |_| {});
 8208    let mut cx = EditorTestContext::new(cx).await;
 8209
 8210    cx.set_state(indoc!(
 8211        r#"line onˇe
 8212           liˇne two
 8213           line three
 8214           line four"#
 8215    ));
 8216
 8217    cx.update_editor(|editor, window, cx| {
 8218        editor.add_selection_below(&Default::default(), window, cx);
 8219    });
 8220
 8221    // test multiple cursors expand in the same direction
 8222    cx.assert_editor_state(indoc!(
 8223        r#"line onˇe
 8224           liˇne twˇo
 8225           liˇne three
 8226           line four"#
 8227    ));
 8228
 8229    cx.update_editor(|editor, window, cx| {
 8230        editor.add_selection_below(&Default::default(), window, cx);
 8231    });
 8232
 8233    cx.update_editor(|editor, window, cx| {
 8234        editor.add_selection_below(&Default::default(), window, cx);
 8235    });
 8236
 8237    // test multiple cursors expand below overflow
 8238    cx.assert_editor_state(indoc!(
 8239        r#"line onˇe
 8240           liˇne twˇo
 8241           liˇne thˇree
 8242           liˇne foˇur"#
 8243    ));
 8244
 8245    cx.update_editor(|editor, window, cx| {
 8246        editor.add_selection_above(&Default::default(), window, cx);
 8247    });
 8248
 8249    // test multiple cursors retrieves back correctly
 8250    cx.assert_editor_state(indoc!(
 8251        r#"line onˇe
 8252           liˇne twˇo
 8253           liˇne thˇree
 8254           line four"#
 8255    ));
 8256
 8257    cx.update_editor(|editor, window, cx| {
 8258        editor.add_selection_above(&Default::default(), window, cx);
 8259    });
 8260
 8261    cx.update_editor(|editor, window, cx| {
 8262        editor.add_selection_above(&Default::default(), window, cx);
 8263    });
 8264
 8265    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8266    cx.assert_editor_state(indoc!(
 8267        r#"liˇne onˇe
 8268           liˇne two
 8269           line three
 8270           line four"#
 8271    ));
 8272
 8273    cx.update_editor(|editor, window, cx| {
 8274        editor.undo_selection(&Default::default(), window, cx);
 8275    });
 8276
 8277    // test undo
 8278    cx.assert_editor_state(indoc!(
 8279        r#"line onˇe
 8280           liˇne twˇo
 8281           line three
 8282           line four"#
 8283    ));
 8284
 8285    cx.update_editor(|editor, window, cx| {
 8286        editor.redo_selection(&Default::default(), window, cx);
 8287    });
 8288
 8289    // test redo
 8290    cx.assert_editor_state(indoc!(
 8291        r#"liˇne onˇe
 8292           liˇne two
 8293           line three
 8294           line four"#
 8295    ));
 8296
 8297    cx.set_state(indoc!(
 8298        r#"abcd
 8299           ef«ghˇ»
 8300           ijkl
 8301           «mˇ»nop"#
 8302    ));
 8303
 8304    cx.update_editor(|editor, window, cx| {
 8305        editor.add_selection_above(&Default::default(), window, cx);
 8306    });
 8307
 8308    // test multiple selections expand in the same direction
 8309    cx.assert_editor_state(indoc!(
 8310        r#"ab«cdˇ»
 8311           ef«ghˇ»
 8312           «iˇ»jkl
 8313           «mˇ»nop"#
 8314    ));
 8315
 8316    cx.update_editor(|editor, window, cx| {
 8317        editor.add_selection_above(&Default::default(), window, cx);
 8318    });
 8319
 8320    // test multiple selection upward overflow
 8321    cx.assert_editor_state(indoc!(
 8322        r#"ab«cdˇ»
 8323           «eˇ»f«ghˇ»
 8324           «iˇ»jkl
 8325           «mˇ»nop"#
 8326    ));
 8327
 8328    cx.update_editor(|editor, window, cx| {
 8329        editor.add_selection_below(&Default::default(), window, cx);
 8330    });
 8331
 8332    // test multiple selection retrieves back correctly
 8333    cx.assert_editor_state(indoc!(
 8334        r#"abcd
 8335           ef«ghˇ»
 8336           «iˇ»jkl
 8337           «mˇ»nop"#
 8338    ));
 8339
 8340    cx.update_editor(|editor, window, cx| {
 8341        editor.add_selection_below(&Default::default(), window, cx);
 8342    });
 8343
 8344    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8345    cx.assert_editor_state(indoc!(
 8346        r#"abcd
 8347           ef«ghˇ»
 8348           ij«klˇ»
 8349           «mˇ»nop"#
 8350    ));
 8351
 8352    cx.update_editor(|editor, window, cx| {
 8353        editor.undo_selection(&Default::default(), window, cx);
 8354    });
 8355
 8356    // test undo
 8357    cx.assert_editor_state(indoc!(
 8358        r#"abcd
 8359           ef«ghˇ»
 8360           «iˇ»jkl
 8361           «mˇ»nop"#
 8362    ));
 8363
 8364    cx.update_editor(|editor, window, cx| {
 8365        editor.redo_selection(&Default::default(), window, cx);
 8366    });
 8367
 8368    // test redo
 8369    cx.assert_editor_state(indoc!(
 8370        r#"abcd
 8371           ef«ghˇ»
 8372           ij«klˇ»
 8373           «mˇ»nop"#
 8374    ));
 8375}
 8376
 8377#[gpui::test]
 8378async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8379    init_test(cx, |_| {});
 8380    let mut cx = EditorTestContext::new(cx).await;
 8381
 8382    cx.set_state(indoc!(
 8383        r#"line onˇe
 8384           liˇne two
 8385           line three
 8386           line four"#
 8387    ));
 8388
 8389    cx.update_editor(|editor, window, cx| {
 8390        editor.add_selection_below(&Default::default(), window, cx);
 8391        editor.add_selection_below(&Default::default(), window, cx);
 8392        editor.add_selection_below(&Default::default(), window, cx);
 8393    });
 8394
 8395    // initial state with two multi cursor groups
 8396    cx.assert_editor_state(indoc!(
 8397        r#"line onˇe
 8398           liˇne twˇo
 8399           liˇne thˇree
 8400           liˇne foˇur"#
 8401    ));
 8402
 8403    // add single cursor in middle - simulate opt click
 8404    cx.update_editor(|editor, window, cx| {
 8405        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8406        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8407        editor.end_selection(window, cx);
 8408    });
 8409
 8410    cx.assert_editor_state(indoc!(
 8411        r#"line onˇe
 8412           liˇne twˇo
 8413           liˇneˇ thˇree
 8414           liˇne foˇur"#
 8415    ));
 8416
 8417    cx.update_editor(|editor, window, cx| {
 8418        editor.add_selection_above(&Default::default(), window, cx);
 8419    });
 8420
 8421    // test new added selection expands above and existing selection shrinks
 8422    cx.assert_editor_state(indoc!(
 8423        r#"line onˇe
 8424           liˇneˇ twˇo
 8425           liˇneˇ thˇree
 8426           line four"#
 8427    ));
 8428
 8429    cx.update_editor(|editor, window, cx| {
 8430        editor.add_selection_above(&Default::default(), window, cx);
 8431    });
 8432
 8433    // test new added selection expands above and existing selection shrinks
 8434    cx.assert_editor_state(indoc!(
 8435        r#"lineˇ onˇe
 8436           liˇneˇ twˇo
 8437           lineˇ three
 8438           line four"#
 8439    ));
 8440
 8441    // intial state with two selection groups
 8442    cx.set_state(indoc!(
 8443        r#"abcd
 8444           ef«ghˇ»
 8445           ijkl
 8446           «mˇ»nop"#
 8447    ));
 8448
 8449    cx.update_editor(|editor, window, cx| {
 8450        editor.add_selection_above(&Default::default(), window, cx);
 8451        editor.add_selection_above(&Default::default(), window, cx);
 8452    });
 8453
 8454    cx.assert_editor_state(indoc!(
 8455        r#"ab«cdˇ»
 8456           «eˇ»f«ghˇ»
 8457           «iˇ»jkl
 8458           «mˇ»nop"#
 8459    ));
 8460
 8461    // add single selection in middle - simulate opt drag
 8462    cx.update_editor(|editor, window, cx| {
 8463        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8464        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8465        editor.update_selection(
 8466            DisplayPoint::new(DisplayRow(2), 4),
 8467            0,
 8468            gpui::Point::<f32>::default(),
 8469            window,
 8470            cx,
 8471        );
 8472        editor.end_selection(window, cx);
 8473    });
 8474
 8475    cx.assert_editor_state(indoc!(
 8476        r#"ab«cdˇ»
 8477           «eˇ»f«ghˇ»
 8478           «iˇ»jk«lˇ»
 8479           «mˇ»nop"#
 8480    ));
 8481
 8482    cx.update_editor(|editor, window, cx| {
 8483        editor.add_selection_below(&Default::default(), window, cx);
 8484    });
 8485
 8486    // test new added selection expands below, others shrinks from above
 8487    cx.assert_editor_state(indoc!(
 8488        r#"abcd
 8489           ef«ghˇ»
 8490           «iˇ»jk«lˇ»
 8491           «mˇ»no«pˇ»"#
 8492    ));
 8493}
 8494
 8495#[gpui::test]
 8496async fn test_select_next(cx: &mut TestAppContext) {
 8497    init_test(cx, |_| {});
 8498    let mut cx = EditorTestContext::new(cx).await;
 8499
 8500    // Enable case sensitive search.
 8501    update_test_editor_settings(&mut cx, |settings| {
 8502        let mut search_settings = SearchSettingsContent::default();
 8503        search_settings.case_sensitive = Some(true);
 8504        settings.search = Some(search_settings);
 8505    });
 8506
 8507    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8508
 8509    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8510        .unwrap();
 8511    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8512
 8513    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8514        .unwrap();
 8515    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8516
 8517    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8518    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8519
 8520    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8521    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8522
 8523    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8524        .unwrap();
 8525    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8526
 8527    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8528        .unwrap();
 8529    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8530
 8531    // Test selection direction should be preserved
 8532    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8533
 8534    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8535        .unwrap();
 8536    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8537
 8538    // Test case sensitivity
 8539    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8540    cx.update_editor(|e, window, cx| {
 8541        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8542    });
 8543    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8544
 8545    // Disable case sensitive search.
 8546    update_test_editor_settings(&mut cx, |settings| {
 8547        let mut search_settings = SearchSettingsContent::default();
 8548        search_settings.case_sensitive = Some(false);
 8549        settings.search = Some(search_settings);
 8550    });
 8551
 8552    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8553    cx.update_editor(|e, window, cx| {
 8554        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8555        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8556    });
 8557    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8558}
 8559
 8560#[gpui::test]
 8561async fn test_select_all_matches(cx: &mut TestAppContext) {
 8562    init_test(cx, |_| {});
 8563    let mut cx = EditorTestContext::new(cx).await;
 8564
 8565    // Enable case sensitive search.
 8566    update_test_editor_settings(&mut cx, |settings| {
 8567        let mut search_settings = SearchSettingsContent::default();
 8568        search_settings.case_sensitive = Some(true);
 8569        settings.search = Some(search_settings);
 8570    });
 8571
 8572    // Test caret-only selections
 8573    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8574    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8575        .unwrap();
 8576    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8577
 8578    // Test left-to-right selections
 8579    cx.set_state("abc\n«abcˇ»\nabc");
 8580    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8581        .unwrap();
 8582    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8583
 8584    // Test right-to-left selections
 8585    cx.set_state("abc\n«ˇabc»\nabc");
 8586    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8587        .unwrap();
 8588    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8589
 8590    // Test selecting whitespace with caret selection
 8591    cx.set_state("abc\nˇ   abc\nabc");
 8592    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8593        .unwrap();
 8594    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8595
 8596    // Test selecting whitespace with left-to-right selection
 8597    cx.set_state("abc\n«ˇ  »abc\nabc");
 8598    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8599        .unwrap();
 8600    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8601
 8602    // Test no matches with right-to-left selection
 8603    cx.set_state("abc\n«  ˇ»abc\nabc");
 8604    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8605        .unwrap();
 8606    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8607
 8608    // Test with a single word and clip_at_line_ends=true (#29823)
 8609    cx.set_state("aˇbc");
 8610    cx.update_editor(|e, window, cx| {
 8611        e.set_clip_at_line_ends(true, cx);
 8612        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8613        e.set_clip_at_line_ends(false, cx);
 8614    });
 8615    cx.assert_editor_state("«abcˇ»");
 8616
 8617    // Test case sensitivity
 8618    cx.set_state("fˇoo\nFOO\nFoo");
 8619    cx.update_editor(|e, window, cx| {
 8620        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8621    });
 8622    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8623
 8624    // Disable case sensitive search.
 8625    update_test_editor_settings(&mut cx, |settings| {
 8626        let mut search_settings = SearchSettingsContent::default();
 8627        search_settings.case_sensitive = Some(false);
 8628        settings.search = Some(search_settings);
 8629    });
 8630
 8631    cx.set_state("fˇoo\nFOO\nFoo");
 8632    cx.update_editor(|e, window, cx| {
 8633        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8634    });
 8635    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8636}
 8637
 8638#[gpui::test]
 8639async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8640    init_test(cx, |_| {});
 8641
 8642    let mut cx = EditorTestContext::new(cx).await;
 8643
 8644    let large_body_1 = "\nd".repeat(200);
 8645    let large_body_2 = "\ne".repeat(200);
 8646
 8647    cx.set_state(&format!(
 8648        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8649    ));
 8650    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8651        let scroll_position = editor.scroll_position(cx);
 8652        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8653        scroll_position
 8654    });
 8655
 8656    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8657        .unwrap();
 8658    cx.assert_editor_state(&format!(
 8659        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8660    ));
 8661    let scroll_position_after_selection =
 8662        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8663    assert_eq!(
 8664        initial_scroll_position, scroll_position_after_selection,
 8665        "Scroll position should not change after selecting all matches"
 8666    );
 8667}
 8668
 8669#[gpui::test]
 8670async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8671    init_test(cx, |_| {});
 8672
 8673    let mut cx = EditorLspTestContext::new_rust(
 8674        lsp::ServerCapabilities {
 8675            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8676            ..Default::default()
 8677        },
 8678        cx,
 8679    )
 8680    .await;
 8681
 8682    cx.set_state(indoc! {"
 8683        line 1
 8684        line 2
 8685        linˇe 3
 8686        line 4
 8687        line 5
 8688    "});
 8689
 8690    // Make an edit
 8691    cx.update_editor(|editor, window, cx| {
 8692        editor.handle_input("X", window, cx);
 8693    });
 8694
 8695    // Move cursor to a different position
 8696    cx.update_editor(|editor, window, cx| {
 8697        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8698            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8699        });
 8700    });
 8701
 8702    cx.assert_editor_state(indoc! {"
 8703        line 1
 8704        line 2
 8705        linXe 3
 8706        line 4
 8707        liˇne 5
 8708    "});
 8709
 8710    cx.lsp
 8711        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8712            Ok(Some(vec![lsp::TextEdit::new(
 8713                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8714                "PREFIX ".to_string(),
 8715            )]))
 8716        });
 8717
 8718    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8719        .unwrap()
 8720        .await
 8721        .unwrap();
 8722
 8723    cx.assert_editor_state(indoc! {"
 8724        PREFIX line 1
 8725        line 2
 8726        linXe 3
 8727        line 4
 8728        liˇne 5
 8729    "});
 8730
 8731    // Undo formatting
 8732    cx.update_editor(|editor, window, cx| {
 8733        editor.undo(&Default::default(), window, cx);
 8734    });
 8735
 8736    // Verify cursor moved back to position after edit
 8737    cx.assert_editor_state(indoc! {"
 8738        line 1
 8739        line 2
 8740        linXˇe 3
 8741        line 4
 8742        line 5
 8743    "});
 8744}
 8745
 8746#[gpui::test]
 8747async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8748    init_test(cx, |_| {});
 8749
 8750    let mut cx = EditorTestContext::new(cx).await;
 8751
 8752    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8753    cx.update_editor(|editor, window, cx| {
 8754        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8755    });
 8756
 8757    cx.set_state(indoc! {"
 8758        line 1
 8759        line 2
 8760        linˇe 3
 8761        line 4
 8762        line 5
 8763        line 6
 8764        line 7
 8765        line 8
 8766        line 9
 8767        line 10
 8768    "});
 8769
 8770    let snapshot = cx.buffer_snapshot();
 8771    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8772
 8773    cx.update(|_, cx| {
 8774        provider.update(cx, |provider, _| {
 8775            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8776                id: None,
 8777                edits: vec![(edit_position..edit_position, "X".into())],
 8778                edit_preview: None,
 8779            }))
 8780        })
 8781    });
 8782
 8783    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8784    cx.update_editor(|editor, window, cx| {
 8785        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8786    });
 8787
 8788    cx.assert_editor_state(indoc! {"
 8789        line 1
 8790        line 2
 8791        lineXˇ 3
 8792        line 4
 8793        line 5
 8794        line 6
 8795        line 7
 8796        line 8
 8797        line 9
 8798        line 10
 8799    "});
 8800
 8801    cx.update_editor(|editor, window, cx| {
 8802        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8803            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8804        });
 8805    });
 8806
 8807    cx.assert_editor_state(indoc! {"
 8808        line 1
 8809        line 2
 8810        lineX 3
 8811        line 4
 8812        line 5
 8813        line 6
 8814        line 7
 8815        line 8
 8816        line 9
 8817        liˇne 10
 8818    "});
 8819
 8820    cx.update_editor(|editor, window, cx| {
 8821        editor.undo(&Default::default(), window, cx);
 8822    });
 8823
 8824    cx.assert_editor_state(indoc! {"
 8825        line 1
 8826        line 2
 8827        lineˇ 3
 8828        line 4
 8829        line 5
 8830        line 6
 8831        line 7
 8832        line 8
 8833        line 9
 8834        line 10
 8835    "});
 8836}
 8837
 8838#[gpui::test]
 8839async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8840    init_test(cx, |_| {});
 8841
 8842    let mut cx = EditorTestContext::new(cx).await;
 8843    cx.set_state(
 8844        r#"let foo = 2;
 8845lˇet foo = 2;
 8846let fooˇ = 2;
 8847let foo = 2;
 8848let foo = ˇ2;"#,
 8849    );
 8850
 8851    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state(
 8854        r#"let foo = 2;
 8855«letˇ» foo = 2;
 8856let «fooˇ» = 2;
 8857let foo = 2;
 8858let foo = «2ˇ»;"#,
 8859    );
 8860
 8861    // noop for multiple selections with different contents
 8862    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8863        .unwrap();
 8864    cx.assert_editor_state(
 8865        r#"let foo = 2;
 8866«letˇ» foo = 2;
 8867let «fooˇ» = 2;
 8868let foo = 2;
 8869let foo = «2ˇ»;"#,
 8870    );
 8871
 8872    // Test last selection direction should be preserved
 8873    cx.set_state(
 8874        r#"let foo = 2;
 8875let foo = 2;
 8876let «fooˇ» = 2;
 8877let «ˇfoo» = 2;
 8878let foo = 2;"#,
 8879    );
 8880
 8881    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8882        .unwrap();
 8883    cx.assert_editor_state(
 8884        r#"let foo = 2;
 8885let foo = 2;
 8886let «fooˇ» = 2;
 8887let «ˇfoo» = 2;
 8888let «ˇfoo» = 2;"#,
 8889    );
 8890}
 8891
 8892#[gpui::test]
 8893async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8894    init_test(cx, |_| {});
 8895
 8896    let mut cx =
 8897        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8898
 8899    cx.assert_editor_state(indoc! {"
 8900        ˇbbb
 8901        ccc
 8902
 8903        bbb
 8904        ccc
 8905        "});
 8906    cx.dispatch_action(SelectPrevious::default());
 8907    cx.assert_editor_state(indoc! {"
 8908                «bbbˇ»
 8909                ccc
 8910
 8911                bbb
 8912                ccc
 8913                "});
 8914    cx.dispatch_action(SelectPrevious::default());
 8915    cx.assert_editor_state(indoc! {"
 8916                «bbbˇ»
 8917                ccc
 8918
 8919                «bbbˇ»
 8920                ccc
 8921                "});
 8922}
 8923
 8924#[gpui::test]
 8925async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8926    init_test(cx, |_| {});
 8927
 8928    let mut cx = EditorTestContext::new(cx).await;
 8929    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8930
 8931    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8932        .unwrap();
 8933    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8934
 8935    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8936        .unwrap();
 8937    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8938
 8939    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8940    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8941
 8942    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8943    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8944
 8945    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8946        .unwrap();
 8947    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8948
 8949    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8950        .unwrap();
 8951    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8952}
 8953
 8954#[gpui::test]
 8955async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8956    init_test(cx, |_| {});
 8957
 8958    let mut cx = EditorTestContext::new(cx).await;
 8959    cx.set_state("");
 8960
 8961    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8962        .unwrap();
 8963    cx.assert_editor_state("«aˇ»");
 8964    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8965        .unwrap();
 8966    cx.assert_editor_state("«aˇ»");
 8967}
 8968
 8969#[gpui::test]
 8970async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8971    init_test(cx, |_| {});
 8972
 8973    let mut cx = EditorTestContext::new(cx).await;
 8974    cx.set_state(
 8975        r#"let foo = 2;
 8976lˇet foo = 2;
 8977let fooˇ = 2;
 8978let foo = 2;
 8979let foo = ˇ2;"#,
 8980    );
 8981
 8982    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8983        .unwrap();
 8984    cx.assert_editor_state(
 8985        r#"let foo = 2;
 8986«letˇ» foo = 2;
 8987let «fooˇ» = 2;
 8988let foo = 2;
 8989let foo = «2ˇ»;"#,
 8990    );
 8991
 8992    // noop for multiple selections with different contents
 8993    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8994        .unwrap();
 8995    cx.assert_editor_state(
 8996        r#"let foo = 2;
 8997«letˇ» foo = 2;
 8998let «fooˇ» = 2;
 8999let foo = 2;
 9000let foo = «2ˇ»;"#,
 9001    );
 9002}
 9003
 9004#[gpui::test]
 9005async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9006    init_test(cx, |_| {});
 9007    let mut cx = EditorTestContext::new(cx).await;
 9008
 9009    // Enable case sensitive search.
 9010    update_test_editor_settings(&mut cx, |settings| {
 9011        let mut search_settings = SearchSettingsContent::default();
 9012        search_settings.case_sensitive = Some(true);
 9013        settings.search = Some(search_settings);
 9014    });
 9015
 9016    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9017
 9018    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9019        .unwrap();
 9020    // selection direction is preserved
 9021    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9022
 9023    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9024        .unwrap();
 9025    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9026
 9027    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9028    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9029
 9030    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9031    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9032
 9033    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9034        .unwrap();
 9035    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9036
 9037    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9038        .unwrap();
 9039    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9040
 9041    // Test case sensitivity
 9042    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9043    cx.update_editor(|e, window, cx| {
 9044        e.select_previous(&SelectPrevious::default(), window, cx)
 9045            .unwrap();
 9046        e.select_previous(&SelectPrevious::default(), window, cx)
 9047            .unwrap();
 9048    });
 9049    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9050
 9051    // Disable case sensitive search.
 9052    update_test_editor_settings(&mut cx, |settings| {
 9053        let mut search_settings = SearchSettingsContent::default();
 9054        search_settings.case_sensitive = Some(false);
 9055        settings.search = Some(search_settings);
 9056    });
 9057
 9058    cx.set_state("foo\nFOO\n«ˇFoo»");
 9059    cx.update_editor(|e, window, cx| {
 9060        e.select_previous(&SelectPrevious::default(), window, cx)
 9061            .unwrap();
 9062        e.select_previous(&SelectPrevious::default(), window, cx)
 9063            .unwrap();
 9064    });
 9065    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9066}
 9067
 9068#[gpui::test]
 9069async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9070    init_test(cx, |_| {});
 9071
 9072    let language = Arc::new(Language::new(
 9073        LanguageConfig::default(),
 9074        Some(tree_sitter_rust::LANGUAGE.into()),
 9075    ));
 9076
 9077    let text = r#"
 9078        use mod1::mod2::{mod3, mod4};
 9079
 9080        fn fn_1(param1: bool, param2: &str) {
 9081            let var1 = "text";
 9082        }
 9083    "#
 9084    .unindent();
 9085
 9086    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9087    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9088    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9089
 9090    editor
 9091        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9092        .await;
 9093
 9094    editor.update_in(cx, |editor, window, cx| {
 9095        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9096            s.select_display_ranges([
 9097                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9098                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9099                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9100            ]);
 9101        });
 9102        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9103    });
 9104    editor.update(cx, |editor, cx| {
 9105        assert_text_with_selections(
 9106            editor,
 9107            indoc! {r#"
 9108                use mod1::mod2::{mod3, «mod4ˇ»};
 9109
 9110                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9111                    let var1 = "«ˇtext»";
 9112                }
 9113            "#},
 9114            cx,
 9115        );
 9116    });
 9117
 9118    editor.update_in(cx, |editor, window, cx| {
 9119        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9120    });
 9121    editor.update(cx, |editor, cx| {
 9122        assert_text_with_selections(
 9123            editor,
 9124            indoc! {r#"
 9125                use mod1::mod2::«{mod3, mod4}ˇ»;
 9126
 9127                «ˇfn fn_1(param1: bool, param2: &str) {
 9128                    let var1 = "text";
 9129 9130            "#},
 9131            cx,
 9132        );
 9133    });
 9134
 9135    editor.update_in(cx, |editor, window, cx| {
 9136        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9137    });
 9138    assert_eq!(
 9139        editor.update(cx, |editor, cx| editor
 9140            .selections
 9141            .display_ranges(&editor.display_snapshot(cx))),
 9142        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9143    );
 9144
 9145    // Trying to expand the selected syntax node one more time has no effect.
 9146    editor.update_in(cx, |editor, window, cx| {
 9147        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9148    });
 9149    assert_eq!(
 9150        editor.update(cx, |editor, cx| editor
 9151            .selections
 9152            .display_ranges(&editor.display_snapshot(cx))),
 9153        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9154    );
 9155
 9156    editor.update_in(cx, |editor, window, cx| {
 9157        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9158    });
 9159    editor.update(cx, |editor, cx| {
 9160        assert_text_with_selections(
 9161            editor,
 9162            indoc! {r#"
 9163                use mod1::mod2::«{mod3, mod4}ˇ»;
 9164
 9165                «ˇfn fn_1(param1: bool, param2: &str) {
 9166                    let var1 = "text";
 9167 9168            "#},
 9169            cx,
 9170        );
 9171    });
 9172
 9173    editor.update_in(cx, |editor, window, cx| {
 9174        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9175    });
 9176    editor.update(cx, |editor, cx| {
 9177        assert_text_with_selections(
 9178            editor,
 9179            indoc! {r#"
 9180                use mod1::mod2::{mod3, «mod4ˇ»};
 9181
 9182                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9183                    let var1 = "«ˇtext»";
 9184                }
 9185            "#},
 9186            cx,
 9187        );
 9188    });
 9189
 9190    editor.update_in(cx, |editor, window, cx| {
 9191        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9192    });
 9193    editor.update(cx, |editor, cx| {
 9194        assert_text_with_selections(
 9195            editor,
 9196            indoc! {r#"
 9197                use mod1::mod2::{mod3, moˇd4};
 9198
 9199                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9200                    let var1 = "teˇxt";
 9201                }
 9202            "#},
 9203            cx,
 9204        );
 9205    });
 9206
 9207    // Trying to shrink the selected syntax node one more time has no effect.
 9208    editor.update_in(cx, |editor, window, cx| {
 9209        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9210    });
 9211    editor.update_in(cx, |editor, _, cx| {
 9212        assert_text_with_selections(
 9213            editor,
 9214            indoc! {r#"
 9215                use mod1::mod2::{mod3, moˇd4};
 9216
 9217                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9218                    let var1 = "teˇxt";
 9219                }
 9220            "#},
 9221            cx,
 9222        );
 9223    });
 9224
 9225    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9226    // a fold.
 9227    editor.update_in(cx, |editor, window, cx| {
 9228        editor.fold_creases(
 9229            vec![
 9230                Crease::simple(
 9231                    Point::new(0, 21)..Point::new(0, 24),
 9232                    FoldPlaceholder::test(),
 9233                ),
 9234                Crease::simple(
 9235                    Point::new(3, 20)..Point::new(3, 22),
 9236                    FoldPlaceholder::test(),
 9237                ),
 9238            ],
 9239            true,
 9240            window,
 9241            cx,
 9242        );
 9243        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9244    });
 9245    editor.update(cx, |editor, cx| {
 9246        assert_text_with_selections(
 9247            editor,
 9248            indoc! {r#"
 9249                use mod1::mod2::«{mod3, mod4}ˇ»;
 9250
 9251                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9252                    let var1 = "«ˇtext»";
 9253                }
 9254            "#},
 9255            cx,
 9256        );
 9257    });
 9258}
 9259
 9260#[gpui::test]
 9261async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9262    init_test(cx, |_| {});
 9263
 9264    let language = Arc::new(Language::new(
 9265        LanguageConfig::default(),
 9266        Some(tree_sitter_rust::LANGUAGE.into()),
 9267    ));
 9268
 9269    let text = "let a = 2;";
 9270
 9271    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9272    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9273    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9274
 9275    editor
 9276        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9277        .await;
 9278
 9279    // Test case 1: Cursor at end of word
 9280    editor.update_in(cx, |editor, window, cx| {
 9281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9282            s.select_display_ranges([
 9283                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9284            ]);
 9285        });
 9286    });
 9287    editor.update(cx, |editor, cx| {
 9288        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9289    });
 9290    editor.update_in(cx, |editor, window, cx| {
 9291        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9292    });
 9293    editor.update(cx, |editor, cx| {
 9294        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9295    });
 9296    editor.update_in(cx, |editor, window, cx| {
 9297        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9298    });
 9299    editor.update(cx, |editor, cx| {
 9300        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9301    });
 9302
 9303    // Test case 2: Cursor at end of statement
 9304    editor.update_in(cx, |editor, window, cx| {
 9305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9306            s.select_display_ranges([
 9307                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9308            ]);
 9309        });
 9310    });
 9311    editor.update(cx, |editor, cx| {
 9312        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9313    });
 9314    editor.update_in(cx, |editor, window, cx| {
 9315        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9316    });
 9317    editor.update(cx, |editor, cx| {
 9318        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9319    });
 9320}
 9321
 9322#[gpui::test]
 9323async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9324    init_test(cx, |_| {});
 9325
 9326    let language = Arc::new(Language::new(
 9327        LanguageConfig {
 9328            name: "JavaScript".into(),
 9329            ..Default::default()
 9330        },
 9331        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9332    ));
 9333
 9334    let text = r#"
 9335        let a = {
 9336            key: "value",
 9337        };
 9338    "#
 9339    .unindent();
 9340
 9341    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9342    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9343    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9344
 9345    editor
 9346        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9347        .await;
 9348
 9349    // Test case 1: Cursor after '{'
 9350    editor.update_in(cx, |editor, window, cx| {
 9351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9352            s.select_display_ranges([
 9353                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9354            ]);
 9355        });
 9356    });
 9357    editor.update(cx, |editor, cx| {
 9358        assert_text_with_selections(
 9359            editor,
 9360            indoc! {r#"
 9361                let a = {ˇ
 9362                    key: "value",
 9363                };
 9364            "#},
 9365            cx,
 9366        );
 9367    });
 9368    editor.update_in(cx, |editor, window, cx| {
 9369        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9370    });
 9371    editor.update(cx, |editor, cx| {
 9372        assert_text_with_selections(
 9373            editor,
 9374            indoc! {r#"
 9375                let a = «ˇ{
 9376                    key: "value",
 9377                }»;
 9378            "#},
 9379            cx,
 9380        );
 9381    });
 9382
 9383    // Test case 2: Cursor after ':'
 9384    editor.update_in(cx, |editor, window, cx| {
 9385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9386            s.select_display_ranges([
 9387                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9388            ]);
 9389        });
 9390    });
 9391    editor.update(cx, |editor, cx| {
 9392        assert_text_with_selections(
 9393            editor,
 9394            indoc! {r#"
 9395                let a = {
 9396                    key:ˇ "value",
 9397                };
 9398            "#},
 9399            cx,
 9400        );
 9401    });
 9402    editor.update_in(cx, |editor, window, cx| {
 9403        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9404    });
 9405    editor.update(cx, |editor, cx| {
 9406        assert_text_with_selections(
 9407            editor,
 9408            indoc! {r#"
 9409                let a = {
 9410                    «ˇkey: "value"»,
 9411                };
 9412            "#},
 9413            cx,
 9414        );
 9415    });
 9416    editor.update_in(cx, |editor, window, cx| {
 9417        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9418    });
 9419    editor.update(cx, |editor, cx| {
 9420        assert_text_with_selections(
 9421            editor,
 9422            indoc! {r#"
 9423                let a = «ˇ{
 9424                    key: "value",
 9425                }»;
 9426            "#},
 9427            cx,
 9428        );
 9429    });
 9430
 9431    // Test case 3: Cursor after ','
 9432    editor.update_in(cx, |editor, window, cx| {
 9433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9434            s.select_display_ranges([
 9435                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9436            ]);
 9437        });
 9438    });
 9439    editor.update(cx, |editor, cx| {
 9440        assert_text_with_selections(
 9441            editor,
 9442            indoc! {r#"
 9443                let a = {
 9444                    key: "value",ˇ
 9445                };
 9446            "#},
 9447            cx,
 9448        );
 9449    });
 9450    editor.update_in(cx, |editor, window, cx| {
 9451        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9452    });
 9453    editor.update(cx, |editor, cx| {
 9454        assert_text_with_selections(
 9455            editor,
 9456            indoc! {r#"
 9457                let a = «ˇ{
 9458                    key: "value",
 9459                }»;
 9460            "#},
 9461            cx,
 9462        );
 9463    });
 9464
 9465    // Test case 4: Cursor after ';'
 9466    editor.update_in(cx, |editor, window, cx| {
 9467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9468            s.select_display_ranges([
 9469                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9470            ]);
 9471        });
 9472    });
 9473    editor.update(cx, |editor, cx| {
 9474        assert_text_with_selections(
 9475            editor,
 9476            indoc! {r#"
 9477                let a = {
 9478                    key: "value",
 9479                };ˇ
 9480            "#},
 9481            cx,
 9482        );
 9483    });
 9484    editor.update_in(cx, |editor, window, cx| {
 9485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9486    });
 9487    editor.update(cx, |editor, cx| {
 9488        assert_text_with_selections(
 9489            editor,
 9490            indoc! {r#"
 9491                «ˇlet a = {
 9492                    key: "value",
 9493                };
 9494                »"#},
 9495            cx,
 9496        );
 9497    });
 9498}
 9499
 9500#[gpui::test]
 9501async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9502    init_test(cx, |_| {});
 9503
 9504    let language = Arc::new(Language::new(
 9505        LanguageConfig::default(),
 9506        Some(tree_sitter_rust::LANGUAGE.into()),
 9507    ));
 9508
 9509    let text = r#"
 9510        use mod1::mod2::{mod3, mod4};
 9511
 9512        fn fn_1(param1: bool, param2: &str) {
 9513            let var1 = "hello world";
 9514        }
 9515    "#
 9516    .unindent();
 9517
 9518    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9519    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9520    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9521
 9522    editor
 9523        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9524        .await;
 9525
 9526    // Test 1: Cursor on a letter of a string word
 9527    editor.update_in(cx, |editor, window, cx| {
 9528        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9529            s.select_display_ranges([
 9530                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9531            ]);
 9532        });
 9533    });
 9534    editor.update_in(cx, |editor, window, cx| {
 9535        assert_text_with_selections(
 9536            editor,
 9537            indoc! {r#"
 9538                use mod1::mod2::{mod3, mod4};
 9539
 9540                fn fn_1(param1: bool, param2: &str) {
 9541                    let var1 = "hˇello world";
 9542                }
 9543            "#},
 9544            cx,
 9545        );
 9546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9547        assert_text_with_selections(
 9548            editor,
 9549            indoc! {r#"
 9550                use mod1::mod2::{mod3, mod4};
 9551
 9552                fn fn_1(param1: bool, param2: &str) {
 9553                    let var1 = "«ˇhello» world";
 9554                }
 9555            "#},
 9556            cx,
 9557        );
 9558    });
 9559
 9560    // Test 2: Partial selection within a word
 9561    editor.update_in(cx, |editor, window, cx| {
 9562        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9563            s.select_display_ranges([
 9564                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9565            ]);
 9566        });
 9567    });
 9568    editor.update_in(cx, |editor, window, cx| {
 9569        assert_text_with_selections(
 9570            editor,
 9571            indoc! {r#"
 9572                use mod1::mod2::{mod3, mod4};
 9573
 9574                fn fn_1(param1: bool, param2: &str) {
 9575                    let var1 = "h«elˇ»lo world";
 9576                }
 9577            "#},
 9578            cx,
 9579        );
 9580        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9581        assert_text_with_selections(
 9582            editor,
 9583            indoc! {r#"
 9584                use mod1::mod2::{mod3, mod4};
 9585
 9586                fn fn_1(param1: bool, param2: &str) {
 9587                    let var1 = "«ˇhello» world";
 9588                }
 9589            "#},
 9590            cx,
 9591        );
 9592    });
 9593
 9594    // Test 3: Complete word already selected
 9595    editor.update_in(cx, |editor, window, cx| {
 9596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9597            s.select_display_ranges([
 9598                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9599            ]);
 9600        });
 9601    });
 9602    editor.update_in(cx, |editor, window, cx| {
 9603        assert_text_with_selections(
 9604            editor,
 9605            indoc! {r#"
 9606                use mod1::mod2::{mod3, mod4};
 9607
 9608                fn fn_1(param1: bool, param2: &str) {
 9609                    let var1 = "«helloˇ» world";
 9610                }
 9611            "#},
 9612            cx,
 9613        );
 9614        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9615        assert_text_with_selections(
 9616            editor,
 9617            indoc! {r#"
 9618                use mod1::mod2::{mod3, mod4};
 9619
 9620                fn fn_1(param1: bool, param2: &str) {
 9621                    let var1 = "«hello worldˇ»";
 9622                }
 9623            "#},
 9624            cx,
 9625        );
 9626    });
 9627
 9628    // Test 4: Selection spanning across words
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9631            s.select_display_ranges([
 9632                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9633            ]);
 9634        });
 9635    });
 9636    editor.update_in(cx, |editor, window, cx| {
 9637        assert_text_with_selections(
 9638            editor,
 9639            indoc! {r#"
 9640                use mod1::mod2::{mod3, mod4};
 9641
 9642                fn fn_1(param1: bool, param2: &str) {
 9643                    let var1 = "hel«lo woˇ»rld";
 9644                }
 9645            "#},
 9646            cx,
 9647        );
 9648        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9649        assert_text_with_selections(
 9650            editor,
 9651            indoc! {r#"
 9652                use mod1::mod2::{mod3, mod4};
 9653
 9654                fn fn_1(param1: bool, param2: &str) {
 9655                    let var1 = "«ˇhello world»";
 9656                }
 9657            "#},
 9658            cx,
 9659        );
 9660    });
 9661
 9662    // Test 5: Expansion beyond string
 9663    editor.update_in(cx, |editor, window, cx| {
 9664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9665        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9666        assert_text_with_selections(
 9667            editor,
 9668            indoc! {r#"
 9669                use mod1::mod2::{mod3, mod4};
 9670
 9671                fn fn_1(param1: bool, param2: &str) {
 9672                    «ˇlet var1 = "hello world";»
 9673                }
 9674            "#},
 9675            cx,
 9676        );
 9677    });
 9678}
 9679
 9680#[gpui::test]
 9681async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9682    init_test(cx, |_| {});
 9683
 9684    let mut cx = EditorTestContext::new(cx).await;
 9685
 9686    let language = Arc::new(Language::new(
 9687        LanguageConfig::default(),
 9688        Some(tree_sitter_rust::LANGUAGE.into()),
 9689    ));
 9690
 9691    cx.update_buffer(|buffer, cx| {
 9692        buffer.set_language(Some(language), cx);
 9693    });
 9694
 9695    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9696    cx.update_editor(|editor, window, cx| {
 9697        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9698    });
 9699
 9700    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9701
 9702    cx.set_state(indoc! { r#"fn a() {
 9703          // what
 9704          // a
 9705          // ˇlong
 9706          // method
 9707          // I
 9708          // sure
 9709          // hope
 9710          // it
 9711          // works
 9712    }"# });
 9713
 9714    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9715    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9716    cx.update(|_, cx| {
 9717        multi_buffer.update(cx, |multi_buffer, cx| {
 9718            multi_buffer.set_excerpts_for_path(
 9719                PathKey::for_buffer(&buffer, cx),
 9720                buffer,
 9721                [Point::new(1, 0)..Point::new(1, 0)],
 9722                3,
 9723                cx,
 9724            );
 9725        });
 9726    });
 9727
 9728    let editor2 = cx.new_window_entity(|window, cx| {
 9729        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9730    });
 9731
 9732    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9733    cx.update_editor(|editor, window, cx| {
 9734        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9735            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9736        })
 9737    });
 9738
 9739    cx.assert_editor_state(indoc! { "
 9740        fn a() {
 9741              // what
 9742              // a
 9743        ˇ      // long
 9744              // method"});
 9745
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9748    });
 9749
 9750    // Although we could potentially make the action work when the syntax node
 9751    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9752    // did. Maybe we could also expand the excerpt to contain the range?
 9753    cx.assert_editor_state(indoc! { "
 9754        fn a() {
 9755              // what
 9756              // a
 9757        ˇ      // long
 9758              // method"});
 9759}
 9760
 9761#[gpui::test]
 9762async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9763    init_test(cx, |_| {});
 9764
 9765    let base_text = r#"
 9766        impl A {
 9767            // this is an uncommitted comment
 9768
 9769            fn b() {
 9770                c();
 9771            }
 9772
 9773            // this is another uncommitted comment
 9774
 9775            fn d() {
 9776                // e
 9777                // f
 9778            }
 9779        }
 9780
 9781        fn g() {
 9782            // h
 9783        }
 9784    "#
 9785    .unindent();
 9786
 9787    let text = r#"
 9788        ˇimpl A {
 9789
 9790            fn b() {
 9791                c();
 9792            }
 9793
 9794            fn d() {
 9795                // e
 9796                // f
 9797            }
 9798        }
 9799
 9800        fn g() {
 9801            // h
 9802        }
 9803    "#
 9804    .unindent();
 9805
 9806    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9807    cx.set_state(&text);
 9808    cx.set_head_text(&base_text);
 9809    cx.update_editor(|editor, window, cx| {
 9810        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9811    });
 9812
 9813    cx.assert_state_with_diff(
 9814        "
 9815        ˇimpl A {
 9816      -     // this is an uncommitted comment
 9817
 9818            fn b() {
 9819                c();
 9820            }
 9821
 9822      -     // this is another uncommitted comment
 9823      -
 9824            fn d() {
 9825                // e
 9826                // f
 9827            }
 9828        }
 9829
 9830        fn g() {
 9831            // h
 9832        }
 9833    "
 9834        .unindent(),
 9835    );
 9836
 9837    let expected_display_text = "
 9838        impl A {
 9839            // this is an uncommitted comment
 9840
 9841            fn b() {
 9842 9843            }
 9844
 9845            // this is another uncommitted comment
 9846
 9847            fn d() {
 9848 9849            }
 9850        }
 9851
 9852        fn g() {
 9853 9854        }
 9855        "
 9856    .unindent();
 9857
 9858    cx.update_editor(|editor, window, cx| {
 9859        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9860        assert_eq!(editor.display_text(cx), expected_display_text);
 9861    });
 9862}
 9863
 9864#[gpui::test]
 9865async fn test_autoindent(cx: &mut TestAppContext) {
 9866    init_test(cx, |_| {});
 9867
 9868    let language = Arc::new(
 9869        Language::new(
 9870            LanguageConfig {
 9871                brackets: BracketPairConfig {
 9872                    pairs: vec![
 9873                        BracketPair {
 9874                            start: "{".to_string(),
 9875                            end: "}".to_string(),
 9876                            close: false,
 9877                            surround: false,
 9878                            newline: true,
 9879                        },
 9880                        BracketPair {
 9881                            start: "(".to_string(),
 9882                            end: ")".to_string(),
 9883                            close: false,
 9884                            surround: false,
 9885                            newline: true,
 9886                        },
 9887                    ],
 9888                    ..Default::default()
 9889                },
 9890                ..Default::default()
 9891            },
 9892            Some(tree_sitter_rust::LANGUAGE.into()),
 9893        )
 9894        .with_indents_query(
 9895            r#"
 9896                (_ "(" ")" @end) @indent
 9897                (_ "{" "}" @end) @indent
 9898            "#,
 9899        )
 9900        .unwrap(),
 9901    );
 9902
 9903    let text = "fn a() {}";
 9904
 9905    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9906    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9907    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9908    editor
 9909        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9910        .await;
 9911
 9912    editor.update_in(cx, |editor, window, cx| {
 9913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9914            s.select_ranges([
 9915                MultiBufferOffset(5)..MultiBufferOffset(5),
 9916                MultiBufferOffset(8)..MultiBufferOffset(8),
 9917                MultiBufferOffset(9)..MultiBufferOffset(9),
 9918            ])
 9919        });
 9920        editor.newline(&Newline, window, cx);
 9921        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9922        assert_eq!(
 9923            editor.selections.ranges(&editor.display_snapshot(cx)),
 9924            &[
 9925                Point::new(1, 4)..Point::new(1, 4),
 9926                Point::new(3, 4)..Point::new(3, 4),
 9927                Point::new(5, 0)..Point::new(5, 0)
 9928            ]
 9929        );
 9930    });
 9931}
 9932
 9933#[gpui::test]
 9934async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9935    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9936
 9937    let language = Arc::new(
 9938        Language::new(
 9939            LanguageConfig {
 9940                brackets: BracketPairConfig {
 9941                    pairs: vec![
 9942                        BracketPair {
 9943                            start: "{".to_string(),
 9944                            end: "}".to_string(),
 9945                            close: false,
 9946                            surround: false,
 9947                            newline: true,
 9948                        },
 9949                        BracketPair {
 9950                            start: "(".to_string(),
 9951                            end: ")".to_string(),
 9952                            close: false,
 9953                            surround: false,
 9954                            newline: true,
 9955                        },
 9956                    ],
 9957                    ..Default::default()
 9958                },
 9959                ..Default::default()
 9960            },
 9961            Some(tree_sitter_rust::LANGUAGE.into()),
 9962        )
 9963        .with_indents_query(
 9964            r#"
 9965                (_ "(" ")" @end) @indent
 9966                (_ "{" "}" @end) @indent
 9967            "#,
 9968        )
 9969        .unwrap(),
 9970    );
 9971
 9972    let text = "fn a() {}";
 9973
 9974    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9975    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9976    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9977    editor
 9978        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9979        .await;
 9980
 9981    editor.update_in(cx, |editor, window, cx| {
 9982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9983            s.select_ranges([
 9984                MultiBufferOffset(5)..MultiBufferOffset(5),
 9985                MultiBufferOffset(8)..MultiBufferOffset(8),
 9986                MultiBufferOffset(9)..MultiBufferOffset(9),
 9987            ])
 9988        });
 9989        editor.newline(&Newline, window, cx);
 9990        assert_eq!(
 9991            editor.text(cx),
 9992            indoc!(
 9993                "
 9994                fn a(
 9995
 9996                ) {
 9997
 9998                }
 9999                "
10000            )
10001        );
10002        assert_eq!(
10003            editor.selections.ranges(&editor.display_snapshot(cx)),
10004            &[
10005                Point::new(1, 0)..Point::new(1, 0),
10006                Point::new(3, 0)..Point::new(3, 0),
10007                Point::new(5, 0)..Point::new(5, 0)
10008            ]
10009        );
10010    });
10011}
10012
10013#[gpui::test]
10014async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10015    init_test(cx, |settings| {
10016        settings.defaults.auto_indent = Some(true);
10017        settings.languages.0.insert(
10018            "python".into(),
10019            LanguageSettingsContent {
10020                auto_indent: Some(false),
10021                ..Default::default()
10022            },
10023        );
10024    });
10025
10026    let mut cx = EditorTestContext::new(cx).await;
10027
10028    let injected_language = Arc::new(
10029        Language::new(
10030            LanguageConfig {
10031                brackets: BracketPairConfig {
10032                    pairs: vec![
10033                        BracketPair {
10034                            start: "{".to_string(),
10035                            end: "}".to_string(),
10036                            close: false,
10037                            surround: false,
10038                            newline: true,
10039                        },
10040                        BracketPair {
10041                            start: "(".to_string(),
10042                            end: ")".to_string(),
10043                            close: true,
10044                            surround: false,
10045                            newline: true,
10046                        },
10047                    ],
10048                    ..Default::default()
10049                },
10050                name: "python".into(),
10051                ..Default::default()
10052            },
10053            Some(tree_sitter_python::LANGUAGE.into()),
10054        )
10055        .with_indents_query(
10056            r#"
10057                (_ "(" ")" @end) @indent
10058                (_ "{" "}" @end) @indent
10059            "#,
10060        )
10061        .unwrap(),
10062    );
10063
10064    let language = Arc::new(
10065        Language::new(
10066            LanguageConfig {
10067                brackets: BracketPairConfig {
10068                    pairs: vec![
10069                        BracketPair {
10070                            start: "{".to_string(),
10071                            end: "}".to_string(),
10072                            close: false,
10073                            surround: false,
10074                            newline: true,
10075                        },
10076                        BracketPair {
10077                            start: "(".to_string(),
10078                            end: ")".to_string(),
10079                            close: true,
10080                            surround: false,
10081                            newline: true,
10082                        },
10083                    ],
10084                    ..Default::default()
10085                },
10086                name: LanguageName::new_static("rust"),
10087                ..Default::default()
10088            },
10089            Some(tree_sitter_rust::LANGUAGE.into()),
10090        )
10091        .with_indents_query(
10092            r#"
10093                (_ "(" ")" @end) @indent
10094                (_ "{" "}" @end) @indent
10095            "#,
10096        )
10097        .unwrap()
10098        .with_injection_query(
10099            r#"
10100            (macro_invocation
10101                macro: (identifier) @_macro_name
10102                (token_tree) @injection.content
10103                (#set! injection.language "python"))
10104           "#,
10105        )
10106        .unwrap(),
10107    );
10108
10109    cx.language_registry().add(injected_language);
10110    cx.language_registry().add(language.clone());
10111
10112    cx.update_buffer(|buffer, cx| {
10113        buffer.set_language(Some(language), cx);
10114    });
10115
10116    cx.set_state(r#"struct A {ˇ}"#);
10117
10118    cx.update_editor(|editor, window, cx| {
10119        editor.newline(&Default::default(), window, cx);
10120    });
10121
10122    cx.assert_editor_state(indoc!(
10123        "struct A {
10124            ˇ
10125        }"
10126    ));
10127
10128    cx.set_state(r#"select_biased!(ˇ)"#);
10129
10130    cx.update_editor(|editor, window, cx| {
10131        editor.newline(&Default::default(), window, cx);
10132        editor.handle_input("def ", window, cx);
10133        editor.handle_input("(", window, cx);
10134        editor.newline(&Default::default(), window, cx);
10135        editor.handle_input("a", window, cx);
10136    });
10137
10138    cx.assert_editor_state(indoc!(
10139        "select_biased!(
10140        def (
1014110142        )
10143        )"
10144    ));
10145}
10146
10147#[gpui::test]
10148async fn test_autoindent_selections(cx: &mut TestAppContext) {
10149    init_test(cx, |_| {});
10150
10151    {
10152        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10153        cx.set_state(indoc! {"
10154            impl A {
10155
10156                fn b() {}
10157
10158            «fn c() {
10159
10160            }ˇ»
10161            }
10162        "});
10163
10164        cx.update_editor(|editor, window, cx| {
10165            editor.autoindent(&Default::default(), window, cx);
10166        });
10167
10168        cx.assert_editor_state(indoc! {"
10169            impl A {
10170
10171                fn b() {}
10172
10173                «fn c() {
10174
10175                }ˇ»
10176            }
10177        "});
10178    }
10179
10180    {
10181        let mut cx = EditorTestContext::new_multibuffer(
10182            cx,
10183            [indoc! { "
10184                impl A {
10185                «
10186                // a
10187                fn b(){}
10188                »
10189                «
10190                    }
10191                    fn c(){}
10192                »
10193            "}],
10194        );
10195
10196        let buffer = cx.update_editor(|editor, _, cx| {
10197            let buffer = editor.buffer().update(cx, |buffer, _| {
10198                buffer.all_buffers().iter().next().unwrap().clone()
10199            });
10200            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10201            buffer
10202        });
10203
10204        cx.run_until_parked();
10205        cx.update_editor(|editor, window, cx| {
10206            editor.select_all(&Default::default(), window, cx);
10207            editor.autoindent(&Default::default(), window, cx)
10208        });
10209        cx.run_until_parked();
10210
10211        cx.update(|_, cx| {
10212            assert_eq!(
10213                buffer.read(cx).text(),
10214                indoc! { "
10215                    impl A {
10216
10217                        // a
10218                        fn b(){}
10219
10220
10221                    }
10222                    fn c(){}
10223
10224                " }
10225            )
10226        });
10227    }
10228}
10229
10230#[gpui::test]
10231async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10232    init_test(cx, |_| {});
10233
10234    let mut cx = EditorTestContext::new(cx).await;
10235
10236    let language = Arc::new(Language::new(
10237        LanguageConfig {
10238            brackets: BracketPairConfig {
10239                pairs: vec![
10240                    BracketPair {
10241                        start: "{".to_string(),
10242                        end: "}".to_string(),
10243                        close: true,
10244                        surround: true,
10245                        newline: true,
10246                    },
10247                    BracketPair {
10248                        start: "(".to_string(),
10249                        end: ")".to_string(),
10250                        close: true,
10251                        surround: true,
10252                        newline: true,
10253                    },
10254                    BracketPair {
10255                        start: "/*".to_string(),
10256                        end: " */".to_string(),
10257                        close: true,
10258                        surround: true,
10259                        newline: true,
10260                    },
10261                    BracketPair {
10262                        start: "[".to_string(),
10263                        end: "]".to_string(),
10264                        close: false,
10265                        surround: false,
10266                        newline: true,
10267                    },
10268                    BracketPair {
10269                        start: "\"".to_string(),
10270                        end: "\"".to_string(),
10271                        close: true,
10272                        surround: true,
10273                        newline: false,
10274                    },
10275                    BracketPair {
10276                        start: "<".to_string(),
10277                        end: ">".to_string(),
10278                        close: false,
10279                        surround: true,
10280                        newline: true,
10281                    },
10282                ],
10283                ..Default::default()
10284            },
10285            autoclose_before: "})]".to_string(),
10286            ..Default::default()
10287        },
10288        Some(tree_sitter_rust::LANGUAGE.into()),
10289    ));
10290
10291    cx.language_registry().add(language.clone());
10292    cx.update_buffer(|buffer, cx| {
10293        buffer.set_language(Some(language), cx);
10294    });
10295
10296    cx.set_state(
10297        &r#"
10298            🏀ˇ
10299            εˇ
10300            ❤️ˇ
10301        "#
10302        .unindent(),
10303    );
10304
10305    // autoclose multiple nested brackets at multiple cursors
10306    cx.update_editor(|editor, window, cx| {
10307        editor.handle_input("{", window, cx);
10308        editor.handle_input("{", window, cx);
10309        editor.handle_input("{", window, cx);
10310    });
10311    cx.assert_editor_state(
10312        &"
10313            🏀{{{ˇ}}}
10314            ε{{{ˇ}}}
10315            ❤️{{{ˇ}}}
10316        "
10317        .unindent(),
10318    );
10319
10320    // insert a different closing bracket
10321    cx.update_editor(|editor, window, cx| {
10322        editor.handle_input(")", window, cx);
10323    });
10324    cx.assert_editor_state(
10325        &"
10326            🏀{{{)ˇ}}}
10327            ε{{{)ˇ}}}
10328            ❤️{{{)ˇ}}}
10329        "
10330        .unindent(),
10331    );
10332
10333    // skip over the auto-closed brackets when typing a closing bracket
10334    cx.update_editor(|editor, window, cx| {
10335        editor.move_right(&MoveRight, window, cx);
10336        editor.handle_input("}", window, cx);
10337        editor.handle_input("}", window, cx);
10338        editor.handle_input("}", window, cx);
10339    });
10340    cx.assert_editor_state(
10341        &"
10342            🏀{{{)}}}}ˇ
10343            ε{{{)}}}}ˇ
10344            ❤️{{{)}}}}ˇ
10345        "
10346        .unindent(),
10347    );
10348
10349    // autoclose multi-character pairs
10350    cx.set_state(
10351        &"
10352            ˇ
10353            ˇ
10354        "
10355        .unindent(),
10356    );
10357    cx.update_editor(|editor, window, cx| {
10358        editor.handle_input("/", window, cx);
10359        editor.handle_input("*", window, cx);
10360    });
10361    cx.assert_editor_state(
10362        &"
10363            /*ˇ */
10364            /*ˇ */
10365        "
10366        .unindent(),
10367    );
10368
10369    // one cursor autocloses a multi-character pair, one cursor
10370    // does not autoclose.
10371    cx.set_state(
10372        &"
1037310374            ˇ
10375        "
10376        .unindent(),
10377    );
10378    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10379    cx.assert_editor_state(
10380        &"
10381            /*ˇ */
1038210383        "
10384        .unindent(),
10385    );
10386
10387    // Don't autoclose if the next character isn't whitespace and isn't
10388    // listed in the language's "autoclose_before" section.
10389    cx.set_state("ˇa b");
10390    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10391    cx.assert_editor_state("{ˇa b");
10392
10393    // Don't autoclose if `close` is false for the bracket pair
10394    cx.set_state("ˇ");
10395    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10396    cx.assert_editor_state("");
10397
10398    // Surround with brackets if text is selected
10399    cx.set_state("«aˇ» b");
10400    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10401    cx.assert_editor_state("{«aˇ»} b");
10402
10403    // Autoclose when not immediately after a word character
10404    cx.set_state("a ˇ");
10405    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10406    cx.assert_editor_state("a \"ˇ\"");
10407
10408    // Autoclose pair where the start and end characters are the same
10409    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10410    cx.assert_editor_state("a \"\"ˇ");
10411
10412    // Don't autoclose when immediately after a word character
10413    cx.set_state("");
10414    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10415    cx.assert_editor_state("a\"ˇ");
10416
10417    // Do autoclose when after a non-word character
10418    cx.set_state("");
10419    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10420    cx.assert_editor_state("{\"ˇ\"");
10421
10422    // Non identical pairs autoclose regardless of preceding character
10423    cx.set_state("");
10424    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10425    cx.assert_editor_state("a{ˇ}");
10426
10427    // Don't autoclose pair if autoclose is disabled
10428    cx.set_state("ˇ");
10429    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10430    cx.assert_editor_state("");
10431
10432    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10433    cx.set_state("«aˇ» b");
10434    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10435    cx.assert_editor_state("<«aˇ»> b");
10436}
10437
10438#[gpui::test]
10439async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10440    init_test(cx, |settings| {
10441        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10442    });
10443
10444    let mut cx = EditorTestContext::new(cx).await;
10445
10446    let language = Arc::new(Language::new(
10447        LanguageConfig {
10448            brackets: BracketPairConfig {
10449                pairs: vec![
10450                    BracketPair {
10451                        start: "{".to_string(),
10452                        end: "}".to_string(),
10453                        close: true,
10454                        surround: true,
10455                        newline: true,
10456                    },
10457                    BracketPair {
10458                        start: "(".to_string(),
10459                        end: ")".to_string(),
10460                        close: true,
10461                        surround: true,
10462                        newline: true,
10463                    },
10464                    BracketPair {
10465                        start: "[".to_string(),
10466                        end: "]".to_string(),
10467                        close: false,
10468                        surround: false,
10469                        newline: true,
10470                    },
10471                ],
10472                ..Default::default()
10473            },
10474            autoclose_before: "})]".to_string(),
10475            ..Default::default()
10476        },
10477        Some(tree_sitter_rust::LANGUAGE.into()),
10478    ));
10479
10480    cx.language_registry().add(language.clone());
10481    cx.update_buffer(|buffer, cx| {
10482        buffer.set_language(Some(language), cx);
10483    });
10484
10485    cx.set_state(
10486        &"
10487            ˇ
10488            ˇ
10489            ˇ
10490        "
10491        .unindent(),
10492    );
10493
10494    // ensure only matching closing brackets are skipped over
10495    cx.update_editor(|editor, window, cx| {
10496        editor.handle_input("}", window, cx);
10497        editor.move_left(&MoveLeft, window, cx);
10498        editor.handle_input(")", window, cx);
10499        editor.move_left(&MoveLeft, window, cx);
10500    });
10501    cx.assert_editor_state(
10502        &"
10503            ˇ)}
10504            ˇ)}
10505            ˇ)}
10506        "
10507        .unindent(),
10508    );
10509
10510    // skip-over closing brackets at multiple cursors
10511    cx.update_editor(|editor, window, cx| {
10512        editor.handle_input(")", window, cx);
10513        editor.handle_input("}", window, cx);
10514    });
10515    cx.assert_editor_state(
10516        &"
10517            )}ˇ
10518            )}ˇ
10519            )}ˇ
10520        "
10521        .unindent(),
10522    );
10523
10524    // ignore non-close brackets
10525    cx.update_editor(|editor, window, cx| {
10526        editor.handle_input("]", window, cx);
10527        editor.move_left(&MoveLeft, window, cx);
10528        editor.handle_input("]", window, cx);
10529    });
10530    cx.assert_editor_state(
10531        &"
10532            )}]ˇ]
10533            )}]ˇ]
10534            )}]ˇ]
10535        "
10536        .unindent(),
10537    );
10538}
10539
10540#[gpui::test]
10541async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10542    init_test(cx, |_| {});
10543
10544    let mut cx = EditorTestContext::new(cx).await;
10545
10546    let html_language = Arc::new(
10547        Language::new(
10548            LanguageConfig {
10549                name: "HTML".into(),
10550                brackets: BracketPairConfig {
10551                    pairs: vec![
10552                        BracketPair {
10553                            start: "<".into(),
10554                            end: ">".into(),
10555                            close: true,
10556                            ..Default::default()
10557                        },
10558                        BracketPair {
10559                            start: "{".into(),
10560                            end: "}".into(),
10561                            close: true,
10562                            ..Default::default()
10563                        },
10564                        BracketPair {
10565                            start: "(".into(),
10566                            end: ")".into(),
10567                            close: true,
10568                            ..Default::default()
10569                        },
10570                    ],
10571                    ..Default::default()
10572                },
10573                autoclose_before: "})]>".into(),
10574                ..Default::default()
10575            },
10576            Some(tree_sitter_html::LANGUAGE.into()),
10577        )
10578        .with_injection_query(
10579            r#"
10580            (script_element
10581                (raw_text) @injection.content
10582                (#set! injection.language "javascript"))
10583            "#,
10584        )
10585        .unwrap(),
10586    );
10587
10588    let javascript_language = Arc::new(Language::new(
10589        LanguageConfig {
10590            name: "JavaScript".into(),
10591            brackets: BracketPairConfig {
10592                pairs: vec![
10593                    BracketPair {
10594                        start: "/*".into(),
10595                        end: " */".into(),
10596                        close: true,
10597                        ..Default::default()
10598                    },
10599                    BracketPair {
10600                        start: "{".into(),
10601                        end: "}".into(),
10602                        close: true,
10603                        ..Default::default()
10604                    },
10605                    BracketPair {
10606                        start: "(".into(),
10607                        end: ")".into(),
10608                        close: true,
10609                        ..Default::default()
10610                    },
10611                ],
10612                ..Default::default()
10613            },
10614            autoclose_before: "})]>".into(),
10615            ..Default::default()
10616        },
10617        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10618    ));
10619
10620    cx.language_registry().add(html_language.clone());
10621    cx.language_registry().add(javascript_language);
10622    cx.executor().run_until_parked();
10623
10624    cx.update_buffer(|buffer, cx| {
10625        buffer.set_language(Some(html_language), cx);
10626    });
10627
10628    cx.set_state(
10629        &r#"
10630            <body>ˇ
10631                <script>
10632                    var x = 1;ˇ
10633                </script>
10634            </body>ˇ
10635        "#
10636        .unindent(),
10637    );
10638
10639    // Precondition: different languages are active at different locations.
10640    cx.update_editor(|editor, window, cx| {
10641        let snapshot = editor.snapshot(window, cx);
10642        let cursors = editor
10643            .selections
10644            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10645        let languages = cursors
10646            .iter()
10647            .map(|c| snapshot.language_at(c.start).unwrap().name())
10648            .collect::<Vec<_>>();
10649        assert_eq!(
10650            languages,
10651            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10652        );
10653    });
10654
10655    // Angle brackets autoclose in HTML, but not JavaScript.
10656    cx.update_editor(|editor, window, cx| {
10657        editor.handle_input("<", window, cx);
10658        editor.handle_input("a", window, cx);
10659    });
10660    cx.assert_editor_state(
10661        &r#"
10662            <body><aˇ>
10663                <script>
10664                    var x = 1;<aˇ
10665                </script>
10666            </body><aˇ>
10667        "#
10668        .unindent(),
10669    );
10670
10671    // Curly braces and parens autoclose in both HTML and JavaScript.
10672    cx.update_editor(|editor, window, cx| {
10673        editor.handle_input(" b=", window, cx);
10674        editor.handle_input("{", window, cx);
10675        editor.handle_input("c", window, cx);
10676        editor.handle_input("(", window, cx);
10677    });
10678    cx.assert_editor_state(
10679        &r#"
10680            <body><a b={c(ˇ)}>
10681                <script>
10682                    var x = 1;<a b={c(ˇ)}
10683                </script>
10684            </body><a b={c(ˇ)}>
10685        "#
10686        .unindent(),
10687    );
10688
10689    // Brackets that were already autoclosed are skipped.
10690    cx.update_editor(|editor, window, cx| {
10691        editor.handle_input(")", window, cx);
10692        editor.handle_input("d", window, cx);
10693        editor.handle_input("}", window, cx);
10694    });
10695    cx.assert_editor_state(
10696        &r#"
10697            <body><a b={c()d}ˇ>
10698                <script>
10699                    var x = 1;<a b={c()d}ˇ
10700                </script>
10701            </body><a b={c()d}ˇ>
10702        "#
10703        .unindent(),
10704    );
10705    cx.update_editor(|editor, window, cx| {
10706        editor.handle_input(">", window, cx);
10707    });
10708    cx.assert_editor_state(
10709        &r#"
10710            <body><a b={c()d}>ˇ
10711                <script>
10712                    var x = 1;<a b={c()d}>ˇ
10713                </script>
10714            </body><a b={c()d}>ˇ
10715        "#
10716        .unindent(),
10717    );
10718
10719    // Reset
10720    cx.set_state(
10721        &r#"
10722            <body>ˇ
10723                <script>
10724                    var x = 1;ˇ
10725                </script>
10726            </body>ˇ
10727        "#
10728        .unindent(),
10729    );
10730
10731    cx.update_editor(|editor, window, cx| {
10732        editor.handle_input("<", window, cx);
10733    });
10734    cx.assert_editor_state(
10735        &r#"
10736            <body><ˇ>
10737                <script>
10738                    var x = 1;<ˇ
10739                </script>
10740            </body><ˇ>
10741        "#
10742        .unindent(),
10743    );
10744
10745    // When backspacing, the closing angle brackets are removed.
10746    cx.update_editor(|editor, window, cx| {
10747        editor.backspace(&Backspace, window, cx);
10748    });
10749    cx.assert_editor_state(
10750        &r#"
10751            <body>ˇ
10752                <script>
10753                    var x = 1;ˇ
10754                </script>
10755            </body>ˇ
10756        "#
10757        .unindent(),
10758    );
10759
10760    // Block comments autoclose in JavaScript, but not HTML.
10761    cx.update_editor(|editor, window, cx| {
10762        editor.handle_input("/", window, cx);
10763        editor.handle_input("*", window, cx);
10764    });
10765    cx.assert_editor_state(
10766        &r#"
10767            <body>/*ˇ
10768                <script>
10769                    var x = 1;/*ˇ */
10770                </script>
10771            </body>/*ˇ
10772        "#
10773        .unindent(),
10774    );
10775}
10776
10777#[gpui::test]
10778async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10779    init_test(cx, |_| {});
10780
10781    let mut cx = EditorTestContext::new(cx).await;
10782
10783    let rust_language = Arc::new(
10784        Language::new(
10785            LanguageConfig {
10786                name: "Rust".into(),
10787                brackets: serde_json::from_value(json!([
10788                    { "start": "{", "end": "}", "close": true, "newline": true },
10789                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10790                ]))
10791                .unwrap(),
10792                autoclose_before: "})]>".into(),
10793                ..Default::default()
10794            },
10795            Some(tree_sitter_rust::LANGUAGE.into()),
10796        )
10797        .with_override_query("(string_literal) @string")
10798        .unwrap(),
10799    );
10800
10801    cx.language_registry().add(rust_language.clone());
10802    cx.update_buffer(|buffer, cx| {
10803        buffer.set_language(Some(rust_language), cx);
10804    });
10805
10806    cx.set_state(
10807        &r#"
10808            let x = ˇ
10809        "#
10810        .unindent(),
10811    );
10812
10813    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10814    cx.update_editor(|editor, window, cx| {
10815        editor.handle_input("\"", window, cx);
10816    });
10817    cx.assert_editor_state(
10818        &r#"
10819            let x = "ˇ"
10820        "#
10821        .unindent(),
10822    );
10823
10824    // Inserting another quotation mark. The cursor moves across the existing
10825    // automatically-inserted quotation mark.
10826    cx.update_editor(|editor, window, cx| {
10827        editor.handle_input("\"", window, cx);
10828    });
10829    cx.assert_editor_state(
10830        &r#"
10831            let x = ""ˇ
10832        "#
10833        .unindent(),
10834    );
10835
10836    // Reset
10837    cx.set_state(
10838        &r#"
10839            let x = ˇ
10840        "#
10841        .unindent(),
10842    );
10843
10844    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10845    cx.update_editor(|editor, window, cx| {
10846        editor.handle_input("\"", window, cx);
10847        editor.handle_input(" ", window, cx);
10848        editor.move_left(&Default::default(), window, cx);
10849        editor.handle_input("\\", window, cx);
10850        editor.handle_input("\"", window, cx);
10851    });
10852    cx.assert_editor_state(
10853        &r#"
10854            let x = "\"ˇ "
10855        "#
10856        .unindent(),
10857    );
10858
10859    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10860    // mark. Nothing is inserted.
10861    cx.update_editor(|editor, window, cx| {
10862        editor.move_right(&Default::default(), window, cx);
10863        editor.handle_input("\"", window, cx);
10864    });
10865    cx.assert_editor_state(
10866        &r#"
10867            let x = "\" "ˇ
10868        "#
10869        .unindent(),
10870    );
10871}
10872
10873#[gpui::test]
10874async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
10875    init_test(cx, |_| {});
10876
10877    let mut cx = EditorTestContext::new(cx).await;
10878    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10879
10880    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10881
10882    // Double quote inside single-quoted string
10883    cx.set_state(indoc! {r#"
10884        def main():
10885            items = ['"', ˇ]
10886    "#});
10887    cx.update_editor(|editor, window, cx| {
10888        editor.handle_input("\"", window, cx);
10889    });
10890    cx.assert_editor_state(indoc! {r#"
10891        def main():
10892            items = ['"', "ˇ"]
10893    "#});
10894
10895    // Two double quotes inside single-quoted string
10896    cx.set_state(indoc! {r#"
10897        def main():
10898            items = ['""', ˇ]
10899    "#});
10900    cx.update_editor(|editor, window, cx| {
10901        editor.handle_input("\"", window, cx);
10902    });
10903    cx.assert_editor_state(indoc! {r#"
10904        def main():
10905            items = ['""', "ˇ"]
10906    "#});
10907
10908    // Single quote inside double-quoted string
10909    cx.set_state(indoc! {r#"
10910        def main():
10911            items = ["'", ˇ]
10912    "#});
10913    cx.update_editor(|editor, window, cx| {
10914        editor.handle_input("'", window, cx);
10915    });
10916    cx.assert_editor_state(indoc! {r#"
10917        def main():
10918            items = ["'", 'ˇ']
10919    "#});
10920
10921    // Two single quotes inside double-quoted string
10922    cx.set_state(indoc! {r#"
10923        def main():
10924            items = ["''", ˇ]
10925    "#});
10926    cx.update_editor(|editor, window, cx| {
10927        editor.handle_input("'", window, cx);
10928    });
10929    cx.assert_editor_state(indoc! {r#"
10930        def main():
10931            items = ["''", 'ˇ']
10932    "#});
10933
10934    // Mixed quotes on same line
10935    cx.set_state(indoc! {r#"
10936        def main():
10937            items = ['"""', "'''''", ˇ]
10938    "#});
10939    cx.update_editor(|editor, window, cx| {
10940        editor.handle_input("\"", window, cx);
10941    });
10942    cx.assert_editor_state(indoc! {r#"
10943        def main():
10944            items = ['"""', "'''''", "ˇ"]
10945    "#});
10946    cx.update_editor(|editor, window, cx| {
10947        editor.move_right(&MoveRight, window, cx);
10948    });
10949    cx.update_editor(|editor, window, cx| {
10950        editor.handle_input(", ", window, cx);
10951    });
10952    cx.update_editor(|editor, window, cx| {
10953        editor.handle_input("'", window, cx);
10954    });
10955    cx.assert_editor_state(indoc! {r#"
10956        def main():
10957            items = ['"""', "'''''", "", 'ˇ']
10958    "#});
10959}
10960
10961#[gpui::test]
10962async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
10963    init_test(cx, |_| {});
10964
10965    let mut cx = EditorTestContext::new(cx).await;
10966    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10967    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10968
10969    cx.set_state(indoc! {r#"
10970        def main():
10971            items = ["🎉", ˇ]
10972    "#});
10973    cx.update_editor(|editor, window, cx| {
10974        editor.handle_input("\"", window, cx);
10975    });
10976    cx.assert_editor_state(indoc! {r#"
10977        def main():
10978            items = ["🎉", "ˇ"]
10979    "#});
10980}
10981
10982#[gpui::test]
10983async fn test_surround_with_pair(cx: &mut TestAppContext) {
10984    init_test(cx, |_| {});
10985
10986    let language = Arc::new(Language::new(
10987        LanguageConfig {
10988            brackets: BracketPairConfig {
10989                pairs: vec![
10990                    BracketPair {
10991                        start: "{".to_string(),
10992                        end: "}".to_string(),
10993                        close: true,
10994                        surround: true,
10995                        newline: true,
10996                    },
10997                    BracketPair {
10998                        start: "/* ".to_string(),
10999                        end: "*/".to_string(),
11000                        close: true,
11001                        surround: true,
11002                        ..Default::default()
11003                    },
11004                ],
11005                ..Default::default()
11006            },
11007            ..Default::default()
11008        },
11009        Some(tree_sitter_rust::LANGUAGE.into()),
11010    ));
11011
11012    let text = r#"
11013        a
11014        b
11015        c
11016    "#
11017    .unindent();
11018
11019    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11020    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11021    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11022    editor
11023        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11024        .await;
11025
11026    editor.update_in(cx, |editor, window, cx| {
11027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11028            s.select_display_ranges([
11029                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11030                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11031                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11032            ])
11033        });
11034
11035        editor.handle_input("{", window, cx);
11036        editor.handle_input("{", window, cx);
11037        editor.handle_input("{", window, cx);
11038        assert_eq!(
11039            editor.text(cx),
11040            "
11041                {{{a}}}
11042                {{{b}}}
11043                {{{c}}}
11044            "
11045            .unindent()
11046        );
11047        assert_eq!(
11048            display_ranges(editor, cx),
11049            [
11050                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11051                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11052                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11053            ]
11054        );
11055
11056        editor.undo(&Undo, window, cx);
11057        editor.undo(&Undo, window, cx);
11058        editor.undo(&Undo, window, cx);
11059        assert_eq!(
11060            editor.text(cx),
11061            "
11062                a
11063                b
11064                c
11065            "
11066            .unindent()
11067        );
11068        assert_eq!(
11069            display_ranges(editor, cx),
11070            [
11071                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11072                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11073                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11074            ]
11075        );
11076
11077        // Ensure inserting the first character of a multi-byte bracket pair
11078        // doesn't surround the selections with the bracket.
11079        editor.handle_input("/", window, cx);
11080        assert_eq!(
11081            editor.text(cx),
11082            "
11083                /
11084                /
11085                /
11086            "
11087            .unindent()
11088        );
11089        assert_eq!(
11090            display_ranges(editor, cx),
11091            [
11092                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11093                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11094                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11095            ]
11096        );
11097
11098        editor.undo(&Undo, window, cx);
11099        assert_eq!(
11100            editor.text(cx),
11101            "
11102                a
11103                b
11104                c
11105            "
11106            .unindent()
11107        );
11108        assert_eq!(
11109            display_ranges(editor, cx),
11110            [
11111                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11112                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11113                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11114            ]
11115        );
11116
11117        // Ensure inserting the last character of a multi-byte bracket pair
11118        // doesn't surround the selections with the bracket.
11119        editor.handle_input("*", window, cx);
11120        assert_eq!(
11121            editor.text(cx),
11122            "
11123                *
11124                *
11125                *
11126            "
11127            .unindent()
11128        );
11129        assert_eq!(
11130            display_ranges(editor, cx),
11131            [
11132                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11133                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11134                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11135            ]
11136        );
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11142    init_test(cx, |_| {});
11143
11144    let language = Arc::new(Language::new(
11145        LanguageConfig {
11146            brackets: BracketPairConfig {
11147                pairs: vec![BracketPair {
11148                    start: "{".to_string(),
11149                    end: "}".to_string(),
11150                    close: true,
11151                    surround: true,
11152                    newline: true,
11153                }],
11154                ..Default::default()
11155            },
11156            autoclose_before: "}".to_string(),
11157            ..Default::default()
11158        },
11159        Some(tree_sitter_rust::LANGUAGE.into()),
11160    ));
11161
11162    let text = r#"
11163        a
11164        b
11165        c
11166    "#
11167    .unindent();
11168
11169    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11170    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11171    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11172    editor
11173        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11174        .await;
11175
11176    editor.update_in(cx, |editor, window, cx| {
11177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11178            s.select_ranges([
11179                Point::new(0, 1)..Point::new(0, 1),
11180                Point::new(1, 1)..Point::new(1, 1),
11181                Point::new(2, 1)..Point::new(2, 1),
11182            ])
11183        });
11184
11185        editor.handle_input("{", window, cx);
11186        editor.handle_input("{", window, cx);
11187        editor.handle_input("_", window, cx);
11188        assert_eq!(
11189            editor.text(cx),
11190            "
11191                a{{_}}
11192                b{{_}}
11193                c{{_}}
11194            "
11195            .unindent()
11196        );
11197        assert_eq!(
11198            editor
11199                .selections
11200                .ranges::<Point>(&editor.display_snapshot(cx)),
11201            [
11202                Point::new(0, 4)..Point::new(0, 4),
11203                Point::new(1, 4)..Point::new(1, 4),
11204                Point::new(2, 4)..Point::new(2, 4)
11205            ]
11206        );
11207
11208        editor.backspace(&Default::default(), window, cx);
11209        editor.backspace(&Default::default(), window, cx);
11210        assert_eq!(
11211            editor.text(cx),
11212            "
11213                a{}
11214                b{}
11215                c{}
11216            "
11217            .unindent()
11218        );
11219        assert_eq!(
11220            editor
11221                .selections
11222                .ranges::<Point>(&editor.display_snapshot(cx)),
11223            [
11224                Point::new(0, 2)..Point::new(0, 2),
11225                Point::new(1, 2)..Point::new(1, 2),
11226                Point::new(2, 2)..Point::new(2, 2)
11227            ]
11228        );
11229
11230        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11231        assert_eq!(
11232            editor.text(cx),
11233            "
11234                a
11235                b
11236                c
11237            "
11238            .unindent()
11239        );
11240        assert_eq!(
11241            editor
11242                .selections
11243                .ranges::<Point>(&editor.display_snapshot(cx)),
11244            [
11245                Point::new(0, 1)..Point::new(0, 1),
11246                Point::new(1, 1)..Point::new(1, 1),
11247                Point::new(2, 1)..Point::new(2, 1)
11248            ]
11249        );
11250    });
11251}
11252
11253#[gpui::test]
11254async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11255    init_test(cx, |settings| {
11256        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11257    });
11258
11259    let mut cx = EditorTestContext::new(cx).await;
11260
11261    let language = Arc::new(Language::new(
11262        LanguageConfig {
11263            brackets: BracketPairConfig {
11264                pairs: vec![
11265                    BracketPair {
11266                        start: "{".to_string(),
11267                        end: "}".to_string(),
11268                        close: true,
11269                        surround: true,
11270                        newline: true,
11271                    },
11272                    BracketPair {
11273                        start: "(".to_string(),
11274                        end: ")".to_string(),
11275                        close: true,
11276                        surround: true,
11277                        newline: true,
11278                    },
11279                    BracketPair {
11280                        start: "[".to_string(),
11281                        end: "]".to_string(),
11282                        close: false,
11283                        surround: true,
11284                        newline: true,
11285                    },
11286                ],
11287                ..Default::default()
11288            },
11289            autoclose_before: "})]".to_string(),
11290            ..Default::default()
11291        },
11292        Some(tree_sitter_rust::LANGUAGE.into()),
11293    ));
11294
11295    cx.language_registry().add(language.clone());
11296    cx.update_buffer(|buffer, cx| {
11297        buffer.set_language(Some(language), cx);
11298    });
11299
11300    cx.set_state(
11301        &"
11302            {(ˇ)}
11303            [[ˇ]]
11304            {(ˇ)}
11305        "
11306        .unindent(),
11307    );
11308
11309    cx.update_editor(|editor, window, cx| {
11310        editor.backspace(&Default::default(), window, cx);
11311        editor.backspace(&Default::default(), window, cx);
11312    });
11313
11314    cx.assert_editor_state(
11315        &"
11316            ˇ
11317            ˇ]]
11318            ˇ
11319        "
11320        .unindent(),
11321    );
11322
11323    cx.update_editor(|editor, window, cx| {
11324        editor.handle_input("{", window, cx);
11325        editor.handle_input("{", window, cx);
11326        editor.move_right(&MoveRight, window, cx);
11327        editor.move_right(&MoveRight, window, cx);
11328        editor.move_left(&MoveLeft, window, cx);
11329        editor.move_left(&MoveLeft, window, cx);
11330        editor.backspace(&Default::default(), window, cx);
11331    });
11332
11333    cx.assert_editor_state(
11334        &"
11335            {ˇ}
11336            {ˇ}]]
11337            {ˇ}
11338        "
11339        .unindent(),
11340    );
11341
11342    cx.update_editor(|editor, window, cx| {
11343        editor.backspace(&Default::default(), window, cx);
11344    });
11345
11346    cx.assert_editor_state(
11347        &"
11348            ˇ
11349            ˇ]]
11350            ˇ
11351        "
11352        .unindent(),
11353    );
11354}
11355
11356#[gpui::test]
11357async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11358    init_test(cx, |_| {});
11359
11360    let language = Arc::new(Language::new(
11361        LanguageConfig::default(),
11362        Some(tree_sitter_rust::LANGUAGE.into()),
11363    ));
11364
11365    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11366    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11367    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11368    editor
11369        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11370        .await;
11371
11372    editor.update_in(cx, |editor, window, cx| {
11373        editor.set_auto_replace_emoji_shortcode(true);
11374
11375        editor.handle_input("Hello ", window, cx);
11376        editor.handle_input(":wave", window, cx);
11377        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11378
11379        editor.handle_input(":", window, cx);
11380        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11381
11382        editor.handle_input(" :smile", window, cx);
11383        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11384
11385        editor.handle_input(":", window, cx);
11386        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11387
11388        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11389        editor.handle_input(":wave", window, cx);
11390        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11391
11392        editor.handle_input(":", window, cx);
11393        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11394
11395        editor.handle_input(":1", window, cx);
11396        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11397
11398        editor.handle_input(":", window, cx);
11399        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11400
11401        // Ensure shortcode does not get replaced when it is part of a word
11402        editor.handle_input(" Test:wave", window, cx);
11403        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11404
11405        editor.handle_input(":", window, cx);
11406        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11407
11408        editor.set_auto_replace_emoji_shortcode(false);
11409
11410        // Ensure shortcode does not get replaced when auto replace is off
11411        editor.handle_input(" :wave", window, cx);
11412        assert_eq!(
11413            editor.text(cx),
11414            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11415        );
11416
11417        editor.handle_input(":", window, cx);
11418        assert_eq!(
11419            editor.text(cx),
11420            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11421        );
11422    });
11423}
11424
11425#[gpui::test]
11426async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11427    init_test(cx, |_| {});
11428
11429    let (text, insertion_ranges) = marked_text_ranges(
11430        indoc! {"
11431            ˇ
11432        "},
11433        false,
11434    );
11435
11436    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11437    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11438
11439    _ = editor.update_in(cx, |editor, window, cx| {
11440        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11441
11442        editor
11443            .insert_snippet(
11444                &insertion_ranges
11445                    .iter()
11446                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11447                    .collect::<Vec<_>>(),
11448                snippet,
11449                window,
11450                cx,
11451            )
11452            .unwrap();
11453
11454        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11455            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11456            assert_eq!(editor.text(cx), expected_text);
11457            assert_eq!(
11458                editor.selections.ranges(&editor.display_snapshot(cx)),
11459                selection_ranges
11460                    .iter()
11461                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11462                    .collect::<Vec<_>>()
11463            );
11464        }
11465
11466        assert(
11467            editor,
11468            cx,
11469            indoc! {"
11470            type «» =•
11471            "},
11472        );
11473
11474        assert!(editor.context_menu_visible(), "There should be a matches");
11475    });
11476}
11477
11478#[gpui::test]
11479async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11480    init_test(cx, |_| {});
11481
11482    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11483        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11484        assert_eq!(editor.text(cx), expected_text);
11485        assert_eq!(
11486            editor.selections.ranges(&editor.display_snapshot(cx)),
11487            selection_ranges
11488                .iter()
11489                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11490                .collect::<Vec<_>>()
11491        );
11492    }
11493
11494    let (text, insertion_ranges) = marked_text_ranges(
11495        indoc! {"
11496            ˇ
11497        "},
11498        false,
11499    );
11500
11501    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11502    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11503
11504    _ = editor.update_in(cx, |editor, window, cx| {
11505        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11506
11507        editor
11508            .insert_snippet(
11509                &insertion_ranges
11510                    .iter()
11511                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11512                    .collect::<Vec<_>>(),
11513                snippet,
11514                window,
11515                cx,
11516            )
11517            .unwrap();
11518
11519        assert_state(
11520            editor,
11521            cx,
11522            indoc! {"
11523            type «» = ;•
11524            "},
11525        );
11526
11527        assert!(
11528            editor.context_menu_visible(),
11529            "Context menu should be visible for placeholder choices"
11530        );
11531
11532        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11533
11534        assert_state(
11535            editor,
11536            cx,
11537            indoc! {"
11538            type  = «»;•
11539            "},
11540        );
11541
11542        assert!(
11543            !editor.context_menu_visible(),
11544            "Context menu should be hidden after moving to next tabstop"
11545        );
11546
11547        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11548
11549        assert_state(
11550            editor,
11551            cx,
11552            indoc! {"
11553            type  = ; ˇ
11554            "},
11555        );
11556
11557        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11558
11559        assert_state(
11560            editor,
11561            cx,
11562            indoc! {"
11563            type  = ; ˇ
11564            "},
11565        );
11566    });
11567
11568    _ = editor.update_in(cx, |editor, window, cx| {
11569        editor.select_all(&SelectAll, window, cx);
11570        editor.backspace(&Backspace, window, cx);
11571
11572        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11573        let insertion_ranges = editor
11574            .selections
11575            .all(&editor.display_snapshot(cx))
11576            .iter()
11577            .map(|s| s.range())
11578            .collect::<Vec<_>>();
11579
11580        editor
11581            .insert_snippet(&insertion_ranges, snippet, window, cx)
11582            .unwrap();
11583
11584        assert_state(editor, cx, "fn «» = value;•");
11585
11586        assert!(
11587            editor.context_menu_visible(),
11588            "Context menu should be visible for placeholder choices"
11589        );
11590
11591        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11592
11593        assert_state(editor, cx, "fn  = «valueˇ»;•");
11594
11595        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11596
11597        assert_state(editor, cx, "fn «» = value;•");
11598
11599        assert!(
11600            editor.context_menu_visible(),
11601            "Context menu should be visible again after returning to first tabstop"
11602        );
11603
11604        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11605
11606        assert_state(editor, cx, "fn «» = value;•");
11607    });
11608}
11609
11610#[gpui::test]
11611async fn test_snippets(cx: &mut TestAppContext) {
11612    init_test(cx, |_| {});
11613
11614    let mut cx = EditorTestContext::new(cx).await;
11615
11616    cx.set_state(indoc! {"
11617        a.ˇ b
11618        a.ˇ b
11619        a.ˇ b
11620    "});
11621
11622    cx.update_editor(|editor, window, cx| {
11623        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11624        let insertion_ranges = editor
11625            .selections
11626            .all(&editor.display_snapshot(cx))
11627            .iter()
11628            .map(|s| s.range())
11629            .collect::<Vec<_>>();
11630        editor
11631            .insert_snippet(&insertion_ranges, snippet, window, cx)
11632            .unwrap();
11633    });
11634
11635    cx.assert_editor_state(indoc! {"
11636        a.f(«oneˇ», two, «threeˇ») b
11637        a.f(«oneˇ», two, «threeˇ») b
11638        a.f(«oneˇ», two, «threeˇ») b
11639    "});
11640
11641    // Can't move earlier than the first tab stop
11642    cx.update_editor(|editor, window, cx| {
11643        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11644    });
11645    cx.assert_editor_state(indoc! {"
11646        a.f(«oneˇ», two, «threeˇ») b
11647        a.f(«oneˇ», two, «threeˇ») b
11648        a.f(«oneˇ», two, «threeˇ») b
11649    "});
11650
11651    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11652    cx.assert_editor_state(indoc! {"
11653        a.f(one, «twoˇ», three) b
11654        a.f(one, «twoˇ», three) b
11655        a.f(one, «twoˇ», three) b
11656    "});
11657
11658    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11659    cx.assert_editor_state(indoc! {"
11660        a.f(«oneˇ», two, «threeˇ») b
11661        a.f(«oneˇ», two, «threeˇ») b
11662        a.f(«oneˇ», two, «threeˇ») b
11663    "});
11664
11665    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11666    cx.assert_editor_state(indoc! {"
11667        a.f(one, «twoˇ», three) b
11668        a.f(one, «twoˇ», three) b
11669        a.f(one, «twoˇ», three) b
11670    "});
11671    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11672    cx.assert_editor_state(indoc! {"
11673        a.f(one, two, three)ˇ b
11674        a.f(one, two, three)ˇ b
11675        a.f(one, two, three)ˇ b
11676    "});
11677
11678    // As soon as the last tab stop is reached, snippet state is gone
11679    cx.update_editor(|editor, window, cx| {
11680        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11681    });
11682    cx.assert_editor_state(indoc! {"
11683        a.f(one, two, three)ˇ b
11684        a.f(one, two, three)ˇ b
11685        a.f(one, two, three)ˇ b
11686    "});
11687}
11688
11689#[gpui::test]
11690async fn test_snippet_indentation(cx: &mut TestAppContext) {
11691    init_test(cx, |_| {});
11692
11693    let mut cx = EditorTestContext::new(cx).await;
11694
11695    cx.update_editor(|editor, window, cx| {
11696        let snippet = Snippet::parse(indoc! {"
11697            /*
11698             * Multiline comment with leading indentation
11699             *
11700             * $1
11701             */
11702            $0"})
11703        .unwrap();
11704        let insertion_ranges = editor
11705            .selections
11706            .all(&editor.display_snapshot(cx))
11707            .iter()
11708            .map(|s| s.range())
11709            .collect::<Vec<_>>();
11710        editor
11711            .insert_snippet(&insertion_ranges, snippet, window, cx)
11712            .unwrap();
11713    });
11714
11715    cx.assert_editor_state(indoc! {"
11716        /*
11717         * Multiline comment with leading indentation
11718         *
11719         * ˇ
11720         */
11721    "});
11722
11723    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11724    cx.assert_editor_state(indoc! {"
11725        /*
11726         * Multiline comment with leading indentation
11727         *
11728         *•
11729         */
11730        ˇ"});
11731}
11732
11733#[gpui::test]
11734async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11735    init_test(cx, |_| {});
11736
11737    let mut cx = EditorTestContext::new(cx).await;
11738    cx.update_editor(|editor, _, cx| {
11739        editor.project().unwrap().update(cx, |project, cx| {
11740            project.snippets().update(cx, |snippets, _cx| {
11741                let snippet = project::snippet_provider::Snippet {
11742                    prefix: vec!["multi word".to_string()],
11743                    body: "this is many words".to_string(),
11744                    description: Some("description".to_string()),
11745                    name: "multi-word snippet test".to_string(),
11746                };
11747                snippets.add_snippet_for_test(
11748                    None,
11749                    PathBuf::from("test_snippets.json"),
11750                    vec![Arc::new(snippet)],
11751                );
11752            });
11753        })
11754    });
11755
11756    for (input_to_simulate, should_match_snippet) in [
11757        ("m", true),
11758        ("m ", true),
11759        ("m w", true),
11760        ("aa m w", true),
11761        ("aa m g", false),
11762    ] {
11763        cx.set_state("ˇ");
11764        cx.simulate_input(input_to_simulate); // fails correctly
11765
11766        cx.update_editor(|editor, _, _| {
11767            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11768            else {
11769                assert!(!should_match_snippet); // no completions! don't even show the menu
11770                return;
11771            };
11772            assert!(context_menu.visible());
11773            let completions = context_menu.completions.borrow();
11774
11775            assert_eq!(!completions.is_empty(), should_match_snippet);
11776        });
11777    }
11778}
11779
11780#[gpui::test]
11781async fn test_document_format_during_save(cx: &mut TestAppContext) {
11782    init_test(cx, |_| {});
11783
11784    let fs = FakeFs::new(cx.executor());
11785    fs.insert_file(path!("/file.rs"), Default::default()).await;
11786
11787    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11788
11789    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11790    language_registry.add(rust_lang());
11791    let mut fake_servers = language_registry.register_fake_lsp(
11792        "Rust",
11793        FakeLspAdapter {
11794            capabilities: lsp::ServerCapabilities {
11795                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11796                ..Default::default()
11797            },
11798            ..Default::default()
11799        },
11800    );
11801
11802    let buffer = project
11803        .update(cx, |project, cx| {
11804            project.open_local_buffer(path!("/file.rs"), cx)
11805        })
11806        .await
11807        .unwrap();
11808
11809    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11810    let (editor, cx) = cx.add_window_view(|window, cx| {
11811        build_editor_with_project(project.clone(), buffer, window, cx)
11812    });
11813    editor.update_in(cx, |editor, window, cx| {
11814        editor.set_text("one\ntwo\nthree\n", window, cx)
11815    });
11816    assert!(cx.read(|cx| editor.is_dirty(cx)));
11817
11818    cx.executor().start_waiting();
11819    let fake_server = fake_servers.next().await.unwrap();
11820
11821    {
11822        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11823            move |params, _| async move {
11824                assert_eq!(
11825                    params.text_document.uri,
11826                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11827                );
11828                assert_eq!(params.options.tab_size, 4);
11829                Ok(Some(vec![lsp::TextEdit::new(
11830                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11831                    ", ".to_string(),
11832                )]))
11833            },
11834        );
11835        let save = editor
11836            .update_in(cx, |editor, window, cx| {
11837                editor.save(
11838                    SaveOptions {
11839                        format: true,
11840                        autosave: false,
11841                    },
11842                    project.clone(),
11843                    window,
11844                    cx,
11845                )
11846            })
11847            .unwrap();
11848        cx.executor().start_waiting();
11849        save.await;
11850
11851        assert_eq!(
11852            editor.update(cx, |editor, cx| editor.text(cx)),
11853            "one, two\nthree\n"
11854        );
11855        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11856    }
11857
11858    {
11859        editor.update_in(cx, |editor, window, cx| {
11860            editor.set_text("one\ntwo\nthree\n", window, cx)
11861        });
11862        assert!(cx.read(|cx| editor.is_dirty(cx)));
11863
11864        // Ensure we can still save even if formatting hangs.
11865        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11866            move |params, _| async move {
11867                assert_eq!(
11868                    params.text_document.uri,
11869                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11870                );
11871                futures::future::pending::<()>().await;
11872                unreachable!()
11873            },
11874        );
11875        let save = editor
11876            .update_in(cx, |editor, window, cx| {
11877                editor.save(
11878                    SaveOptions {
11879                        format: true,
11880                        autosave: false,
11881                    },
11882                    project.clone(),
11883                    window,
11884                    cx,
11885                )
11886            })
11887            .unwrap();
11888        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11889        cx.executor().start_waiting();
11890        save.await;
11891        assert_eq!(
11892            editor.update(cx, |editor, cx| editor.text(cx)),
11893            "one\ntwo\nthree\n"
11894        );
11895    }
11896
11897    // Set rust language override and assert overridden tabsize is sent to language server
11898    update_test_language_settings(cx, |settings| {
11899        settings.languages.0.insert(
11900            "Rust".into(),
11901            LanguageSettingsContent {
11902                tab_size: NonZeroU32::new(8),
11903                ..Default::default()
11904            },
11905        );
11906    });
11907
11908    {
11909        editor.update_in(cx, |editor, window, cx| {
11910            editor.set_text("somehting_new\n", window, cx)
11911        });
11912        assert!(cx.read(|cx| editor.is_dirty(cx)));
11913        let _formatting_request_signal = fake_server
11914            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11915                assert_eq!(
11916                    params.text_document.uri,
11917                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11918                );
11919                assert_eq!(params.options.tab_size, 8);
11920                Ok(Some(vec![]))
11921            });
11922        let save = editor
11923            .update_in(cx, |editor, window, cx| {
11924                editor.save(
11925                    SaveOptions {
11926                        format: true,
11927                        autosave: false,
11928                    },
11929                    project.clone(),
11930                    window,
11931                    cx,
11932                )
11933            })
11934            .unwrap();
11935        cx.executor().start_waiting();
11936        save.await;
11937    }
11938}
11939
11940#[gpui::test]
11941async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11942    init_test(cx, |settings| {
11943        settings.defaults.ensure_final_newline_on_save = Some(false);
11944    });
11945
11946    let fs = FakeFs::new(cx.executor());
11947    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11948
11949    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11950
11951    let buffer = project
11952        .update(cx, |project, cx| {
11953            project.open_local_buffer(path!("/file.txt"), cx)
11954        })
11955        .await
11956        .unwrap();
11957
11958    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11959    let (editor, cx) = cx.add_window_view(|window, cx| {
11960        build_editor_with_project(project.clone(), buffer, window, cx)
11961    });
11962    editor.update_in(cx, |editor, window, cx| {
11963        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11964            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11965        });
11966    });
11967    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11968
11969    editor.update_in(cx, |editor, window, cx| {
11970        editor.handle_input("\n", window, cx)
11971    });
11972    cx.run_until_parked();
11973    save(&editor, &project, cx).await;
11974    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11975
11976    editor.update_in(cx, |editor, window, cx| {
11977        editor.undo(&Default::default(), window, cx);
11978    });
11979    save(&editor, &project, cx).await;
11980    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11981
11982    editor.update_in(cx, |editor, window, cx| {
11983        editor.redo(&Default::default(), window, cx);
11984    });
11985    cx.run_until_parked();
11986    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11987
11988    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11989        let save = editor
11990            .update_in(cx, |editor, window, cx| {
11991                editor.save(
11992                    SaveOptions {
11993                        format: true,
11994                        autosave: false,
11995                    },
11996                    project.clone(),
11997                    window,
11998                    cx,
11999                )
12000            })
12001            .unwrap();
12002        cx.executor().start_waiting();
12003        save.await;
12004        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12005    }
12006}
12007
12008#[gpui::test]
12009async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12010    init_test(cx, |_| {});
12011
12012    let cols = 4;
12013    let rows = 10;
12014    let sample_text_1 = sample_text(rows, cols, 'a');
12015    assert_eq!(
12016        sample_text_1,
12017        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12018    );
12019    let sample_text_2 = sample_text(rows, cols, 'l');
12020    assert_eq!(
12021        sample_text_2,
12022        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12023    );
12024    let sample_text_3 = sample_text(rows, cols, 'v');
12025    assert_eq!(
12026        sample_text_3,
12027        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12028    );
12029
12030    let fs = FakeFs::new(cx.executor());
12031    fs.insert_tree(
12032        path!("/a"),
12033        json!({
12034            "main.rs": sample_text_1,
12035            "other.rs": sample_text_2,
12036            "lib.rs": sample_text_3,
12037        }),
12038    )
12039    .await;
12040
12041    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12042    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12043    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12044
12045    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12046    language_registry.add(rust_lang());
12047    let mut fake_servers = language_registry.register_fake_lsp(
12048        "Rust",
12049        FakeLspAdapter {
12050            capabilities: lsp::ServerCapabilities {
12051                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12052                ..Default::default()
12053            },
12054            ..Default::default()
12055        },
12056    );
12057
12058    let worktree = project.update(cx, |project, cx| {
12059        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12060        assert_eq!(worktrees.len(), 1);
12061        worktrees.pop().unwrap()
12062    });
12063    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12064
12065    let buffer_1 = project
12066        .update(cx, |project, cx| {
12067            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12068        })
12069        .await
12070        .unwrap();
12071    let buffer_2 = project
12072        .update(cx, |project, cx| {
12073            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12074        })
12075        .await
12076        .unwrap();
12077    let buffer_3 = project
12078        .update(cx, |project, cx| {
12079            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12080        })
12081        .await
12082        .unwrap();
12083
12084    let multi_buffer = cx.new(|cx| {
12085        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12086        multi_buffer.push_excerpts(
12087            buffer_1.clone(),
12088            [
12089                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12090                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12091                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12092            ],
12093            cx,
12094        );
12095        multi_buffer.push_excerpts(
12096            buffer_2.clone(),
12097            [
12098                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12099                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12100                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12101            ],
12102            cx,
12103        );
12104        multi_buffer.push_excerpts(
12105            buffer_3.clone(),
12106            [
12107                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12108                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12109                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12110            ],
12111            cx,
12112        );
12113        multi_buffer
12114    });
12115    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12116        Editor::new(
12117            EditorMode::full(),
12118            multi_buffer,
12119            Some(project.clone()),
12120            window,
12121            cx,
12122        )
12123    });
12124
12125    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12126        editor.change_selections(
12127            SelectionEffects::scroll(Autoscroll::Next),
12128            window,
12129            cx,
12130            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12131        );
12132        editor.insert("|one|two|three|", window, cx);
12133    });
12134    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12135    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12136        editor.change_selections(
12137            SelectionEffects::scroll(Autoscroll::Next),
12138            window,
12139            cx,
12140            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12141        );
12142        editor.insert("|four|five|six|", window, cx);
12143    });
12144    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12145
12146    // First two buffers should be edited, but not the third one.
12147    assert_eq!(
12148        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12149        "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}",
12150    );
12151    buffer_1.update(cx, |buffer, _| {
12152        assert!(buffer.is_dirty());
12153        assert_eq!(
12154            buffer.text(),
12155            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12156        )
12157    });
12158    buffer_2.update(cx, |buffer, _| {
12159        assert!(buffer.is_dirty());
12160        assert_eq!(
12161            buffer.text(),
12162            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12163        )
12164    });
12165    buffer_3.update(cx, |buffer, _| {
12166        assert!(!buffer.is_dirty());
12167        assert_eq!(buffer.text(), sample_text_3,)
12168    });
12169    cx.executor().run_until_parked();
12170
12171    cx.executor().start_waiting();
12172    let save = multi_buffer_editor
12173        .update_in(cx, |editor, window, cx| {
12174            editor.save(
12175                SaveOptions {
12176                    format: true,
12177                    autosave: false,
12178                },
12179                project.clone(),
12180                window,
12181                cx,
12182            )
12183        })
12184        .unwrap();
12185
12186    let fake_server = fake_servers.next().await.unwrap();
12187    fake_server
12188        .server
12189        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12190            Ok(Some(vec![lsp::TextEdit::new(
12191                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12192                format!("[{} formatted]", params.text_document.uri),
12193            )]))
12194        })
12195        .detach();
12196    save.await;
12197
12198    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12199    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12200    assert_eq!(
12201        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12202        uri!(
12203            "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}"
12204        ),
12205    );
12206    buffer_1.update(cx, |buffer, _| {
12207        assert!(!buffer.is_dirty());
12208        assert_eq!(
12209            buffer.text(),
12210            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12211        )
12212    });
12213    buffer_2.update(cx, |buffer, _| {
12214        assert!(!buffer.is_dirty());
12215        assert_eq!(
12216            buffer.text(),
12217            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12218        )
12219    });
12220    buffer_3.update(cx, |buffer, _| {
12221        assert!(!buffer.is_dirty());
12222        assert_eq!(buffer.text(), sample_text_3,)
12223    });
12224}
12225
12226#[gpui::test]
12227async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12228    init_test(cx, |_| {});
12229
12230    let fs = FakeFs::new(cx.executor());
12231    fs.insert_tree(
12232        path!("/dir"),
12233        json!({
12234            "file1.rs": "fn main() { println!(\"hello\"); }",
12235            "file2.rs": "fn test() { println!(\"test\"); }",
12236            "file3.rs": "fn other() { println!(\"other\"); }\n",
12237        }),
12238    )
12239    .await;
12240
12241    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12242    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12243    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12244
12245    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12246    language_registry.add(rust_lang());
12247
12248    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12249    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12250
12251    // Open three buffers
12252    let buffer_1 = project
12253        .update(cx, |project, cx| {
12254            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12255        })
12256        .await
12257        .unwrap();
12258    let buffer_2 = project
12259        .update(cx, |project, cx| {
12260            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12261        })
12262        .await
12263        .unwrap();
12264    let buffer_3 = project
12265        .update(cx, |project, cx| {
12266            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12267        })
12268        .await
12269        .unwrap();
12270
12271    // Create a multi-buffer with all three buffers
12272    let multi_buffer = cx.new(|cx| {
12273        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12274        multi_buffer.push_excerpts(
12275            buffer_1.clone(),
12276            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12277            cx,
12278        );
12279        multi_buffer.push_excerpts(
12280            buffer_2.clone(),
12281            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12282            cx,
12283        );
12284        multi_buffer.push_excerpts(
12285            buffer_3.clone(),
12286            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12287            cx,
12288        );
12289        multi_buffer
12290    });
12291
12292    let editor = cx.new_window_entity(|window, cx| {
12293        Editor::new(
12294            EditorMode::full(),
12295            multi_buffer,
12296            Some(project.clone()),
12297            window,
12298            cx,
12299        )
12300    });
12301
12302    // Edit only the first buffer
12303    editor.update_in(cx, |editor, window, cx| {
12304        editor.change_selections(
12305            SelectionEffects::scroll(Autoscroll::Next),
12306            window,
12307            cx,
12308            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12309        );
12310        editor.insert("// edited", window, cx);
12311    });
12312
12313    // Verify that only buffer 1 is dirty
12314    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12315    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12316    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12317
12318    // Get write counts after file creation (files were created with initial content)
12319    // We expect each file to have been written once during creation
12320    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12321    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12322    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12323
12324    // Perform autosave
12325    let save_task = editor.update_in(cx, |editor, window, cx| {
12326        editor.save(
12327            SaveOptions {
12328                format: true,
12329                autosave: true,
12330            },
12331            project.clone(),
12332            window,
12333            cx,
12334        )
12335    });
12336    save_task.await.unwrap();
12337
12338    // Only the dirty buffer should have been saved
12339    assert_eq!(
12340        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12341        1,
12342        "Buffer 1 was dirty, so it should have been written once during autosave"
12343    );
12344    assert_eq!(
12345        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12346        0,
12347        "Buffer 2 was clean, so it should not have been written during autosave"
12348    );
12349    assert_eq!(
12350        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12351        0,
12352        "Buffer 3 was clean, so it should not have been written during autosave"
12353    );
12354
12355    // Verify buffer states after autosave
12356    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12357    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12358    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12359
12360    // Now perform a manual save (format = true)
12361    let save_task = editor.update_in(cx, |editor, window, cx| {
12362        editor.save(
12363            SaveOptions {
12364                format: true,
12365                autosave: false,
12366            },
12367            project.clone(),
12368            window,
12369            cx,
12370        )
12371    });
12372    save_task.await.unwrap();
12373
12374    // During manual save, clean buffers don't get written to disk
12375    // They just get did_save called for language server notifications
12376    assert_eq!(
12377        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12378        1,
12379        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12380    );
12381    assert_eq!(
12382        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12383        0,
12384        "Buffer 2 should not have been written at all"
12385    );
12386    assert_eq!(
12387        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12388        0,
12389        "Buffer 3 should not have been written at all"
12390    );
12391}
12392
12393async fn setup_range_format_test(
12394    cx: &mut TestAppContext,
12395) -> (
12396    Entity<Project>,
12397    Entity<Editor>,
12398    &mut gpui::VisualTestContext,
12399    lsp::FakeLanguageServer,
12400) {
12401    init_test(cx, |_| {});
12402
12403    let fs = FakeFs::new(cx.executor());
12404    fs.insert_file(path!("/file.rs"), Default::default()).await;
12405
12406    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12407
12408    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12409    language_registry.add(rust_lang());
12410    let mut fake_servers = language_registry.register_fake_lsp(
12411        "Rust",
12412        FakeLspAdapter {
12413            capabilities: lsp::ServerCapabilities {
12414                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12415                ..lsp::ServerCapabilities::default()
12416            },
12417            ..FakeLspAdapter::default()
12418        },
12419    );
12420
12421    let buffer = project
12422        .update(cx, |project, cx| {
12423            project.open_local_buffer(path!("/file.rs"), cx)
12424        })
12425        .await
12426        .unwrap();
12427
12428    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12429    let (editor, cx) = cx.add_window_view(|window, cx| {
12430        build_editor_with_project(project.clone(), buffer, window, cx)
12431    });
12432
12433    cx.executor().start_waiting();
12434    let fake_server = fake_servers.next().await.unwrap();
12435
12436    (project, editor, cx, fake_server)
12437}
12438
12439#[gpui::test]
12440async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12441    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12442
12443    editor.update_in(cx, |editor, window, cx| {
12444        editor.set_text("one\ntwo\nthree\n", window, cx)
12445    });
12446    assert!(cx.read(|cx| editor.is_dirty(cx)));
12447
12448    let save = editor
12449        .update_in(cx, |editor, window, cx| {
12450            editor.save(
12451                SaveOptions {
12452                    format: true,
12453                    autosave: false,
12454                },
12455                project.clone(),
12456                window,
12457                cx,
12458            )
12459        })
12460        .unwrap();
12461    fake_server
12462        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12463            assert_eq!(
12464                params.text_document.uri,
12465                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12466            );
12467            assert_eq!(params.options.tab_size, 4);
12468            Ok(Some(vec![lsp::TextEdit::new(
12469                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12470                ", ".to_string(),
12471            )]))
12472        })
12473        .next()
12474        .await;
12475    cx.executor().start_waiting();
12476    save.await;
12477    assert_eq!(
12478        editor.update(cx, |editor, cx| editor.text(cx)),
12479        "one, two\nthree\n"
12480    );
12481    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12482}
12483
12484#[gpui::test]
12485async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12486    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12487
12488    editor.update_in(cx, |editor, window, cx| {
12489        editor.set_text("one\ntwo\nthree\n", window, cx)
12490    });
12491    assert!(cx.read(|cx| editor.is_dirty(cx)));
12492
12493    // Test that save still works when formatting hangs
12494    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12495        move |params, _| async move {
12496            assert_eq!(
12497                params.text_document.uri,
12498                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12499            );
12500            futures::future::pending::<()>().await;
12501            unreachable!()
12502        },
12503    );
12504    let save = editor
12505        .update_in(cx, |editor, window, cx| {
12506            editor.save(
12507                SaveOptions {
12508                    format: true,
12509                    autosave: false,
12510                },
12511                project.clone(),
12512                window,
12513                cx,
12514            )
12515        })
12516        .unwrap();
12517    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12518    cx.executor().start_waiting();
12519    save.await;
12520    assert_eq!(
12521        editor.update(cx, |editor, cx| editor.text(cx)),
12522        "one\ntwo\nthree\n"
12523    );
12524    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12525}
12526
12527#[gpui::test]
12528async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12529    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12530
12531    // Buffer starts clean, no formatting should be requested
12532    let save = editor
12533        .update_in(cx, |editor, window, cx| {
12534            editor.save(
12535                SaveOptions {
12536                    format: false,
12537                    autosave: false,
12538                },
12539                project.clone(),
12540                window,
12541                cx,
12542            )
12543        })
12544        .unwrap();
12545    let _pending_format_request = fake_server
12546        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12547            panic!("Should not be invoked");
12548        })
12549        .next();
12550    cx.executor().start_waiting();
12551    save.await;
12552    cx.run_until_parked();
12553}
12554
12555#[gpui::test]
12556async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12557    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12558
12559    // Set Rust language override and assert overridden tabsize is sent to language server
12560    update_test_language_settings(cx, |settings| {
12561        settings.languages.0.insert(
12562            "Rust".into(),
12563            LanguageSettingsContent {
12564                tab_size: NonZeroU32::new(8),
12565                ..Default::default()
12566            },
12567        );
12568    });
12569
12570    editor.update_in(cx, |editor, window, cx| {
12571        editor.set_text("something_new\n", window, cx)
12572    });
12573    assert!(cx.read(|cx| editor.is_dirty(cx)));
12574    let save = editor
12575        .update_in(cx, |editor, window, cx| {
12576            editor.save(
12577                SaveOptions {
12578                    format: true,
12579                    autosave: false,
12580                },
12581                project.clone(),
12582                window,
12583                cx,
12584            )
12585        })
12586        .unwrap();
12587    fake_server
12588        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12589            assert_eq!(
12590                params.text_document.uri,
12591                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12592            );
12593            assert_eq!(params.options.tab_size, 8);
12594            Ok(Some(Vec::new()))
12595        })
12596        .next()
12597        .await;
12598    save.await;
12599}
12600
12601#[gpui::test]
12602async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12603    init_test(cx, |settings| {
12604        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12605            settings::LanguageServerFormatterSpecifier::Current,
12606        )))
12607    });
12608
12609    let fs = FakeFs::new(cx.executor());
12610    fs.insert_file(path!("/file.rs"), Default::default()).await;
12611
12612    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12613
12614    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12615    language_registry.add(Arc::new(Language::new(
12616        LanguageConfig {
12617            name: "Rust".into(),
12618            matcher: LanguageMatcher {
12619                path_suffixes: vec!["rs".to_string()],
12620                ..Default::default()
12621            },
12622            ..LanguageConfig::default()
12623        },
12624        Some(tree_sitter_rust::LANGUAGE.into()),
12625    )));
12626    update_test_language_settings(cx, |settings| {
12627        // Enable Prettier formatting for the same buffer, and ensure
12628        // LSP is called instead of Prettier.
12629        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12630    });
12631    let mut fake_servers = language_registry.register_fake_lsp(
12632        "Rust",
12633        FakeLspAdapter {
12634            capabilities: lsp::ServerCapabilities {
12635                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12636                ..Default::default()
12637            },
12638            ..Default::default()
12639        },
12640    );
12641
12642    let buffer = project
12643        .update(cx, |project, cx| {
12644            project.open_local_buffer(path!("/file.rs"), cx)
12645        })
12646        .await
12647        .unwrap();
12648
12649    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12650    let (editor, cx) = cx.add_window_view(|window, cx| {
12651        build_editor_with_project(project.clone(), buffer, window, cx)
12652    });
12653    editor.update_in(cx, |editor, window, cx| {
12654        editor.set_text("one\ntwo\nthree\n", window, cx)
12655    });
12656
12657    cx.executor().start_waiting();
12658    let fake_server = fake_servers.next().await.unwrap();
12659
12660    let format = editor
12661        .update_in(cx, |editor, window, cx| {
12662            editor.perform_format(
12663                project.clone(),
12664                FormatTrigger::Manual,
12665                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12666                window,
12667                cx,
12668            )
12669        })
12670        .unwrap();
12671    fake_server
12672        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12673            assert_eq!(
12674                params.text_document.uri,
12675                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12676            );
12677            assert_eq!(params.options.tab_size, 4);
12678            Ok(Some(vec![lsp::TextEdit::new(
12679                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12680                ", ".to_string(),
12681            )]))
12682        })
12683        .next()
12684        .await;
12685    cx.executor().start_waiting();
12686    format.await;
12687    assert_eq!(
12688        editor.update(cx, |editor, cx| editor.text(cx)),
12689        "one, two\nthree\n"
12690    );
12691
12692    editor.update_in(cx, |editor, window, cx| {
12693        editor.set_text("one\ntwo\nthree\n", window, cx)
12694    });
12695    // Ensure we don't lock if formatting hangs.
12696    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12697        move |params, _| async move {
12698            assert_eq!(
12699                params.text_document.uri,
12700                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12701            );
12702            futures::future::pending::<()>().await;
12703            unreachable!()
12704        },
12705    );
12706    let format = editor
12707        .update_in(cx, |editor, window, cx| {
12708            editor.perform_format(
12709                project,
12710                FormatTrigger::Manual,
12711                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12712                window,
12713                cx,
12714            )
12715        })
12716        .unwrap();
12717    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12718    cx.executor().start_waiting();
12719    format.await;
12720    assert_eq!(
12721        editor.update(cx, |editor, cx| editor.text(cx)),
12722        "one\ntwo\nthree\n"
12723    );
12724}
12725
12726#[gpui::test]
12727async fn test_multiple_formatters(cx: &mut TestAppContext) {
12728    init_test(cx, |settings| {
12729        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12730        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12731            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12732            Formatter::CodeAction("code-action-1".into()),
12733            Formatter::CodeAction("code-action-2".into()),
12734        ]))
12735    });
12736
12737    let fs = FakeFs::new(cx.executor());
12738    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12739        .await;
12740
12741    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12742    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12743    language_registry.add(rust_lang());
12744
12745    let mut fake_servers = language_registry.register_fake_lsp(
12746        "Rust",
12747        FakeLspAdapter {
12748            capabilities: lsp::ServerCapabilities {
12749                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12750                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12751                    commands: vec!["the-command-for-code-action-1".into()],
12752                    ..Default::default()
12753                }),
12754                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12755                ..Default::default()
12756            },
12757            ..Default::default()
12758        },
12759    );
12760
12761    let buffer = project
12762        .update(cx, |project, cx| {
12763            project.open_local_buffer(path!("/file.rs"), cx)
12764        })
12765        .await
12766        .unwrap();
12767
12768    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12769    let (editor, cx) = cx.add_window_view(|window, cx| {
12770        build_editor_with_project(project.clone(), buffer, window, cx)
12771    });
12772
12773    cx.executor().start_waiting();
12774
12775    let fake_server = fake_servers.next().await.unwrap();
12776    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12777        move |_params, _| async move {
12778            Ok(Some(vec![lsp::TextEdit::new(
12779                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12780                "applied-formatting\n".to_string(),
12781            )]))
12782        },
12783    );
12784    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12785        move |params, _| async move {
12786            let requested_code_actions = params.context.only.expect("Expected code action request");
12787            assert_eq!(requested_code_actions.len(), 1);
12788
12789            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12790            let code_action = match requested_code_actions[0].as_str() {
12791                "code-action-1" => lsp::CodeAction {
12792                    kind: Some("code-action-1".into()),
12793                    edit: Some(lsp::WorkspaceEdit::new(
12794                        [(
12795                            uri,
12796                            vec![lsp::TextEdit::new(
12797                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12798                                "applied-code-action-1-edit\n".to_string(),
12799                            )],
12800                        )]
12801                        .into_iter()
12802                        .collect(),
12803                    )),
12804                    command: Some(lsp::Command {
12805                        command: "the-command-for-code-action-1".into(),
12806                        ..Default::default()
12807                    }),
12808                    ..Default::default()
12809                },
12810                "code-action-2" => lsp::CodeAction {
12811                    kind: Some("code-action-2".into()),
12812                    edit: Some(lsp::WorkspaceEdit::new(
12813                        [(
12814                            uri,
12815                            vec![lsp::TextEdit::new(
12816                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12817                                "applied-code-action-2-edit\n".to_string(),
12818                            )],
12819                        )]
12820                        .into_iter()
12821                        .collect(),
12822                    )),
12823                    ..Default::default()
12824                },
12825                req => panic!("Unexpected code action request: {:?}", req),
12826            };
12827            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12828                code_action,
12829            )]))
12830        },
12831    );
12832
12833    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12834        move |params, _| async move { Ok(params) }
12835    });
12836
12837    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12838    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12839        let fake = fake_server.clone();
12840        let lock = command_lock.clone();
12841        move |params, _| {
12842            assert_eq!(params.command, "the-command-for-code-action-1");
12843            let fake = fake.clone();
12844            let lock = lock.clone();
12845            async move {
12846                lock.lock().await;
12847                fake.server
12848                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12849                        label: None,
12850                        edit: lsp::WorkspaceEdit {
12851                            changes: Some(
12852                                [(
12853                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12854                                    vec![lsp::TextEdit {
12855                                        range: lsp::Range::new(
12856                                            lsp::Position::new(0, 0),
12857                                            lsp::Position::new(0, 0),
12858                                        ),
12859                                        new_text: "applied-code-action-1-command\n".into(),
12860                                    }],
12861                                )]
12862                                .into_iter()
12863                                .collect(),
12864                            ),
12865                            ..Default::default()
12866                        },
12867                    })
12868                    .await
12869                    .into_response()
12870                    .unwrap();
12871                Ok(Some(json!(null)))
12872            }
12873        }
12874    });
12875
12876    cx.executor().start_waiting();
12877    editor
12878        .update_in(cx, |editor, window, cx| {
12879            editor.perform_format(
12880                project.clone(),
12881                FormatTrigger::Manual,
12882                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12883                window,
12884                cx,
12885            )
12886        })
12887        .unwrap()
12888        .await;
12889    editor.update(cx, |editor, cx| {
12890        assert_eq!(
12891            editor.text(cx),
12892            r#"
12893                applied-code-action-2-edit
12894                applied-code-action-1-command
12895                applied-code-action-1-edit
12896                applied-formatting
12897                one
12898                two
12899                three
12900            "#
12901            .unindent()
12902        );
12903    });
12904
12905    editor.update_in(cx, |editor, window, cx| {
12906        editor.undo(&Default::default(), window, cx);
12907        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12908    });
12909
12910    // Perform a manual edit while waiting for an LSP command
12911    // that's being run as part of a formatting code action.
12912    let lock_guard = command_lock.lock().await;
12913    let format = editor
12914        .update_in(cx, |editor, window, cx| {
12915            editor.perform_format(
12916                project.clone(),
12917                FormatTrigger::Manual,
12918                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12919                window,
12920                cx,
12921            )
12922        })
12923        .unwrap();
12924    cx.run_until_parked();
12925    editor.update(cx, |editor, cx| {
12926        assert_eq!(
12927            editor.text(cx),
12928            r#"
12929                applied-code-action-1-edit
12930                applied-formatting
12931                one
12932                two
12933                three
12934            "#
12935            .unindent()
12936        );
12937
12938        editor.buffer.update(cx, |buffer, cx| {
12939            let ix = buffer.len(cx);
12940            buffer.edit([(ix..ix, "edited\n")], None, cx);
12941        });
12942    });
12943
12944    // Allow the LSP command to proceed. Because the buffer was edited,
12945    // the second code action will not be run.
12946    drop(lock_guard);
12947    format.await;
12948    editor.update_in(cx, |editor, window, cx| {
12949        assert_eq!(
12950            editor.text(cx),
12951            r#"
12952                applied-code-action-1-command
12953                applied-code-action-1-edit
12954                applied-formatting
12955                one
12956                two
12957                three
12958                edited
12959            "#
12960            .unindent()
12961        );
12962
12963        // The manual edit is undone first, because it is the last thing the user did
12964        // (even though the command completed afterwards).
12965        editor.undo(&Default::default(), window, cx);
12966        assert_eq!(
12967            editor.text(cx),
12968            r#"
12969                applied-code-action-1-command
12970                applied-code-action-1-edit
12971                applied-formatting
12972                one
12973                two
12974                three
12975            "#
12976            .unindent()
12977        );
12978
12979        // All the formatting (including the command, which completed after the manual edit)
12980        // is undone together.
12981        editor.undo(&Default::default(), window, cx);
12982        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12983    });
12984}
12985
12986#[gpui::test]
12987async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12988    init_test(cx, |settings| {
12989        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12990            settings::LanguageServerFormatterSpecifier::Current,
12991        )]))
12992    });
12993
12994    let fs = FakeFs::new(cx.executor());
12995    fs.insert_file(path!("/file.ts"), Default::default()).await;
12996
12997    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12998
12999    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13000    language_registry.add(Arc::new(Language::new(
13001        LanguageConfig {
13002            name: "TypeScript".into(),
13003            matcher: LanguageMatcher {
13004                path_suffixes: vec!["ts".to_string()],
13005                ..Default::default()
13006            },
13007            ..LanguageConfig::default()
13008        },
13009        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13010    )));
13011    update_test_language_settings(cx, |settings| {
13012        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13013    });
13014    let mut fake_servers = language_registry.register_fake_lsp(
13015        "TypeScript",
13016        FakeLspAdapter {
13017            capabilities: lsp::ServerCapabilities {
13018                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13019                ..Default::default()
13020            },
13021            ..Default::default()
13022        },
13023    );
13024
13025    let buffer = project
13026        .update(cx, |project, cx| {
13027            project.open_local_buffer(path!("/file.ts"), cx)
13028        })
13029        .await
13030        .unwrap();
13031
13032    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13033    let (editor, cx) = cx.add_window_view(|window, cx| {
13034        build_editor_with_project(project.clone(), buffer, window, cx)
13035    });
13036    editor.update_in(cx, |editor, window, cx| {
13037        editor.set_text(
13038            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13039            window,
13040            cx,
13041        )
13042    });
13043
13044    cx.executor().start_waiting();
13045    let fake_server = fake_servers.next().await.unwrap();
13046
13047    let format = editor
13048        .update_in(cx, |editor, window, cx| {
13049            editor.perform_code_action_kind(
13050                project.clone(),
13051                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13052                window,
13053                cx,
13054            )
13055        })
13056        .unwrap();
13057    fake_server
13058        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13059            assert_eq!(
13060                params.text_document.uri,
13061                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13062            );
13063            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13064                lsp::CodeAction {
13065                    title: "Organize Imports".to_string(),
13066                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13067                    edit: Some(lsp::WorkspaceEdit {
13068                        changes: Some(
13069                            [(
13070                                params.text_document.uri.clone(),
13071                                vec![lsp::TextEdit::new(
13072                                    lsp::Range::new(
13073                                        lsp::Position::new(1, 0),
13074                                        lsp::Position::new(2, 0),
13075                                    ),
13076                                    "".to_string(),
13077                                )],
13078                            )]
13079                            .into_iter()
13080                            .collect(),
13081                        ),
13082                        ..Default::default()
13083                    }),
13084                    ..Default::default()
13085                },
13086            )]))
13087        })
13088        .next()
13089        .await;
13090    cx.executor().start_waiting();
13091    format.await;
13092    assert_eq!(
13093        editor.update(cx, |editor, cx| editor.text(cx)),
13094        "import { a } from 'module';\n\nconst x = a;\n"
13095    );
13096
13097    editor.update_in(cx, |editor, window, cx| {
13098        editor.set_text(
13099            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13100            window,
13101            cx,
13102        )
13103    });
13104    // Ensure we don't lock if code action hangs.
13105    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13106        move |params, _| async move {
13107            assert_eq!(
13108                params.text_document.uri,
13109                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13110            );
13111            futures::future::pending::<()>().await;
13112            unreachable!()
13113        },
13114    );
13115    let format = editor
13116        .update_in(cx, |editor, window, cx| {
13117            editor.perform_code_action_kind(
13118                project,
13119                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13120                window,
13121                cx,
13122            )
13123        })
13124        .unwrap();
13125    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13126    cx.executor().start_waiting();
13127    format.await;
13128    assert_eq!(
13129        editor.update(cx, |editor, cx| editor.text(cx)),
13130        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13131    );
13132}
13133
13134#[gpui::test]
13135async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13136    init_test(cx, |_| {});
13137
13138    let mut cx = EditorLspTestContext::new_rust(
13139        lsp::ServerCapabilities {
13140            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13141            ..Default::default()
13142        },
13143        cx,
13144    )
13145    .await;
13146
13147    cx.set_state(indoc! {"
13148        one.twoˇ
13149    "});
13150
13151    // The format request takes a long time. When it completes, it inserts
13152    // a newline and an indent before the `.`
13153    cx.lsp
13154        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13155            let executor = cx.background_executor().clone();
13156            async move {
13157                executor.timer(Duration::from_millis(100)).await;
13158                Ok(Some(vec![lsp::TextEdit {
13159                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13160                    new_text: "\n    ".into(),
13161                }]))
13162            }
13163        });
13164
13165    // Submit a format request.
13166    let format_1 = cx
13167        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13168        .unwrap();
13169    cx.executor().run_until_parked();
13170
13171    // Submit a second format request.
13172    let format_2 = cx
13173        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13174        .unwrap();
13175    cx.executor().run_until_parked();
13176
13177    // Wait for both format requests to complete
13178    cx.executor().advance_clock(Duration::from_millis(200));
13179    cx.executor().start_waiting();
13180    format_1.await.unwrap();
13181    cx.executor().start_waiting();
13182    format_2.await.unwrap();
13183
13184    // The formatting edits only happens once.
13185    cx.assert_editor_state(indoc! {"
13186        one
13187            .twoˇ
13188    "});
13189}
13190
13191#[gpui::test]
13192async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13193    init_test(cx, |settings| {
13194        settings.defaults.formatter = Some(FormatterList::default())
13195    });
13196
13197    let mut cx = EditorLspTestContext::new_rust(
13198        lsp::ServerCapabilities {
13199            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13200            ..Default::default()
13201        },
13202        cx,
13203    )
13204    .await;
13205
13206    // Record which buffer changes have been sent to the language server
13207    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13208    cx.lsp
13209        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13210            let buffer_changes = buffer_changes.clone();
13211            move |params, _| {
13212                buffer_changes.lock().extend(
13213                    params
13214                        .content_changes
13215                        .into_iter()
13216                        .map(|e| (e.range.unwrap(), e.text)),
13217                );
13218            }
13219        });
13220    // Handle formatting requests to the language server.
13221    cx.lsp
13222        .set_request_handler::<lsp::request::Formatting, _, _>({
13223            move |_, _| {
13224                // Insert blank lines between each line of the buffer.
13225                async move {
13226                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13227                    // DidChangedTextDocument to the LSP before sending the formatting request.
13228                    // assert_eq!(
13229                    //     &buffer_changes.lock()[1..],
13230                    //     &[
13231                    //         (
13232                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13233                    //             "".into()
13234                    //         ),
13235                    //         (
13236                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13237                    //             "".into()
13238                    //         ),
13239                    //         (
13240                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13241                    //             "\n".into()
13242                    //         ),
13243                    //     ]
13244                    // );
13245
13246                    Ok(Some(vec![
13247                        lsp::TextEdit {
13248                            range: lsp::Range::new(
13249                                lsp::Position::new(1, 0),
13250                                lsp::Position::new(1, 0),
13251                            ),
13252                            new_text: "\n".into(),
13253                        },
13254                        lsp::TextEdit {
13255                            range: lsp::Range::new(
13256                                lsp::Position::new(2, 0),
13257                                lsp::Position::new(2, 0),
13258                            ),
13259                            new_text: "\n".into(),
13260                        },
13261                    ]))
13262                }
13263            }
13264        });
13265
13266    // Set up a buffer white some trailing whitespace and no trailing newline.
13267    cx.set_state(
13268        &[
13269            "one ",   //
13270            "twoˇ",   //
13271            "three ", //
13272            "four",   //
13273        ]
13274        .join("\n"),
13275    );
13276
13277    // Submit a format request.
13278    let format = cx
13279        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13280        .unwrap();
13281
13282    cx.run_until_parked();
13283    // After formatting the buffer, the trailing whitespace is stripped,
13284    // a newline is appended, and the edits provided by the language server
13285    // have been applied.
13286    format.await.unwrap();
13287
13288    cx.assert_editor_state(
13289        &[
13290            "one",   //
13291            "",      //
13292            "twoˇ",  //
13293            "",      //
13294            "three", //
13295            "four",  //
13296            "",      //
13297        ]
13298        .join("\n"),
13299    );
13300
13301    // Undoing the formatting undoes the trailing whitespace removal, the
13302    // trailing newline, and the LSP edits.
13303    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13304    cx.assert_editor_state(
13305        &[
13306            "one ",   //
13307            "twoˇ",   //
13308            "three ", //
13309            "four",   //
13310        ]
13311        .join("\n"),
13312    );
13313}
13314
13315#[gpui::test]
13316async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13317    cx: &mut TestAppContext,
13318) {
13319    init_test(cx, |_| {});
13320
13321    cx.update(|cx| {
13322        cx.update_global::<SettingsStore, _>(|settings, cx| {
13323            settings.update_user_settings(cx, |settings| {
13324                settings.editor.auto_signature_help = Some(true);
13325            });
13326        });
13327    });
13328
13329    let mut cx = EditorLspTestContext::new_rust(
13330        lsp::ServerCapabilities {
13331            signature_help_provider: Some(lsp::SignatureHelpOptions {
13332                ..Default::default()
13333            }),
13334            ..Default::default()
13335        },
13336        cx,
13337    )
13338    .await;
13339
13340    let language = Language::new(
13341        LanguageConfig {
13342            name: "Rust".into(),
13343            brackets: BracketPairConfig {
13344                pairs: vec![
13345                    BracketPair {
13346                        start: "{".to_string(),
13347                        end: "}".to_string(),
13348                        close: true,
13349                        surround: true,
13350                        newline: true,
13351                    },
13352                    BracketPair {
13353                        start: "(".to_string(),
13354                        end: ")".to_string(),
13355                        close: true,
13356                        surround: true,
13357                        newline: true,
13358                    },
13359                    BracketPair {
13360                        start: "/*".to_string(),
13361                        end: " */".to_string(),
13362                        close: true,
13363                        surround: true,
13364                        newline: true,
13365                    },
13366                    BracketPair {
13367                        start: "[".to_string(),
13368                        end: "]".to_string(),
13369                        close: false,
13370                        surround: false,
13371                        newline: true,
13372                    },
13373                    BracketPair {
13374                        start: "\"".to_string(),
13375                        end: "\"".to_string(),
13376                        close: true,
13377                        surround: true,
13378                        newline: false,
13379                    },
13380                    BracketPair {
13381                        start: "<".to_string(),
13382                        end: ">".to_string(),
13383                        close: false,
13384                        surround: true,
13385                        newline: true,
13386                    },
13387                ],
13388                ..Default::default()
13389            },
13390            autoclose_before: "})]".to_string(),
13391            ..Default::default()
13392        },
13393        Some(tree_sitter_rust::LANGUAGE.into()),
13394    );
13395    let language = Arc::new(language);
13396
13397    cx.language_registry().add(language.clone());
13398    cx.update_buffer(|buffer, cx| {
13399        buffer.set_language(Some(language), cx);
13400    });
13401
13402    cx.set_state(
13403        &r#"
13404            fn main() {
13405                sampleˇ
13406            }
13407        "#
13408        .unindent(),
13409    );
13410
13411    cx.update_editor(|editor, window, cx| {
13412        editor.handle_input("(", window, cx);
13413    });
13414    cx.assert_editor_state(
13415        &"
13416            fn main() {
13417                sample(ˇ)
13418            }
13419        "
13420        .unindent(),
13421    );
13422
13423    let mocked_response = lsp::SignatureHelp {
13424        signatures: vec![lsp::SignatureInformation {
13425            label: "fn sample(param1: u8, param2: u8)".to_string(),
13426            documentation: None,
13427            parameters: Some(vec![
13428                lsp::ParameterInformation {
13429                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13430                    documentation: None,
13431                },
13432                lsp::ParameterInformation {
13433                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13434                    documentation: None,
13435                },
13436            ]),
13437            active_parameter: None,
13438        }],
13439        active_signature: Some(0),
13440        active_parameter: Some(0),
13441    };
13442    handle_signature_help_request(&mut cx, mocked_response).await;
13443
13444    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13445        .await;
13446
13447    cx.editor(|editor, _, _| {
13448        let signature_help_state = editor.signature_help_state.popover().cloned();
13449        let signature = signature_help_state.unwrap();
13450        assert_eq!(
13451            signature.signatures[signature.current_signature].label,
13452            "fn sample(param1: u8, param2: u8)"
13453        );
13454    });
13455}
13456
13457#[gpui::test]
13458async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13459    init_test(cx, |_| {});
13460
13461    cx.update(|cx| {
13462        cx.update_global::<SettingsStore, _>(|settings, cx| {
13463            settings.update_user_settings(cx, |settings| {
13464                settings.editor.auto_signature_help = Some(false);
13465                settings.editor.show_signature_help_after_edits = Some(false);
13466            });
13467        });
13468    });
13469
13470    let mut cx = EditorLspTestContext::new_rust(
13471        lsp::ServerCapabilities {
13472            signature_help_provider: Some(lsp::SignatureHelpOptions {
13473                ..Default::default()
13474            }),
13475            ..Default::default()
13476        },
13477        cx,
13478    )
13479    .await;
13480
13481    let language = Language::new(
13482        LanguageConfig {
13483            name: "Rust".into(),
13484            brackets: BracketPairConfig {
13485                pairs: vec![
13486                    BracketPair {
13487                        start: "{".to_string(),
13488                        end: "}".to_string(),
13489                        close: true,
13490                        surround: true,
13491                        newline: true,
13492                    },
13493                    BracketPair {
13494                        start: "(".to_string(),
13495                        end: ")".to_string(),
13496                        close: true,
13497                        surround: true,
13498                        newline: true,
13499                    },
13500                    BracketPair {
13501                        start: "/*".to_string(),
13502                        end: " */".to_string(),
13503                        close: true,
13504                        surround: true,
13505                        newline: true,
13506                    },
13507                    BracketPair {
13508                        start: "[".to_string(),
13509                        end: "]".to_string(),
13510                        close: false,
13511                        surround: false,
13512                        newline: true,
13513                    },
13514                    BracketPair {
13515                        start: "\"".to_string(),
13516                        end: "\"".to_string(),
13517                        close: true,
13518                        surround: true,
13519                        newline: false,
13520                    },
13521                    BracketPair {
13522                        start: "<".to_string(),
13523                        end: ">".to_string(),
13524                        close: false,
13525                        surround: true,
13526                        newline: true,
13527                    },
13528                ],
13529                ..Default::default()
13530            },
13531            autoclose_before: "})]".to_string(),
13532            ..Default::default()
13533        },
13534        Some(tree_sitter_rust::LANGUAGE.into()),
13535    );
13536    let language = Arc::new(language);
13537
13538    cx.language_registry().add(language.clone());
13539    cx.update_buffer(|buffer, cx| {
13540        buffer.set_language(Some(language), cx);
13541    });
13542
13543    // Ensure that signature_help is not called when no signature help is enabled.
13544    cx.set_state(
13545        &r#"
13546            fn main() {
13547                sampleˇ
13548            }
13549        "#
13550        .unindent(),
13551    );
13552    cx.update_editor(|editor, window, cx| {
13553        editor.handle_input("(", window, cx);
13554    });
13555    cx.assert_editor_state(
13556        &"
13557            fn main() {
13558                sample(ˇ)
13559            }
13560        "
13561        .unindent(),
13562    );
13563    cx.editor(|editor, _, _| {
13564        assert!(editor.signature_help_state.task().is_none());
13565    });
13566
13567    let mocked_response = lsp::SignatureHelp {
13568        signatures: vec![lsp::SignatureInformation {
13569            label: "fn sample(param1: u8, param2: u8)".to_string(),
13570            documentation: None,
13571            parameters: Some(vec![
13572                lsp::ParameterInformation {
13573                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13574                    documentation: None,
13575                },
13576                lsp::ParameterInformation {
13577                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13578                    documentation: None,
13579                },
13580            ]),
13581            active_parameter: None,
13582        }],
13583        active_signature: Some(0),
13584        active_parameter: Some(0),
13585    };
13586
13587    // Ensure that signature_help is called when enabled afte edits
13588    cx.update(|_, cx| {
13589        cx.update_global::<SettingsStore, _>(|settings, cx| {
13590            settings.update_user_settings(cx, |settings| {
13591                settings.editor.auto_signature_help = Some(false);
13592                settings.editor.show_signature_help_after_edits = Some(true);
13593            });
13594        });
13595    });
13596    cx.set_state(
13597        &r#"
13598            fn main() {
13599                sampleˇ
13600            }
13601        "#
13602        .unindent(),
13603    );
13604    cx.update_editor(|editor, window, cx| {
13605        editor.handle_input("(", window, cx);
13606    });
13607    cx.assert_editor_state(
13608        &"
13609            fn main() {
13610                sample(ˇ)
13611            }
13612        "
13613        .unindent(),
13614    );
13615    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13616    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13617        .await;
13618    cx.update_editor(|editor, _, _| {
13619        let signature_help_state = editor.signature_help_state.popover().cloned();
13620        assert!(signature_help_state.is_some());
13621        let signature = signature_help_state.unwrap();
13622        assert_eq!(
13623            signature.signatures[signature.current_signature].label,
13624            "fn sample(param1: u8, param2: u8)"
13625        );
13626        editor.signature_help_state = SignatureHelpState::default();
13627    });
13628
13629    // Ensure that signature_help is called when auto signature help override is enabled
13630    cx.update(|_, cx| {
13631        cx.update_global::<SettingsStore, _>(|settings, cx| {
13632            settings.update_user_settings(cx, |settings| {
13633                settings.editor.auto_signature_help = Some(true);
13634                settings.editor.show_signature_help_after_edits = Some(false);
13635            });
13636        });
13637    });
13638    cx.set_state(
13639        &r#"
13640            fn main() {
13641                sampleˇ
13642            }
13643        "#
13644        .unindent(),
13645    );
13646    cx.update_editor(|editor, window, cx| {
13647        editor.handle_input("(", window, cx);
13648    });
13649    cx.assert_editor_state(
13650        &"
13651            fn main() {
13652                sample(ˇ)
13653            }
13654        "
13655        .unindent(),
13656    );
13657    handle_signature_help_request(&mut cx, mocked_response).await;
13658    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13659        .await;
13660    cx.editor(|editor, _, _| {
13661        let signature_help_state = editor.signature_help_state.popover().cloned();
13662        assert!(signature_help_state.is_some());
13663        let signature = signature_help_state.unwrap();
13664        assert_eq!(
13665            signature.signatures[signature.current_signature].label,
13666            "fn sample(param1: u8, param2: u8)"
13667        );
13668    });
13669}
13670
13671#[gpui::test]
13672async fn test_signature_help(cx: &mut TestAppContext) {
13673    init_test(cx, |_| {});
13674    cx.update(|cx| {
13675        cx.update_global::<SettingsStore, _>(|settings, cx| {
13676            settings.update_user_settings(cx, |settings| {
13677                settings.editor.auto_signature_help = Some(true);
13678            });
13679        });
13680    });
13681
13682    let mut cx = EditorLspTestContext::new_rust(
13683        lsp::ServerCapabilities {
13684            signature_help_provider: Some(lsp::SignatureHelpOptions {
13685                ..Default::default()
13686            }),
13687            ..Default::default()
13688        },
13689        cx,
13690    )
13691    .await;
13692
13693    // A test that directly calls `show_signature_help`
13694    cx.update_editor(|editor, window, cx| {
13695        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13696    });
13697
13698    let mocked_response = lsp::SignatureHelp {
13699        signatures: vec![lsp::SignatureInformation {
13700            label: "fn sample(param1: u8, param2: u8)".to_string(),
13701            documentation: None,
13702            parameters: Some(vec![
13703                lsp::ParameterInformation {
13704                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13705                    documentation: None,
13706                },
13707                lsp::ParameterInformation {
13708                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13709                    documentation: None,
13710                },
13711            ]),
13712            active_parameter: None,
13713        }],
13714        active_signature: Some(0),
13715        active_parameter: Some(0),
13716    };
13717    handle_signature_help_request(&mut cx, mocked_response).await;
13718
13719    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13720        .await;
13721
13722    cx.editor(|editor, _, _| {
13723        let signature_help_state = editor.signature_help_state.popover().cloned();
13724        assert!(signature_help_state.is_some());
13725        let signature = signature_help_state.unwrap();
13726        assert_eq!(
13727            signature.signatures[signature.current_signature].label,
13728            "fn sample(param1: u8, param2: u8)"
13729        );
13730    });
13731
13732    // When exiting outside from inside the brackets, `signature_help` is closed.
13733    cx.set_state(indoc! {"
13734        fn main() {
13735            sample(ˇ);
13736        }
13737
13738        fn sample(param1: u8, param2: u8) {}
13739    "});
13740
13741    cx.update_editor(|editor, window, cx| {
13742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13743            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13744        });
13745    });
13746
13747    let mocked_response = lsp::SignatureHelp {
13748        signatures: Vec::new(),
13749        active_signature: None,
13750        active_parameter: None,
13751    };
13752    handle_signature_help_request(&mut cx, mocked_response).await;
13753
13754    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13755        .await;
13756
13757    cx.editor(|editor, _, _| {
13758        assert!(!editor.signature_help_state.is_shown());
13759    });
13760
13761    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13762    cx.set_state(indoc! {"
13763        fn main() {
13764            sample(ˇ);
13765        }
13766
13767        fn sample(param1: u8, param2: u8) {}
13768    "});
13769
13770    let mocked_response = lsp::SignatureHelp {
13771        signatures: vec![lsp::SignatureInformation {
13772            label: "fn sample(param1: u8, param2: u8)".to_string(),
13773            documentation: None,
13774            parameters: Some(vec![
13775                lsp::ParameterInformation {
13776                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13777                    documentation: None,
13778                },
13779                lsp::ParameterInformation {
13780                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13781                    documentation: None,
13782                },
13783            ]),
13784            active_parameter: None,
13785        }],
13786        active_signature: Some(0),
13787        active_parameter: Some(0),
13788    };
13789    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13790    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13791        .await;
13792    cx.editor(|editor, _, _| {
13793        assert!(editor.signature_help_state.is_shown());
13794    });
13795
13796    // Restore the popover with more parameter input
13797    cx.set_state(indoc! {"
13798        fn main() {
13799            sample(param1, param2ˇ);
13800        }
13801
13802        fn sample(param1: u8, param2: u8) {}
13803    "});
13804
13805    let mocked_response = lsp::SignatureHelp {
13806        signatures: vec![lsp::SignatureInformation {
13807            label: "fn sample(param1: u8, param2: u8)".to_string(),
13808            documentation: None,
13809            parameters: Some(vec![
13810                lsp::ParameterInformation {
13811                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13812                    documentation: None,
13813                },
13814                lsp::ParameterInformation {
13815                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13816                    documentation: None,
13817                },
13818            ]),
13819            active_parameter: None,
13820        }],
13821        active_signature: Some(0),
13822        active_parameter: Some(1),
13823    };
13824    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13825    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13826        .await;
13827
13828    // When selecting a range, the popover is gone.
13829    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13830    cx.update_editor(|editor, window, cx| {
13831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13832            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13833        })
13834    });
13835    cx.assert_editor_state(indoc! {"
13836        fn main() {
13837            sample(param1, «ˇparam2»);
13838        }
13839
13840        fn sample(param1: u8, param2: u8) {}
13841    "});
13842    cx.editor(|editor, _, _| {
13843        assert!(!editor.signature_help_state.is_shown());
13844    });
13845
13846    // When unselecting again, the popover is back if within the brackets.
13847    cx.update_editor(|editor, window, cx| {
13848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13849            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13850        })
13851    });
13852    cx.assert_editor_state(indoc! {"
13853        fn main() {
13854            sample(param1, ˇparam2);
13855        }
13856
13857        fn sample(param1: u8, param2: u8) {}
13858    "});
13859    handle_signature_help_request(&mut cx, mocked_response).await;
13860    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13861        .await;
13862    cx.editor(|editor, _, _| {
13863        assert!(editor.signature_help_state.is_shown());
13864    });
13865
13866    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13867    cx.update_editor(|editor, window, cx| {
13868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13869            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13870            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13871        })
13872    });
13873    cx.assert_editor_state(indoc! {"
13874        fn main() {
13875            sample(param1, ˇparam2);
13876        }
13877
13878        fn sample(param1: u8, param2: u8) {}
13879    "});
13880
13881    let mocked_response = lsp::SignatureHelp {
13882        signatures: vec![lsp::SignatureInformation {
13883            label: "fn sample(param1: u8, param2: u8)".to_string(),
13884            documentation: None,
13885            parameters: Some(vec![
13886                lsp::ParameterInformation {
13887                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13888                    documentation: None,
13889                },
13890                lsp::ParameterInformation {
13891                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13892                    documentation: None,
13893                },
13894            ]),
13895            active_parameter: None,
13896        }],
13897        active_signature: Some(0),
13898        active_parameter: Some(1),
13899    };
13900    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13901    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13902        .await;
13903    cx.update_editor(|editor, _, cx| {
13904        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13905    });
13906    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13907        .await;
13908    cx.update_editor(|editor, window, cx| {
13909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13910            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13911        })
13912    });
13913    cx.assert_editor_state(indoc! {"
13914        fn main() {
13915            sample(param1, «ˇparam2»);
13916        }
13917
13918        fn sample(param1: u8, param2: u8) {}
13919    "});
13920    cx.update_editor(|editor, window, cx| {
13921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13922            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13923        })
13924    });
13925    cx.assert_editor_state(indoc! {"
13926        fn main() {
13927            sample(param1, ˇparam2);
13928        }
13929
13930        fn sample(param1: u8, param2: u8) {}
13931    "});
13932    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13933        .await;
13934}
13935
13936#[gpui::test]
13937async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13938    init_test(cx, |_| {});
13939
13940    let mut cx = EditorLspTestContext::new_rust(
13941        lsp::ServerCapabilities {
13942            signature_help_provider: Some(lsp::SignatureHelpOptions {
13943                ..Default::default()
13944            }),
13945            ..Default::default()
13946        },
13947        cx,
13948    )
13949    .await;
13950
13951    cx.set_state(indoc! {"
13952        fn main() {
13953            overloadedˇ
13954        }
13955    "});
13956
13957    cx.update_editor(|editor, window, cx| {
13958        editor.handle_input("(", window, cx);
13959        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13960    });
13961
13962    // Mock response with 3 signatures
13963    let mocked_response = lsp::SignatureHelp {
13964        signatures: vec![
13965            lsp::SignatureInformation {
13966                label: "fn overloaded(x: i32)".to_string(),
13967                documentation: None,
13968                parameters: Some(vec![lsp::ParameterInformation {
13969                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13970                    documentation: None,
13971                }]),
13972                active_parameter: None,
13973            },
13974            lsp::SignatureInformation {
13975                label: "fn overloaded(x: i32, y: i32)".to_string(),
13976                documentation: None,
13977                parameters: Some(vec![
13978                    lsp::ParameterInformation {
13979                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13980                        documentation: None,
13981                    },
13982                    lsp::ParameterInformation {
13983                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13984                        documentation: None,
13985                    },
13986                ]),
13987                active_parameter: None,
13988            },
13989            lsp::SignatureInformation {
13990                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13991                documentation: None,
13992                parameters: Some(vec![
13993                    lsp::ParameterInformation {
13994                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13995                        documentation: None,
13996                    },
13997                    lsp::ParameterInformation {
13998                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13999                        documentation: None,
14000                    },
14001                    lsp::ParameterInformation {
14002                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14003                        documentation: None,
14004                    },
14005                ]),
14006                active_parameter: None,
14007            },
14008        ],
14009        active_signature: Some(1),
14010        active_parameter: Some(0),
14011    };
14012    handle_signature_help_request(&mut cx, mocked_response).await;
14013
14014    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14015        .await;
14016
14017    // Verify we have multiple signatures and the right one is selected
14018    cx.editor(|editor, _, _| {
14019        let popover = editor.signature_help_state.popover().cloned().unwrap();
14020        assert_eq!(popover.signatures.len(), 3);
14021        // active_signature was 1, so that should be the current
14022        assert_eq!(popover.current_signature, 1);
14023        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14024        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14025        assert_eq!(
14026            popover.signatures[2].label,
14027            "fn overloaded(x: i32, y: i32, z: i32)"
14028        );
14029    });
14030
14031    // Test navigation functionality
14032    cx.update_editor(|editor, window, cx| {
14033        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14034    });
14035
14036    cx.editor(|editor, _, _| {
14037        let popover = editor.signature_help_state.popover().cloned().unwrap();
14038        assert_eq!(popover.current_signature, 2);
14039    });
14040
14041    // Test wrap around
14042    cx.update_editor(|editor, window, cx| {
14043        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14044    });
14045
14046    cx.editor(|editor, _, _| {
14047        let popover = editor.signature_help_state.popover().cloned().unwrap();
14048        assert_eq!(popover.current_signature, 0);
14049    });
14050
14051    // Test previous navigation
14052    cx.update_editor(|editor, window, cx| {
14053        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14054    });
14055
14056    cx.editor(|editor, _, _| {
14057        let popover = editor.signature_help_state.popover().cloned().unwrap();
14058        assert_eq!(popover.current_signature, 2);
14059    });
14060}
14061
14062#[gpui::test]
14063async fn test_completion_mode(cx: &mut TestAppContext) {
14064    init_test(cx, |_| {});
14065    let mut cx = EditorLspTestContext::new_rust(
14066        lsp::ServerCapabilities {
14067            completion_provider: Some(lsp::CompletionOptions {
14068                resolve_provider: Some(true),
14069                ..Default::default()
14070            }),
14071            ..Default::default()
14072        },
14073        cx,
14074    )
14075    .await;
14076
14077    struct Run {
14078        run_description: &'static str,
14079        initial_state: String,
14080        buffer_marked_text: String,
14081        completion_label: &'static str,
14082        completion_text: &'static str,
14083        expected_with_insert_mode: String,
14084        expected_with_replace_mode: String,
14085        expected_with_replace_subsequence_mode: String,
14086        expected_with_replace_suffix_mode: String,
14087    }
14088
14089    let runs = [
14090        Run {
14091            run_description: "Start of word matches completion text",
14092            initial_state: "before ediˇ after".into(),
14093            buffer_marked_text: "before <edi|> after".into(),
14094            completion_label: "editor",
14095            completion_text: "editor",
14096            expected_with_insert_mode: "before editorˇ after".into(),
14097            expected_with_replace_mode: "before editorˇ after".into(),
14098            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14099            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14100        },
14101        Run {
14102            run_description: "Accept same text at the middle of the word",
14103            initial_state: "before ediˇtor after".into(),
14104            buffer_marked_text: "before <edi|tor> after".into(),
14105            completion_label: "editor",
14106            completion_text: "editor",
14107            expected_with_insert_mode: "before editorˇtor after".into(),
14108            expected_with_replace_mode: "before editorˇ after".into(),
14109            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14110            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14111        },
14112        Run {
14113            run_description: "End of word matches completion text -- cursor at end",
14114            initial_state: "before torˇ after".into(),
14115            buffer_marked_text: "before <tor|> after".into(),
14116            completion_label: "editor",
14117            completion_text: "editor",
14118            expected_with_insert_mode: "before editorˇ after".into(),
14119            expected_with_replace_mode: "before editorˇ after".into(),
14120            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14121            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14122        },
14123        Run {
14124            run_description: "End of word matches completion text -- cursor at start",
14125            initial_state: "before ˇtor after".into(),
14126            buffer_marked_text: "before <|tor> after".into(),
14127            completion_label: "editor",
14128            completion_text: "editor",
14129            expected_with_insert_mode: "before editorˇtor after".into(),
14130            expected_with_replace_mode: "before editorˇ after".into(),
14131            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14132            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14133        },
14134        Run {
14135            run_description: "Prepend text containing whitespace",
14136            initial_state: "pˇfield: bool".into(),
14137            buffer_marked_text: "<p|field>: bool".into(),
14138            completion_label: "pub ",
14139            completion_text: "pub ",
14140            expected_with_insert_mode: "pub ˇfield: bool".into(),
14141            expected_with_replace_mode: "pub ˇ: bool".into(),
14142            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14143            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14144        },
14145        Run {
14146            run_description: "Add element to start of list",
14147            initial_state: "[element_ˇelement_2]".into(),
14148            buffer_marked_text: "[<element_|element_2>]".into(),
14149            completion_label: "element_1",
14150            completion_text: "element_1",
14151            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14152            expected_with_replace_mode: "[element_1ˇ]".into(),
14153            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14154            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14155        },
14156        Run {
14157            run_description: "Add element to start of list -- first and second elements are equal",
14158            initial_state: "[elˇelement]".into(),
14159            buffer_marked_text: "[<el|element>]".into(),
14160            completion_label: "element",
14161            completion_text: "element",
14162            expected_with_insert_mode: "[elementˇelement]".into(),
14163            expected_with_replace_mode: "[elementˇ]".into(),
14164            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14165            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14166        },
14167        Run {
14168            run_description: "Ends with matching suffix",
14169            initial_state: "SubˇError".into(),
14170            buffer_marked_text: "<Sub|Error>".into(),
14171            completion_label: "SubscriptionError",
14172            completion_text: "SubscriptionError",
14173            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14174            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14175            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14176            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14177        },
14178        Run {
14179            run_description: "Suffix is a subsequence -- contiguous",
14180            initial_state: "SubˇErr".into(),
14181            buffer_marked_text: "<Sub|Err>".into(),
14182            completion_label: "SubscriptionError",
14183            completion_text: "SubscriptionError",
14184            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14185            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14186            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14187            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14188        },
14189        Run {
14190            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14191            initial_state: "Suˇscrirr".into(),
14192            buffer_marked_text: "<Su|scrirr>".into(),
14193            completion_label: "SubscriptionError",
14194            completion_text: "SubscriptionError",
14195            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14196            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14197            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14198            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14199        },
14200        Run {
14201            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14202            initial_state: "foo(indˇix)".into(),
14203            buffer_marked_text: "foo(<ind|ix>)".into(),
14204            completion_label: "node_index",
14205            completion_text: "node_index",
14206            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14207            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14208            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14209            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14210        },
14211        Run {
14212            run_description: "Replace range ends before cursor - should extend to cursor",
14213            initial_state: "before editˇo after".into(),
14214            buffer_marked_text: "before <{ed}>it|o after".into(),
14215            completion_label: "editor",
14216            completion_text: "editor",
14217            expected_with_insert_mode: "before editorˇo after".into(),
14218            expected_with_replace_mode: "before editorˇo after".into(),
14219            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14220            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14221        },
14222        Run {
14223            run_description: "Uses label for suffix matching",
14224            initial_state: "before ediˇtor after".into(),
14225            buffer_marked_text: "before <edi|tor> after".into(),
14226            completion_label: "editor",
14227            completion_text: "editor()",
14228            expected_with_insert_mode: "before editor()ˇtor after".into(),
14229            expected_with_replace_mode: "before editor()ˇ after".into(),
14230            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14231            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14232        },
14233        Run {
14234            run_description: "Case insensitive subsequence and suffix matching",
14235            initial_state: "before EDiˇtoR after".into(),
14236            buffer_marked_text: "before <EDi|toR> after".into(),
14237            completion_label: "editor",
14238            completion_text: "editor",
14239            expected_with_insert_mode: "before editorˇtoR after".into(),
14240            expected_with_replace_mode: "before editorˇ after".into(),
14241            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14242            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14243        },
14244    ];
14245
14246    for run in runs {
14247        let run_variations = [
14248            (LspInsertMode::Insert, run.expected_with_insert_mode),
14249            (LspInsertMode::Replace, run.expected_with_replace_mode),
14250            (
14251                LspInsertMode::ReplaceSubsequence,
14252                run.expected_with_replace_subsequence_mode,
14253            ),
14254            (
14255                LspInsertMode::ReplaceSuffix,
14256                run.expected_with_replace_suffix_mode,
14257            ),
14258        ];
14259
14260        for (lsp_insert_mode, expected_text) in run_variations {
14261            eprintln!(
14262                "run = {:?}, mode = {lsp_insert_mode:.?}",
14263                run.run_description,
14264            );
14265
14266            update_test_language_settings(&mut cx, |settings| {
14267                settings.defaults.completions = Some(CompletionSettingsContent {
14268                    lsp_insert_mode: Some(lsp_insert_mode),
14269                    words: Some(WordsCompletionMode::Disabled),
14270                    words_min_length: Some(0),
14271                    ..Default::default()
14272                });
14273            });
14274
14275            cx.set_state(&run.initial_state);
14276            cx.update_editor(|editor, window, cx| {
14277                editor.show_completions(&ShowCompletions, window, cx);
14278            });
14279
14280            let counter = Arc::new(AtomicUsize::new(0));
14281            handle_completion_request_with_insert_and_replace(
14282                &mut cx,
14283                &run.buffer_marked_text,
14284                vec![(run.completion_label, run.completion_text)],
14285                counter.clone(),
14286            )
14287            .await;
14288            cx.condition(|editor, _| editor.context_menu_visible())
14289                .await;
14290            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14291
14292            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14293                editor
14294                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14295                    .unwrap()
14296            });
14297            cx.assert_editor_state(&expected_text);
14298            handle_resolve_completion_request(&mut cx, None).await;
14299            apply_additional_edits.await.unwrap();
14300        }
14301    }
14302}
14303
14304#[gpui::test]
14305async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14306    init_test(cx, |_| {});
14307    let mut cx = EditorLspTestContext::new_rust(
14308        lsp::ServerCapabilities {
14309            completion_provider: Some(lsp::CompletionOptions {
14310                resolve_provider: Some(true),
14311                ..Default::default()
14312            }),
14313            ..Default::default()
14314        },
14315        cx,
14316    )
14317    .await;
14318
14319    let initial_state = "SubˇError";
14320    let buffer_marked_text = "<Sub|Error>";
14321    let completion_text = "SubscriptionError";
14322    let expected_with_insert_mode = "SubscriptionErrorˇError";
14323    let expected_with_replace_mode = "SubscriptionErrorˇ";
14324
14325    update_test_language_settings(&mut cx, |settings| {
14326        settings.defaults.completions = Some(CompletionSettingsContent {
14327            words: Some(WordsCompletionMode::Disabled),
14328            words_min_length: Some(0),
14329            // set the opposite here to ensure that the action is overriding the default behavior
14330            lsp_insert_mode: Some(LspInsertMode::Insert),
14331            ..Default::default()
14332        });
14333    });
14334
14335    cx.set_state(initial_state);
14336    cx.update_editor(|editor, window, cx| {
14337        editor.show_completions(&ShowCompletions, window, cx);
14338    });
14339
14340    let counter = Arc::new(AtomicUsize::new(0));
14341    handle_completion_request_with_insert_and_replace(
14342        &mut cx,
14343        buffer_marked_text,
14344        vec![(completion_text, completion_text)],
14345        counter.clone(),
14346    )
14347    .await;
14348    cx.condition(|editor, _| editor.context_menu_visible())
14349        .await;
14350    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14351
14352    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14353        editor
14354            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14355            .unwrap()
14356    });
14357    cx.assert_editor_state(expected_with_replace_mode);
14358    handle_resolve_completion_request(&mut cx, None).await;
14359    apply_additional_edits.await.unwrap();
14360
14361    update_test_language_settings(&mut cx, |settings| {
14362        settings.defaults.completions = Some(CompletionSettingsContent {
14363            words: Some(WordsCompletionMode::Disabled),
14364            words_min_length: Some(0),
14365            // set the opposite here to ensure that the action is overriding the default behavior
14366            lsp_insert_mode: Some(LspInsertMode::Replace),
14367            ..Default::default()
14368        });
14369    });
14370
14371    cx.set_state(initial_state);
14372    cx.update_editor(|editor, window, cx| {
14373        editor.show_completions(&ShowCompletions, window, cx);
14374    });
14375    handle_completion_request_with_insert_and_replace(
14376        &mut cx,
14377        buffer_marked_text,
14378        vec![(completion_text, completion_text)],
14379        counter.clone(),
14380    )
14381    .await;
14382    cx.condition(|editor, _| editor.context_menu_visible())
14383        .await;
14384    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14385
14386    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14387        editor
14388            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14389            .unwrap()
14390    });
14391    cx.assert_editor_state(expected_with_insert_mode);
14392    handle_resolve_completion_request(&mut cx, None).await;
14393    apply_additional_edits.await.unwrap();
14394}
14395
14396#[gpui::test]
14397async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14398    init_test(cx, |_| {});
14399    let mut cx = EditorLspTestContext::new_rust(
14400        lsp::ServerCapabilities {
14401            completion_provider: Some(lsp::CompletionOptions {
14402                resolve_provider: Some(true),
14403                ..Default::default()
14404            }),
14405            ..Default::default()
14406        },
14407        cx,
14408    )
14409    .await;
14410
14411    // scenario: surrounding text matches completion text
14412    let completion_text = "to_offset";
14413    let initial_state = indoc! {"
14414        1. buf.to_offˇsuffix
14415        2. buf.to_offˇsuf
14416        3. buf.to_offˇfix
14417        4. buf.to_offˇ
14418        5. into_offˇensive
14419        6. ˇsuffix
14420        7. let ˇ //
14421        8. aaˇzz
14422        9. buf.to_off«zzzzzˇ»suffix
14423        10. buf.«ˇzzzzz»suffix
14424        11. to_off«ˇzzzzz»
14425
14426        buf.to_offˇsuffix  // newest cursor
14427    "};
14428    let completion_marked_buffer = indoc! {"
14429        1. buf.to_offsuffix
14430        2. buf.to_offsuf
14431        3. buf.to_offfix
14432        4. buf.to_off
14433        5. into_offensive
14434        6. suffix
14435        7. let  //
14436        8. aazz
14437        9. buf.to_offzzzzzsuffix
14438        10. buf.zzzzzsuffix
14439        11. to_offzzzzz
14440
14441        buf.<to_off|suffix>  // newest cursor
14442    "};
14443    let expected = indoc! {"
14444        1. buf.to_offsetˇ
14445        2. buf.to_offsetˇsuf
14446        3. buf.to_offsetˇfix
14447        4. buf.to_offsetˇ
14448        5. into_offsetˇensive
14449        6. to_offsetˇsuffix
14450        7. let to_offsetˇ //
14451        8. aato_offsetˇzz
14452        9. buf.to_offsetˇ
14453        10. buf.to_offsetˇsuffix
14454        11. to_offsetˇ
14455
14456        buf.to_offsetˇ  // newest cursor
14457    "};
14458    cx.set_state(initial_state);
14459    cx.update_editor(|editor, window, cx| {
14460        editor.show_completions(&ShowCompletions, window, cx);
14461    });
14462    handle_completion_request_with_insert_and_replace(
14463        &mut cx,
14464        completion_marked_buffer,
14465        vec![(completion_text, completion_text)],
14466        Arc::new(AtomicUsize::new(0)),
14467    )
14468    .await;
14469    cx.condition(|editor, _| editor.context_menu_visible())
14470        .await;
14471    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14472        editor
14473            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14474            .unwrap()
14475    });
14476    cx.assert_editor_state(expected);
14477    handle_resolve_completion_request(&mut cx, None).await;
14478    apply_additional_edits.await.unwrap();
14479
14480    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14481    let completion_text = "foo_and_bar";
14482    let initial_state = indoc! {"
14483        1. ooanbˇ
14484        2. zooanbˇ
14485        3. ooanbˇz
14486        4. zooanbˇz
14487        5. ooanˇ
14488        6. oanbˇ
14489
14490        ooanbˇ
14491    "};
14492    let completion_marked_buffer = indoc! {"
14493        1. ooanb
14494        2. zooanb
14495        3. ooanbz
14496        4. zooanbz
14497        5. ooan
14498        6. oanb
14499
14500        <ooanb|>
14501    "};
14502    let expected = indoc! {"
14503        1. foo_and_barˇ
14504        2. zfoo_and_barˇ
14505        3. foo_and_barˇz
14506        4. zfoo_and_barˇz
14507        5. ooanfoo_and_barˇ
14508        6. oanbfoo_and_barˇ
14509
14510        foo_and_barˇ
14511    "};
14512    cx.set_state(initial_state);
14513    cx.update_editor(|editor, window, cx| {
14514        editor.show_completions(&ShowCompletions, window, cx);
14515    });
14516    handle_completion_request_with_insert_and_replace(
14517        &mut cx,
14518        completion_marked_buffer,
14519        vec![(completion_text, completion_text)],
14520        Arc::new(AtomicUsize::new(0)),
14521    )
14522    .await;
14523    cx.condition(|editor, _| editor.context_menu_visible())
14524        .await;
14525    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14526        editor
14527            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14528            .unwrap()
14529    });
14530    cx.assert_editor_state(expected);
14531    handle_resolve_completion_request(&mut cx, None).await;
14532    apply_additional_edits.await.unwrap();
14533
14534    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14535    // (expects the same as if it was inserted at the end)
14536    let completion_text = "foo_and_bar";
14537    let initial_state = indoc! {"
14538        1. ooˇanb
14539        2. zooˇanb
14540        3. ooˇanbz
14541        4. zooˇanbz
14542
14543        ooˇanb
14544    "};
14545    let completion_marked_buffer = indoc! {"
14546        1. ooanb
14547        2. zooanb
14548        3. ooanbz
14549        4. zooanbz
14550
14551        <oo|anb>
14552    "};
14553    let expected = indoc! {"
14554        1. foo_and_barˇ
14555        2. zfoo_and_barˇ
14556        3. foo_and_barˇz
14557        4. zfoo_and_barˇz
14558
14559        foo_and_barˇ
14560    "};
14561    cx.set_state(initial_state);
14562    cx.update_editor(|editor, window, cx| {
14563        editor.show_completions(&ShowCompletions, window, cx);
14564    });
14565    handle_completion_request_with_insert_and_replace(
14566        &mut cx,
14567        completion_marked_buffer,
14568        vec![(completion_text, completion_text)],
14569        Arc::new(AtomicUsize::new(0)),
14570    )
14571    .await;
14572    cx.condition(|editor, _| editor.context_menu_visible())
14573        .await;
14574    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14575        editor
14576            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14577            .unwrap()
14578    });
14579    cx.assert_editor_state(expected);
14580    handle_resolve_completion_request(&mut cx, None).await;
14581    apply_additional_edits.await.unwrap();
14582}
14583
14584// This used to crash
14585#[gpui::test]
14586async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14587    init_test(cx, |_| {});
14588
14589    let buffer_text = indoc! {"
14590        fn main() {
14591            10.satu;
14592
14593            //
14594            // separate cursors so they open in different excerpts (manually reproducible)
14595            //
14596
14597            10.satu20;
14598        }
14599    "};
14600    let multibuffer_text_with_selections = indoc! {"
14601        fn main() {
14602            10.satuˇ;
14603
14604            //
14605
14606            //
14607
14608            10.satuˇ20;
14609        }
14610    "};
14611    let expected_multibuffer = indoc! {"
14612        fn main() {
14613            10.saturating_sub()ˇ;
14614
14615            //
14616
14617            //
14618
14619            10.saturating_sub()ˇ;
14620        }
14621    "};
14622
14623    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14624    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14625
14626    let fs = FakeFs::new(cx.executor());
14627    fs.insert_tree(
14628        path!("/a"),
14629        json!({
14630            "main.rs": buffer_text,
14631        }),
14632    )
14633    .await;
14634
14635    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14636    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14637    language_registry.add(rust_lang());
14638    let mut fake_servers = language_registry.register_fake_lsp(
14639        "Rust",
14640        FakeLspAdapter {
14641            capabilities: lsp::ServerCapabilities {
14642                completion_provider: Some(lsp::CompletionOptions {
14643                    resolve_provider: None,
14644                    ..lsp::CompletionOptions::default()
14645                }),
14646                ..lsp::ServerCapabilities::default()
14647            },
14648            ..FakeLspAdapter::default()
14649        },
14650    );
14651    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14652    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14653    let buffer = project
14654        .update(cx, |project, cx| {
14655            project.open_local_buffer(path!("/a/main.rs"), cx)
14656        })
14657        .await
14658        .unwrap();
14659
14660    let multi_buffer = cx.new(|cx| {
14661        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14662        multi_buffer.push_excerpts(
14663            buffer.clone(),
14664            [ExcerptRange::new(0..first_excerpt_end)],
14665            cx,
14666        );
14667        multi_buffer.push_excerpts(
14668            buffer.clone(),
14669            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14670            cx,
14671        );
14672        multi_buffer
14673    });
14674
14675    let editor = workspace
14676        .update(cx, |_, window, cx| {
14677            cx.new(|cx| {
14678                Editor::new(
14679                    EditorMode::Full {
14680                        scale_ui_elements_with_buffer_font_size: false,
14681                        show_active_line_background: false,
14682                        sizing_behavior: SizingBehavior::Default,
14683                    },
14684                    multi_buffer.clone(),
14685                    Some(project.clone()),
14686                    window,
14687                    cx,
14688                )
14689            })
14690        })
14691        .unwrap();
14692
14693    let pane = workspace
14694        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14695        .unwrap();
14696    pane.update_in(cx, |pane, window, cx| {
14697        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14698    });
14699
14700    let fake_server = fake_servers.next().await.unwrap();
14701
14702    editor.update_in(cx, |editor, window, cx| {
14703        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14704            s.select_ranges([
14705                Point::new(1, 11)..Point::new(1, 11),
14706                Point::new(7, 11)..Point::new(7, 11),
14707            ])
14708        });
14709
14710        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14711    });
14712
14713    editor.update_in(cx, |editor, window, cx| {
14714        editor.show_completions(&ShowCompletions, window, cx);
14715    });
14716
14717    fake_server
14718        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14719            let completion_item = lsp::CompletionItem {
14720                label: "saturating_sub()".into(),
14721                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14722                    lsp::InsertReplaceEdit {
14723                        new_text: "saturating_sub()".to_owned(),
14724                        insert: lsp::Range::new(
14725                            lsp::Position::new(7, 7),
14726                            lsp::Position::new(7, 11),
14727                        ),
14728                        replace: lsp::Range::new(
14729                            lsp::Position::new(7, 7),
14730                            lsp::Position::new(7, 13),
14731                        ),
14732                    },
14733                )),
14734                ..lsp::CompletionItem::default()
14735            };
14736
14737            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14738        })
14739        .next()
14740        .await
14741        .unwrap();
14742
14743    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14744        .await;
14745
14746    editor
14747        .update_in(cx, |editor, window, cx| {
14748            editor
14749                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14750                .unwrap()
14751        })
14752        .await
14753        .unwrap();
14754
14755    editor.update(cx, |editor, cx| {
14756        assert_text_with_selections(editor, expected_multibuffer, cx);
14757    })
14758}
14759
14760#[gpui::test]
14761async fn test_completion(cx: &mut TestAppContext) {
14762    init_test(cx, |_| {});
14763
14764    let mut cx = EditorLspTestContext::new_rust(
14765        lsp::ServerCapabilities {
14766            completion_provider: Some(lsp::CompletionOptions {
14767                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14768                resolve_provider: Some(true),
14769                ..Default::default()
14770            }),
14771            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14772            ..Default::default()
14773        },
14774        cx,
14775    )
14776    .await;
14777    let counter = Arc::new(AtomicUsize::new(0));
14778
14779    cx.set_state(indoc! {"
14780        oneˇ
14781        two
14782        three
14783    "});
14784    cx.simulate_keystroke(".");
14785    handle_completion_request(
14786        indoc! {"
14787            one.|<>
14788            two
14789            three
14790        "},
14791        vec!["first_completion", "second_completion"],
14792        true,
14793        counter.clone(),
14794        &mut cx,
14795    )
14796    .await;
14797    cx.condition(|editor, _| editor.context_menu_visible())
14798        .await;
14799    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14800
14801    let _handler = handle_signature_help_request(
14802        &mut cx,
14803        lsp::SignatureHelp {
14804            signatures: vec![lsp::SignatureInformation {
14805                label: "test signature".to_string(),
14806                documentation: None,
14807                parameters: Some(vec![lsp::ParameterInformation {
14808                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14809                    documentation: None,
14810                }]),
14811                active_parameter: None,
14812            }],
14813            active_signature: None,
14814            active_parameter: None,
14815        },
14816    );
14817    cx.update_editor(|editor, window, cx| {
14818        assert!(
14819            !editor.signature_help_state.is_shown(),
14820            "No signature help was called for"
14821        );
14822        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14823    });
14824    cx.run_until_parked();
14825    cx.update_editor(|editor, _, _| {
14826        assert!(
14827            !editor.signature_help_state.is_shown(),
14828            "No signature help should be shown when completions menu is open"
14829        );
14830    });
14831
14832    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14833        editor.context_menu_next(&Default::default(), window, cx);
14834        editor
14835            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14836            .unwrap()
14837    });
14838    cx.assert_editor_state(indoc! {"
14839        one.second_completionˇ
14840        two
14841        three
14842    "});
14843
14844    handle_resolve_completion_request(
14845        &mut cx,
14846        Some(vec![
14847            (
14848                //This overlaps with the primary completion edit which is
14849                //misbehavior from the LSP spec, test that we filter it out
14850                indoc! {"
14851                    one.second_ˇcompletion
14852                    two
14853                    threeˇ
14854                "},
14855                "overlapping additional edit",
14856            ),
14857            (
14858                indoc! {"
14859                    one.second_completion
14860                    two
14861                    threeˇ
14862                "},
14863                "\nadditional edit",
14864            ),
14865        ]),
14866    )
14867    .await;
14868    apply_additional_edits.await.unwrap();
14869    cx.assert_editor_state(indoc! {"
14870        one.second_completionˇ
14871        two
14872        three
14873        additional edit
14874    "});
14875
14876    cx.set_state(indoc! {"
14877        one.second_completion
14878        twoˇ
14879        threeˇ
14880        additional edit
14881    "});
14882    cx.simulate_keystroke(" ");
14883    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14884    cx.simulate_keystroke("s");
14885    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14886
14887    cx.assert_editor_state(indoc! {"
14888        one.second_completion
14889        two sˇ
14890        three sˇ
14891        additional edit
14892    "});
14893    handle_completion_request(
14894        indoc! {"
14895            one.second_completion
14896            two s
14897            three <s|>
14898            additional edit
14899        "},
14900        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14901        true,
14902        counter.clone(),
14903        &mut cx,
14904    )
14905    .await;
14906    cx.condition(|editor, _| editor.context_menu_visible())
14907        .await;
14908    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14909
14910    cx.simulate_keystroke("i");
14911
14912    handle_completion_request(
14913        indoc! {"
14914            one.second_completion
14915            two si
14916            three <si|>
14917            additional edit
14918        "},
14919        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14920        true,
14921        counter.clone(),
14922        &mut cx,
14923    )
14924    .await;
14925    cx.condition(|editor, _| editor.context_menu_visible())
14926        .await;
14927    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14928
14929    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14930        editor
14931            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14932            .unwrap()
14933    });
14934    cx.assert_editor_state(indoc! {"
14935        one.second_completion
14936        two sixth_completionˇ
14937        three sixth_completionˇ
14938        additional edit
14939    "});
14940
14941    apply_additional_edits.await.unwrap();
14942
14943    update_test_language_settings(&mut cx, |settings| {
14944        settings.defaults.show_completions_on_input = Some(false);
14945    });
14946    cx.set_state("editorˇ");
14947    cx.simulate_keystroke(".");
14948    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14949    cx.simulate_keystrokes("c l o");
14950    cx.assert_editor_state("editor.cloˇ");
14951    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14952    cx.update_editor(|editor, window, cx| {
14953        editor.show_completions(&ShowCompletions, window, cx);
14954    });
14955    handle_completion_request(
14956        "editor.<clo|>",
14957        vec!["close", "clobber"],
14958        true,
14959        counter.clone(),
14960        &mut cx,
14961    )
14962    .await;
14963    cx.condition(|editor, _| editor.context_menu_visible())
14964        .await;
14965    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14966
14967    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14968        editor
14969            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14970            .unwrap()
14971    });
14972    cx.assert_editor_state("editor.clobberˇ");
14973    handle_resolve_completion_request(&mut cx, None).await;
14974    apply_additional_edits.await.unwrap();
14975}
14976
14977#[gpui::test]
14978async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14979    init_test(cx, |_| {});
14980
14981    let fs = FakeFs::new(cx.executor());
14982    fs.insert_tree(
14983        path!("/a"),
14984        json!({
14985            "main.rs": "",
14986        }),
14987    )
14988    .await;
14989
14990    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14991    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14992    language_registry.add(rust_lang());
14993    let command_calls = Arc::new(AtomicUsize::new(0));
14994    let registered_command = "_the/command";
14995
14996    let closure_command_calls = command_calls.clone();
14997    let mut fake_servers = language_registry.register_fake_lsp(
14998        "Rust",
14999        FakeLspAdapter {
15000            capabilities: lsp::ServerCapabilities {
15001                completion_provider: Some(lsp::CompletionOptions {
15002                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15003                    ..lsp::CompletionOptions::default()
15004                }),
15005                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15006                    commands: vec![registered_command.to_owned()],
15007                    ..lsp::ExecuteCommandOptions::default()
15008                }),
15009                ..lsp::ServerCapabilities::default()
15010            },
15011            initializer: Some(Box::new(move |fake_server| {
15012                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15013                    move |params, _| async move {
15014                        Ok(Some(lsp::CompletionResponse::Array(vec![
15015                            lsp::CompletionItem {
15016                                label: "registered_command".to_owned(),
15017                                text_edit: gen_text_edit(&params, ""),
15018                                command: Some(lsp::Command {
15019                                    title: registered_command.to_owned(),
15020                                    command: "_the/command".to_owned(),
15021                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15022                                }),
15023                                ..lsp::CompletionItem::default()
15024                            },
15025                            lsp::CompletionItem {
15026                                label: "unregistered_command".to_owned(),
15027                                text_edit: gen_text_edit(&params, ""),
15028                                command: Some(lsp::Command {
15029                                    title: "????????????".to_owned(),
15030                                    command: "????????????".to_owned(),
15031                                    arguments: Some(vec![serde_json::Value::Null]),
15032                                }),
15033                                ..lsp::CompletionItem::default()
15034                            },
15035                        ])))
15036                    },
15037                );
15038                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15039                    let command_calls = closure_command_calls.clone();
15040                    move |params, _| {
15041                        assert_eq!(params.command, registered_command);
15042                        let command_calls = command_calls.clone();
15043                        async move {
15044                            command_calls.fetch_add(1, atomic::Ordering::Release);
15045                            Ok(Some(json!(null)))
15046                        }
15047                    }
15048                });
15049            })),
15050            ..FakeLspAdapter::default()
15051        },
15052    );
15053    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15054    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15055    let editor = workspace
15056        .update(cx, |workspace, window, cx| {
15057            workspace.open_abs_path(
15058                PathBuf::from(path!("/a/main.rs")),
15059                OpenOptions::default(),
15060                window,
15061                cx,
15062            )
15063        })
15064        .unwrap()
15065        .await
15066        .unwrap()
15067        .downcast::<Editor>()
15068        .unwrap();
15069    let _fake_server = fake_servers.next().await.unwrap();
15070
15071    editor.update_in(cx, |editor, window, cx| {
15072        cx.focus_self(window);
15073        editor.move_to_end(&MoveToEnd, window, cx);
15074        editor.handle_input(".", window, cx);
15075    });
15076    cx.run_until_parked();
15077    editor.update(cx, |editor, _| {
15078        assert!(editor.context_menu_visible());
15079        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15080        {
15081            let completion_labels = menu
15082                .completions
15083                .borrow()
15084                .iter()
15085                .map(|c| c.label.text.clone())
15086                .collect::<Vec<_>>();
15087            assert_eq!(
15088                completion_labels,
15089                &["registered_command", "unregistered_command",],
15090            );
15091        } else {
15092            panic!("expected completion menu to be open");
15093        }
15094    });
15095
15096    editor
15097        .update_in(cx, |editor, window, cx| {
15098            editor
15099                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15100                .unwrap()
15101        })
15102        .await
15103        .unwrap();
15104    cx.run_until_parked();
15105    assert_eq!(
15106        command_calls.load(atomic::Ordering::Acquire),
15107        1,
15108        "For completion with a registered command, Zed should send a command execution request",
15109    );
15110
15111    editor.update_in(cx, |editor, window, cx| {
15112        cx.focus_self(window);
15113        editor.handle_input(".", window, cx);
15114    });
15115    cx.run_until_parked();
15116    editor.update(cx, |editor, _| {
15117        assert!(editor.context_menu_visible());
15118        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15119        {
15120            let completion_labels = menu
15121                .completions
15122                .borrow()
15123                .iter()
15124                .map(|c| c.label.text.clone())
15125                .collect::<Vec<_>>();
15126            assert_eq!(
15127                completion_labels,
15128                &["registered_command", "unregistered_command",],
15129            );
15130        } else {
15131            panic!("expected completion menu to be open");
15132        }
15133    });
15134    editor
15135        .update_in(cx, |editor, window, cx| {
15136            editor.context_menu_next(&Default::default(), window, cx);
15137            editor
15138                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15139                .unwrap()
15140        })
15141        .await
15142        .unwrap();
15143    cx.run_until_parked();
15144    assert_eq!(
15145        command_calls.load(atomic::Ordering::Acquire),
15146        1,
15147        "For completion with an unregistered command, Zed should not send a command execution request",
15148    );
15149}
15150
15151#[gpui::test]
15152async fn test_completion_reuse(cx: &mut TestAppContext) {
15153    init_test(cx, |_| {});
15154
15155    let mut cx = EditorLspTestContext::new_rust(
15156        lsp::ServerCapabilities {
15157            completion_provider: Some(lsp::CompletionOptions {
15158                trigger_characters: Some(vec![".".to_string()]),
15159                ..Default::default()
15160            }),
15161            ..Default::default()
15162        },
15163        cx,
15164    )
15165    .await;
15166
15167    let counter = Arc::new(AtomicUsize::new(0));
15168    cx.set_state("objˇ");
15169    cx.simulate_keystroke(".");
15170
15171    // Initial completion request returns complete results
15172    let is_incomplete = false;
15173    handle_completion_request(
15174        "obj.|<>",
15175        vec!["a", "ab", "abc"],
15176        is_incomplete,
15177        counter.clone(),
15178        &mut cx,
15179    )
15180    .await;
15181    cx.run_until_parked();
15182    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15183    cx.assert_editor_state("obj.ˇ");
15184    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15185
15186    // Type "a" - filters existing completions
15187    cx.simulate_keystroke("a");
15188    cx.run_until_parked();
15189    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15190    cx.assert_editor_state("obj.aˇ");
15191    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15192
15193    // Type "b" - filters existing completions
15194    cx.simulate_keystroke("b");
15195    cx.run_until_parked();
15196    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15197    cx.assert_editor_state("obj.abˇ");
15198    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15199
15200    // Type "c" - filters existing completions
15201    cx.simulate_keystroke("c");
15202    cx.run_until_parked();
15203    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15204    cx.assert_editor_state("obj.abcˇ");
15205    check_displayed_completions(vec!["abc"], &mut cx);
15206
15207    // Backspace to delete "c" - filters existing completions
15208    cx.update_editor(|editor, window, cx| {
15209        editor.backspace(&Backspace, window, cx);
15210    });
15211    cx.run_until_parked();
15212    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15213    cx.assert_editor_state("obj.abˇ");
15214    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15215
15216    // Moving cursor to the left dismisses menu.
15217    cx.update_editor(|editor, window, cx| {
15218        editor.move_left(&MoveLeft, window, cx);
15219    });
15220    cx.run_until_parked();
15221    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15222    cx.assert_editor_state("obj.aˇb");
15223    cx.update_editor(|editor, _, _| {
15224        assert_eq!(editor.context_menu_visible(), false);
15225    });
15226
15227    // Type "b" - new request
15228    cx.simulate_keystroke("b");
15229    let is_incomplete = false;
15230    handle_completion_request(
15231        "obj.<ab|>a",
15232        vec!["ab", "abc"],
15233        is_incomplete,
15234        counter.clone(),
15235        &mut cx,
15236    )
15237    .await;
15238    cx.run_until_parked();
15239    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15240    cx.assert_editor_state("obj.abˇb");
15241    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15242
15243    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15244    cx.update_editor(|editor, window, cx| {
15245        editor.backspace(&Backspace, window, cx);
15246    });
15247    let is_incomplete = false;
15248    handle_completion_request(
15249        "obj.<a|>b",
15250        vec!["a", "ab", "abc"],
15251        is_incomplete,
15252        counter.clone(),
15253        &mut cx,
15254    )
15255    .await;
15256    cx.run_until_parked();
15257    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15258    cx.assert_editor_state("obj.aˇb");
15259    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15260
15261    // Backspace to delete "a" - dismisses menu.
15262    cx.update_editor(|editor, window, cx| {
15263        editor.backspace(&Backspace, window, cx);
15264    });
15265    cx.run_until_parked();
15266    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15267    cx.assert_editor_state("obj.ˇb");
15268    cx.update_editor(|editor, _, _| {
15269        assert_eq!(editor.context_menu_visible(), false);
15270    });
15271}
15272
15273#[gpui::test]
15274async fn test_word_completion(cx: &mut TestAppContext) {
15275    let lsp_fetch_timeout_ms = 10;
15276    init_test(cx, |language_settings| {
15277        language_settings.defaults.completions = Some(CompletionSettingsContent {
15278            words_min_length: Some(0),
15279            lsp_fetch_timeout_ms: Some(10),
15280            lsp_insert_mode: Some(LspInsertMode::Insert),
15281            ..Default::default()
15282        });
15283    });
15284
15285    let mut cx = EditorLspTestContext::new_rust(
15286        lsp::ServerCapabilities {
15287            completion_provider: Some(lsp::CompletionOptions {
15288                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15289                ..lsp::CompletionOptions::default()
15290            }),
15291            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15292            ..lsp::ServerCapabilities::default()
15293        },
15294        cx,
15295    )
15296    .await;
15297
15298    let throttle_completions = Arc::new(AtomicBool::new(false));
15299
15300    let lsp_throttle_completions = throttle_completions.clone();
15301    let _completion_requests_handler =
15302        cx.lsp
15303            .server
15304            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15305                let lsp_throttle_completions = lsp_throttle_completions.clone();
15306                let cx = cx.clone();
15307                async move {
15308                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15309                        cx.background_executor()
15310                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15311                            .await;
15312                    }
15313                    Ok(Some(lsp::CompletionResponse::Array(vec![
15314                        lsp::CompletionItem {
15315                            label: "first".into(),
15316                            ..lsp::CompletionItem::default()
15317                        },
15318                        lsp::CompletionItem {
15319                            label: "last".into(),
15320                            ..lsp::CompletionItem::default()
15321                        },
15322                    ])))
15323                }
15324            });
15325
15326    cx.set_state(indoc! {"
15327        oneˇ
15328        two
15329        three
15330    "});
15331    cx.simulate_keystroke(".");
15332    cx.executor().run_until_parked();
15333    cx.condition(|editor, _| editor.context_menu_visible())
15334        .await;
15335    cx.update_editor(|editor, window, cx| {
15336        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15337        {
15338            assert_eq!(
15339                completion_menu_entries(menu),
15340                &["first", "last"],
15341                "When LSP server is fast to reply, no fallback word completions are used"
15342            );
15343        } else {
15344            panic!("expected completion menu to be open");
15345        }
15346        editor.cancel(&Cancel, window, cx);
15347    });
15348    cx.executor().run_until_parked();
15349    cx.condition(|editor, _| !editor.context_menu_visible())
15350        .await;
15351
15352    throttle_completions.store(true, atomic::Ordering::Release);
15353    cx.simulate_keystroke(".");
15354    cx.executor()
15355        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15356    cx.executor().run_until_parked();
15357    cx.condition(|editor, _| editor.context_menu_visible())
15358        .await;
15359    cx.update_editor(|editor, _, _| {
15360        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15361        {
15362            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15363                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15364        } else {
15365            panic!("expected completion menu to be open");
15366        }
15367    });
15368}
15369
15370#[gpui::test]
15371async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15372    init_test(cx, |language_settings| {
15373        language_settings.defaults.completions = Some(CompletionSettingsContent {
15374            words: Some(WordsCompletionMode::Enabled),
15375            words_min_length: Some(0),
15376            lsp_insert_mode: Some(LspInsertMode::Insert),
15377            ..Default::default()
15378        });
15379    });
15380
15381    let mut cx = EditorLspTestContext::new_rust(
15382        lsp::ServerCapabilities {
15383            completion_provider: Some(lsp::CompletionOptions {
15384                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15385                ..lsp::CompletionOptions::default()
15386            }),
15387            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15388            ..lsp::ServerCapabilities::default()
15389        },
15390        cx,
15391    )
15392    .await;
15393
15394    let _completion_requests_handler =
15395        cx.lsp
15396            .server
15397            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15398                Ok(Some(lsp::CompletionResponse::Array(vec![
15399                    lsp::CompletionItem {
15400                        label: "first".into(),
15401                        ..lsp::CompletionItem::default()
15402                    },
15403                    lsp::CompletionItem {
15404                        label: "last".into(),
15405                        ..lsp::CompletionItem::default()
15406                    },
15407                ])))
15408            });
15409
15410    cx.set_state(indoc! {"ˇ
15411        first
15412        last
15413        second
15414    "});
15415    cx.simulate_keystroke(".");
15416    cx.executor().run_until_parked();
15417    cx.condition(|editor, _| editor.context_menu_visible())
15418        .await;
15419    cx.update_editor(|editor, _, _| {
15420        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15421        {
15422            assert_eq!(
15423                completion_menu_entries(menu),
15424                &["first", "last", "second"],
15425                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15426            );
15427        } else {
15428            panic!("expected completion menu to be open");
15429        }
15430    });
15431}
15432
15433#[gpui::test]
15434async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15435    init_test(cx, |language_settings| {
15436        language_settings.defaults.completions = Some(CompletionSettingsContent {
15437            words: Some(WordsCompletionMode::Disabled),
15438            words_min_length: Some(0),
15439            lsp_insert_mode: Some(LspInsertMode::Insert),
15440            ..Default::default()
15441        });
15442    });
15443
15444    let mut cx = EditorLspTestContext::new_rust(
15445        lsp::ServerCapabilities {
15446            completion_provider: Some(lsp::CompletionOptions {
15447                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15448                ..lsp::CompletionOptions::default()
15449            }),
15450            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15451            ..lsp::ServerCapabilities::default()
15452        },
15453        cx,
15454    )
15455    .await;
15456
15457    let _completion_requests_handler =
15458        cx.lsp
15459            .server
15460            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15461                panic!("LSP completions should not be queried when dealing with word completions")
15462            });
15463
15464    cx.set_state(indoc! {"ˇ
15465        first
15466        last
15467        second
15468    "});
15469    cx.update_editor(|editor, window, cx| {
15470        editor.show_word_completions(&ShowWordCompletions, window, cx);
15471    });
15472    cx.executor().run_until_parked();
15473    cx.condition(|editor, _| editor.context_menu_visible())
15474        .await;
15475    cx.update_editor(|editor, _, _| {
15476        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15477        {
15478            assert_eq!(
15479                completion_menu_entries(menu),
15480                &["first", "last", "second"],
15481                "`ShowWordCompletions` action should show word completions"
15482            );
15483        } else {
15484            panic!("expected completion menu to be open");
15485        }
15486    });
15487
15488    cx.simulate_keystroke("l");
15489    cx.executor().run_until_parked();
15490    cx.condition(|editor, _| editor.context_menu_visible())
15491        .await;
15492    cx.update_editor(|editor, _, _| {
15493        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15494        {
15495            assert_eq!(
15496                completion_menu_entries(menu),
15497                &["last"],
15498                "After showing word completions, further editing should filter them and not query the LSP"
15499            );
15500        } else {
15501            panic!("expected completion menu to be open");
15502        }
15503    });
15504}
15505
15506#[gpui::test]
15507async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15508    init_test(cx, |language_settings| {
15509        language_settings.defaults.completions = Some(CompletionSettingsContent {
15510            words_min_length: Some(0),
15511            lsp: Some(false),
15512            lsp_insert_mode: Some(LspInsertMode::Insert),
15513            ..Default::default()
15514        });
15515    });
15516
15517    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15518
15519    cx.set_state(indoc! {"ˇ
15520        0_usize
15521        let
15522        33
15523        4.5f32
15524    "});
15525    cx.update_editor(|editor, window, cx| {
15526        editor.show_completions(&ShowCompletions, window, cx);
15527    });
15528    cx.executor().run_until_parked();
15529    cx.condition(|editor, _| editor.context_menu_visible())
15530        .await;
15531    cx.update_editor(|editor, window, cx| {
15532        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15533        {
15534            assert_eq!(
15535                completion_menu_entries(menu),
15536                &["let"],
15537                "With no digits in the completion query, no digits should be in the word completions"
15538            );
15539        } else {
15540            panic!("expected completion menu to be open");
15541        }
15542        editor.cancel(&Cancel, window, cx);
15543    });
15544
15545    cx.set_state(indoc! {"15546        0_usize
15547        let
15548        3
15549        33.35f32
15550    "});
15551    cx.update_editor(|editor, window, cx| {
15552        editor.show_completions(&ShowCompletions, window, cx);
15553    });
15554    cx.executor().run_until_parked();
15555    cx.condition(|editor, _| editor.context_menu_visible())
15556        .await;
15557    cx.update_editor(|editor, _, _| {
15558        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15559        {
15560            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15561                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15562        } else {
15563            panic!("expected completion menu to be open");
15564        }
15565    });
15566}
15567
15568#[gpui::test]
15569async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15570    init_test(cx, |language_settings| {
15571        language_settings.defaults.completions = Some(CompletionSettingsContent {
15572            words: Some(WordsCompletionMode::Enabled),
15573            words_min_length: Some(3),
15574            lsp_insert_mode: Some(LspInsertMode::Insert),
15575            ..Default::default()
15576        });
15577    });
15578
15579    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15580    cx.set_state(indoc! {"ˇ
15581        wow
15582        wowen
15583        wowser
15584    "});
15585    cx.simulate_keystroke("w");
15586    cx.executor().run_until_parked();
15587    cx.update_editor(|editor, _, _| {
15588        if editor.context_menu.borrow_mut().is_some() {
15589            panic!(
15590                "expected completion menu to be hidden, as words completion threshold is not met"
15591            );
15592        }
15593    });
15594
15595    cx.update_editor(|editor, window, cx| {
15596        editor.show_word_completions(&ShowWordCompletions, window, cx);
15597    });
15598    cx.executor().run_until_parked();
15599    cx.update_editor(|editor, window, cx| {
15600        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15601        {
15602            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");
15603        } else {
15604            panic!("expected completion menu to be open after the word completions are called with an action");
15605        }
15606
15607        editor.cancel(&Cancel, window, cx);
15608    });
15609    cx.update_editor(|editor, _, _| {
15610        if editor.context_menu.borrow_mut().is_some() {
15611            panic!("expected completion menu to be hidden after canceling");
15612        }
15613    });
15614
15615    cx.simulate_keystroke("o");
15616    cx.executor().run_until_parked();
15617    cx.update_editor(|editor, _, _| {
15618        if editor.context_menu.borrow_mut().is_some() {
15619            panic!(
15620                "expected completion menu to be hidden, as words completion threshold is not met still"
15621            );
15622        }
15623    });
15624
15625    cx.simulate_keystroke("w");
15626    cx.executor().run_until_parked();
15627    cx.update_editor(|editor, _, _| {
15628        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15629        {
15630            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15631        } else {
15632            panic!("expected completion menu to be open after the word completions threshold is met");
15633        }
15634    });
15635}
15636
15637#[gpui::test]
15638async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15639    init_test(cx, |language_settings| {
15640        language_settings.defaults.completions = Some(CompletionSettingsContent {
15641            words: Some(WordsCompletionMode::Enabled),
15642            words_min_length: Some(0),
15643            lsp_insert_mode: Some(LspInsertMode::Insert),
15644            ..Default::default()
15645        });
15646    });
15647
15648    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15649    cx.update_editor(|editor, _, _| {
15650        editor.disable_word_completions();
15651    });
15652    cx.set_state(indoc! {"ˇ
15653        wow
15654        wowen
15655        wowser
15656    "});
15657    cx.simulate_keystroke("w");
15658    cx.executor().run_until_parked();
15659    cx.update_editor(|editor, _, _| {
15660        if editor.context_menu.borrow_mut().is_some() {
15661            panic!(
15662                "expected completion menu to be hidden, as words completion are disabled for this editor"
15663            );
15664        }
15665    });
15666
15667    cx.update_editor(|editor, window, cx| {
15668        editor.show_word_completions(&ShowWordCompletions, window, cx);
15669    });
15670    cx.executor().run_until_parked();
15671    cx.update_editor(|editor, _, _| {
15672        if editor.context_menu.borrow_mut().is_some() {
15673            panic!(
15674                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15675            );
15676        }
15677    });
15678}
15679
15680#[gpui::test]
15681async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15682    init_test(cx, |language_settings| {
15683        language_settings.defaults.completions = Some(CompletionSettingsContent {
15684            words: Some(WordsCompletionMode::Disabled),
15685            words_min_length: Some(0),
15686            lsp_insert_mode: Some(LspInsertMode::Insert),
15687            ..Default::default()
15688        });
15689    });
15690
15691    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15692    cx.update_editor(|editor, _, _| {
15693        editor.set_completion_provider(None);
15694    });
15695    cx.set_state(indoc! {"ˇ
15696        wow
15697        wowen
15698        wowser
15699    "});
15700    cx.simulate_keystroke("w");
15701    cx.executor().run_until_parked();
15702    cx.update_editor(|editor, _, _| {
15703        if editor.context_menu.borrow_mut().is_some() {
15704            panic!("expected completion menu to be hidden, as disabled in settings");
15705        }
15706    });
15707}
15708
15709fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15710    let position = || lsp::Position {
15711        line: params.text_document_position.position.line,
15712        character: params.text_document_position.position.character,
15713    };
15714    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15715        range: lsp::Range {
15716            start: position(),
15717            end: position(),
15718        },
15719        new_text: text.to_string(),
15720    }))
15721}
15722
15723#[gpui::test]
15724async fn test_multiline_completion(cx: &mut TestAppContext) {
15725    init_test(cx, |_| {});
15726
15727    let fs = FakeFs::new(cx.executor());
15728    fs.insert_tree(
15729        path!("/a"),
15730        json!({
15731            "main.ts": "a",
15732        }),
15733    )
15734    .await;
15735
15736    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15737    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15738    let typescript_language = Arc::new(Language::new(
15739        LanguageConfig {
15740            name: "TypeScript".into(),
15741            matcher: LanguageMatcher {
15742                path_suffixes: vec!["ts".to_string()],
15743                ..LanguageMatcher::default()
15744            },
15745            line_comments: vec!["// ".into()],
15746            ..LanguageConfig::default()
15747        },
15748        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15749    ));
15750    language_registry.add(typescript_language.clone());
15751    let mut fake_servers = language_registry.register_fake_lsp(
15752        "TypeScript",
15753        FakeLspAdapter {
15754            capabilities: lsp::ServerCapabilities {
15755                completion_provider: Some(lsp::CompletionOptions {
15756                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15757                    ..lsp::CompletionOptions::default()
15758                }),
15759                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15760                ..lsp::ServerCapabilities::default()
15761            },
15762            // Emulate vtsls label generation
15763            label_for_completion: Some(Box::new(|item, _| {
15764                let text = if let Some(description) = item
15765                    .label_details
15766                    .as_ref()
15767                    .and_then(|label_details| label_details.description.as_ref())
15768                {
15769                    format!("{} {}", item.label, description)
15770                } else if let Some(detail) = &item.detail {
15771                    format!("{} {}", item.label, detail)
15772                } else {
15773                    item.label.clone()
15774                };
15775                Some(language::CodeLabel::plain(text, None))
15776            })),
15777            ..FakeLspAdapter::default()
15778        },
15779    );
15780    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15781    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15782    let worktree_id = workspace
15783        .update(cx, |workspace, _window, cx| {
15784            workspace.project().update(cx, |project, cx| {
15785                project.worktrees(cx).next().unwrap().read(cx).id()
15786            })
15787        })
15788        .unwrap();
15789    let _buffer = project
15790        .update(cx, |project, cx| {
15791            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15792        })
15793        .await
15794        .unwrap();
15795    let editor = workspace
15796        .update(cx, |workspace, window, cx| {
15797            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15798        })
15799        .unwrap()
15800        .await
15801        .unwrap()
15802        .downcast::<Editor>()
15803        .unwrap();
15804    let fake_server = fake_servers.next().await.unwrap();
15805
15806    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15807    let multiline_label_2 = "a\nb\nc\n";
15808    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15809    let multiline_description = "d\ne\nf\n";
15810    let multiline_detail_2 = "g\nh\ni\n";
15811
15812    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15813        move |params, _| async move {
15814            Ok(Some(lsp::CompletionResponse::Array(vec![
15815                lsp::CompletionItem {
15816                    label: multiline_label.to_string(),
15817                    text_edit: gen_text_edit(&params, "new_text_1"),
15818                    ..lsp::CompletionItem::default()
15819                },
15820                lsp::CompletionItem {
15821                    label: "single line label 1".to_string(),
15822                    detail: Some(multiline_detail.to_string()),
15823                    text_edit: gen_text_edit(&params, "new_text_2"),
15824                    ..lsp::CompletionItem::default()
15825                },
15826                lsp::CompletionItem {
15827                    label: "single line label 2".to_string(),
15828                    label_details: Some(lsp::CompletionItemLabelDetails {
15829                        description: Some(multiline_description.to_string()),
15830                        detail: None,
15831                    }),
15832                    text_edit: gen_text_edit(&params, "new_text_2"),
15833                    ..lsp::CompletionItem::default()
15834                },
15835                lsp::CompletionItem {
15836                    label: multiline_label_2.to_string(),
15837                    detail: Some(multiline_detail_2.to_string()),
15838                    text_edit: gen_text_edit(&params, "new_text_3"),
15839                    ..lsp::CompletionItem::default()
15840                },
15841                lsp::CompletionItem {
15842                    label: "Label with many     spaces and \t but without newlines".to_string(),
15843                    detail: Some(
15844                        "Details with many     spaces and \t but without newlines".to_string(),
15845                    ),
15846                    text_edit: gen_text_edit(&params, "new_text_4"),
15847                    ..lsp::CompletionItem::default()
15848                },
15849            ])))
15850        },
15851    );
15852
15853    editor.update_in(cx, |editor, window, cx| {
15854        cx.focus_self(window);
15855        editor.move_to_end(&MoveToEnd, window, cx);
15856        editor.handle_input(".", window, cx);
15857    });
15858    cx.run_until_parked();
15859    completion_handle.next().await.unwrap();
15860
15861    editor.update(cx, |editor, _| {
15862        assert!(editor.context_menu_visible());
15863        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15864        {
15865            let completion_labels = menu
15866                .completions
15867                .borrow()
15868                .iter()
15869                .map(|c| c.label.text.clone())
15870                .collect::<Vec<_>>();
15871            assert_eq!(
15872                completion_labels,
15873                &[
15874                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15875                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15876                    "single line label 2 d e f ",
15877                    "a b c g h i ",
15878                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15879                ],
15880                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15881            );
15882
15883            for completion in menu
15884                .completions
15885                .borrow()
15886                .iter() {
15887                    assert_eq!(
15888                        completion.label.filter_range,
15889                        0..completion.label.text.len(),
15890                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15891                    );
15892                }
15893        } else {
15894            panic!("expected completion menu to be open");
15895        }
15896    });
15897}
15898
15899#[gpui::test]
15900async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15901    init_test(cx, |_| {});
15902    let mut cx = EditorLspTestContext::new_rust(
15903        lsp::ServerCapabilities {
15904            completion_provider: Some(lsp::CompletionOptions {
15905                trigger_characters: Some(vec![".".to_string()]),
15906                ..Default::default()
15907            }),
15908            ..Default::default()
15909        },
15910        cx,
15911    )
15912    .await;
15913    cx.lsp
15914        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15915            Ok(Some(lsp::CompletionResponse::Array(vec![
15916                lsp::CompletionItem {
15917                    label: "first".into(),
15918                    ..Default::default()
15919                },
15920                lsp::CompletionItem {
15921                    label: "last".into(),
15922                    ..Default::default()
15923                },
15924            ])))
15925        });
15926    cx.set_state("variableˇ");
15927    cx.simulate_keystroke(".");
15928    cx.executor().run_until_parked();
15929
15930    cx.update_editor(|editor, _, _| {
15931        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15932        {
15933            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15934        } else {
15935            panic!("expected completion menu to be open");
15936        }
15937    });
15938
15939    cx.update_editor(|editor, window, cx| {
15940        editor.move_page_down(&MovePageDown::default(), window, cx);
15941        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15942        {
15943            assert!(
15944                menu.selected_item == 1,
15945                "expected PageDown to select the last item from the context menu"
15946            );
15947        } else {
15948            panic!("expected completion menu to stay open after PageDown");
15949        }
15950    });
15951
15952    cx.update_editor(|editor, window, cx| {
15953        editor.move_page_up(&MovePageUp::default(), window, cx);
15954        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15955        {
15956            assert!(
15957                menu.selected_item == 0,
15958                "expected PageUp to select the first item from the context menu"
15959            );
15960        } else {
15961            panic!("expected completion menu to stay open after PageUp");
15962        }
15963    });
15964}
15965
15966#[gpui::test]
15967async fn test_as_is_completions(cx: &mut TestAppContext) {
15968    init_test(cx, |_| {});
15969    let mut cx = EditorLspTestContext::new_rust(
15970        lsp::ServerCapabilities {
15971            completion_provider: Some(lsp::CompletionOptions {
15972                ..Default::default()
15973            }),
15974            ..Default::default()
15975        },
15976        cx,
15977    )
15978    .await;
15979    cx.lsp
15980        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15981            Ok(Some(lsp::CompletionResponse::Array(vec![
15982                lsp::CompletionItem {
15983                    label: "unsafe".into(),
15984                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15985                        range: lsp::Range {
15986                            start: lsp::Position {
15987                                line: 1,
15988                                character: 2,
15989                            },
15990                            end: lsp::Position {
15991                                line: 1,
15992                                character: 3,
15993                            },
15994                        },
15995                        new_text: "unsafe".to_string(),
15996                    })),
15997                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15998                    ..Default::default()
15999                },
16000            ])))
16001        });
16002    cx.set_state("fn a() {}\n");
16003    cx.executor().run_until_parked();
16004    cx.update_editor(|editor, window, cx| {
16005        editor.trigger_completion_on_input("n", true, window, cx)
16006    });
16007    cx.executor().run_until_parked();
16008
16009    cx.update_editor(|editor, window, cx| {
16010        editor.confirm_completion(&Default::default(), window, cx)
16011    });
16012    cx.executor().run_until_parked();
16013    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16014}
16015
16016#[gpui::test]
16017async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16018    init_test(cx, |_| {});
16019    let language =
16020        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16021    let mut cx = EditorLspTestContext::new(
16022        language,
16023        lsp::ServerCapabilities {
16024            completion_provider: Some(lsp::CompletionOptions {
16025                ..lsp::CompletionOptions::default()
16026            }),
16027            ..lsp::ServerCapabilities::default()
16028        },
16029        cx,
16030    )
16031    .await;
16032
16033    cx.set_state(
16034        "#ifndef BAR_H
16035#define BAR_H
16036
16037#include <stdbool.h>
16038
16039int fn_branch(bool do_branch1, bool do_branch2);
16040
16041#endif // BAR_H
16042ˇ",
16043    );
16044    cx.executor().run_until_parked();
16045    cx.update_editor(|editor, window, cx| {
16046        editor.handle_input("#", window, cx);
16047    });
16048    cx.executor().run_until_parked();
16049    cx.update_editor(|editor, window, cx| {
16050        editor.handle_input("i", window, cx);
16051    });
16052    cx.executor().run_until_parked();
16053    cx.update_editor(|editor, window, cx| {
16054        editor.handle_input("n", window, cx);
16055    });
16056    cx.executor().run_until_parked();
16057    cx.assert_editor_state(
16058        "#ifndef BAR_H
16059#define BAR_H
16060
16061#include <stdbool.h>
16062
16063int fn_branch(bool do_branch1, bool do_branch2);
16064
16065#endif // BAR_H
16066#inˇ",
16067    );
16068
16069    cx.lsp
16070        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16071            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16072                is_incomplete: false,
16073                item_defaults: None,
16074                items: vec![lsp::CompletionItem {
16075                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16076                    label_details: Some(lsp::CompletionItemLabelDetails {
16077                        detail: Some("header".to_string()),
16078                        description: None,
16079                    }),
16080                    label: " include".to_string(),
16081                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16082                        range: lsp::Range {
16083                            start: lsp::Position {
16084                                line: 8,
16085                                character: 1,
16086                            },
16087                            end: lsp::Position {
16088                                line: 8,
16089                                character: 1,
16090                            },
16091                        },
16092                        new_text: "include \"$0\"".to_string(),
16093                    })),
16094                    sort_text: Some("40b67681include".to_string()),
16095                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16096                    filter_text: Some("include".to_string()),
16097                    insert_text: Some("include \"$0\"".to_string()),
16098                    ..lsp::CompletionItem::default()
16099                }],
16100            })))
16101        });
16102    cx.update_editor(|editor, window, cx| {
16103        editor.show_completions(&ShowCompletions, window, cx);
16104    });
16105    cx.executor().run_until_parked();
16106    cx.update_editor(|editor, window, cx| {
16107        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16108    });
16109    cx.executor().run_until_parked();
16110    cx.assert_editor_state(
16111        "#ifndef BAR_H
16112#define BAR_H
16113
16114#include <stdbool.h>
16115
16116int fn_branch(bool do_branch1, bool do_branch2);
16117
16118#endif // BAR_H
16119#include \"ˇ\"",
16120    );
16121
16122    cx.lsp
16123        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16124            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16125                is_incomplete: true,
16126                item_defaults: None,
16127                items: vec![lsp::CompletionItem {
16128                    kind: Some(lsp::CompletionItemKind::FILE),
16129                    label: "AGL/".to_string(),
16130                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16131                        range: lsp::Range {
16132                            start: lsp::Position {
16133                                line: 8,
16134                                character: 10,
16135                            },
16136                            end: lsp::Position {
16137                                line: 8,
16138                                character: 11,
16139                            },
16140                        },
16141                        new_text: "AGL/".to_string(),
16142                    })),
16143                    sort_text: Some("40b67681AGL/".to_string()),
16144                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16145                    filter_text: Some("AGL/".to_string()),
16146                    insert_text: Some("AGL/".to_string()),
16147                    ..lsp::CompletionItem::default()
16148                }],
16149            })))
16150        });
16151    cx.update_editor(|editor, window, cx| {
16152        editor.show_completions(&ShowCompletions, window, cx);
16153    });
16154    cx.executor().run_until_parked();
16155    cx.update_editor(|editor, window, cx| {
16156        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16157    });
16158    cx.executor().run_until_parked();
16159    cx.assert_editor_state(
16160        r##"#ifndef BAR_H
16161#define BAR_H
16162
16163#include <stdbool.h>
16164
16165int fn_branch(bool do_branch1, bool do_branch2);
16166
16167#endif // BAR_H
16168#include "AGL/ˇ"##,
16169    );
16170
16171    cx.update_editor(|editor, window, cx| {
16172        editor.handle_input("\"", window, cx);
16173    });
16174    cx.executor().run_until_parked();
16175    cx.assert_editor_state(
16176        r##"#ifndef BAR_H
16177#define BAR_H
16178
16179#include <stdbool.h>
16180
16181int fn_branch(bool do_branch1, bool do_branch2);
16182
16183#endif // BAR_H
16184#include "AGL/"ˇ"##,
16185    );
16186}
16187
16188#[gpui::test]
16189async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16190    init_test(cx, |_| {});
16191
16192    let mut cx = EditorLspTestContext::new_rust(
16193        lsp::ServerCapabilities {
16194            completion_provider: Some(lsp::CompletionOptions {
16195                trigger_characters: Some(vec![".".to_string()]),
16196                resolve_provider: Some(true),
16197                ..Default::default()
16198            }),
16199            ..Default::default()
16200        },
16201        cx,
16202    )
16203    .await;
16204
16205    cx.set_state("fn main() { let a = 2ˇ; }");
16206    cx.simulate_keystroke(".");
16207    let completion_item = lsp::CompletionItem {
16208        label: "Some".into(),
16209        kind: Some(lsp::CompletionItemKind::SNIPPET),
16210        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16211        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16212            kind: lsp::MarkupKind::Markdown,
16213            value: "```rust\nSome(2)\n```".to_string(),
16214        })),
16215        deprecated: Some(false),
16216        sort_text: Some("Some".to_string()),
16217        filter_text: Some("Some".to_string()),
16218        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16219        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16220            range: lsp::Range {
16221                start: lsp::Position {
16222                    line: 0,
16223                    character: 22,
16224                },
16225                end: lsp::Position {
16226                    line: 0,
16227                    character: 22,
16228                },
16229            },
16230            new_text: "Some(2)".to_string(),
16231        })),
16232        additional_text_edits: Some(vec![lsp::TextEdit {
16233            range: lsp::Range {
16234                start: lsp::Position {
16235                    line: 0,
16236                    character: 20,
16237                },
16238                end: lsp::Position {
16239                    line: 0,
16240                    character: 22,
16241                },
16242            },
16243            new_text: "".to_string(),
16244        }]),
16245        ..Default::default()
16246    };
16247
16248    let closure_completion_item = completion_item.clone();
16249    let counter = Arc::new(AtomicUsize::new(0));
16250    let counter_clone = counter.clone();
16251    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16252        let task_completion_item = closure_completion_item.clone();
16253        counter_clone.fetch_add(1, atomic::Ordering::Release);
16254        async move {
16255            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16256                is_incomplete: true,
16257                item_defaults: None,
16258                items: vec![task_completion_item],
16259            })))
16260        }
16261    });
16262
16263    cx.condition(|editor, _| editor.context_menu_visible())
16264        .await;
16265    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16266    assert!(request.next().await.is_some());
16267    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16268
16269    cx.simulate_keystrokes("S o m");
16270    cx.condition(|editor, _| editor.context_menu_visible())
16271        .await;
16272    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16273    assert!(request.next().await.is_some());
16274    assert!(request.next().await.is_some());
16275    assert!(request.next().await.is_some());
16276    request.close();
16277    assert!(request.next().await.is_none());
16278    assert_eq!(
16279        counter.load(atomic::Ordering::Acquire),
16280        4,
16281        "With the completions menu open, only one LSP request should happen per input"
16282    );
16283}
16284
16285#[gpui::test]
16286async fn test_toggle_comment(cx: &mut TestAppContext) {
16287    init_test(cx, |_| {});
16288    let mut cx = EditorTestContext::new(cx).await;
16289    let language = Arc::new(Language::new(
16290        LanguageConfig {
16291            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16292            ..Default::default()
16293        },
16294        Some(tree_sitter_rust::LANGUAGE.into()),
16295    ));
16296    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16297
16298    // If multiple selections intersect a line, the line is only toggled once.
16299    cx.set_state(indoc! {"
16300        fn a() {
16301            «//b();
16302            ˇ»// «c();
16303            //ˇ»  d();
16304        }
16305    "});
16306
16307    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16308
16309    cx.assert_editor_state(indoc! {"
16310        fn a() {
16311            «b();
16312            ˇ»«c();
16313            ˇ» d();
16314        }
16315    "});
16316
16317    // The comment prefix is inserted at the same column for every line in a
16318    // selection.
16319    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16320
16321    cx.assert_editor_state(indoc! {"
16322        fn a() {
16323            // «b();
16324            ˇ»// «c();
16325            ˇ» // d();
16326        }
16327    "});
16328
16329    // If a selection ends at the beginning of a line, that line is not toggled.
16330    cx.set_selections_state(indoc! {"
16331        fn a() {
16332            // b();
16333            «// c();
16334        ˇ»     // d();
16335        }
16336    "});
16337
16338    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16339
16340    cx.assert_editor_state(indoc! {"
16341        fn a() {
16342            // b();
16343            «c();
16344        ˇ»     // d();
16345        }
16346    "});
16347
16348    // If a selection span a single line and is empty, the line is toggled.
16349    cx.set_state(indoc! {"
16350        fn a() {
16351            a();
16352            b();
16353        ˇ
16354        }
16355    "});
16356
16357    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16358
16359    cx.assert_editor_state(indoc! {"
16360        fn a() {
16361            a();
16362            b();
16363        //•ˇ
16364        }
16365    "});
16366
16367    // If a selection span multiple lines, empty lines are not toggled.
16368    cx.set_state(indoc! {"
16369        fn a() {
16370            «a();
16371
16372            c();ˇ»
16373        }
16374    "});
16375
16376    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16377
16378    cx.assert_editor_state(indoc! {"
16379        fn a() {
16380            // «a();
16381
16382            // c();ˇ»
16383        }
16384    "});
16385
16386    // If a selection includes multiple comment prefixes, all lines are uncommented.
16387    cx.set_state(indoc! {"
16388        fn a() {
16389            «// a();
16390            /// b();
16391            //! c();ˇ»
16392        }
16393    "});
16394
16395    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16396
16397    cx.assert_editor_state(indoc! {"
16398        fn a() {
16399            «a();
16400            b();
16401            c();ˇ»
16402        }
16403    "});
16404}
16405
16406#[gpui::test]
16407async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16408    init_test(cx, |_| {});
16409    let mut cx = EditorTestContext::new(cx).await;
16410    let language = Arc::new(Language::new(
16411        LanguageConfig {
16412            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16413            ..Default::default()
16414        },
16415        Some(tree_sitter_rust::LANGUAGE.into()),
16416    ));
16417    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16418
16419    let toggle_comments = &ToggleComments {
16420        advance_downwards: false,
16421        ignore_indent: true,
16422    };
16423
16424    // If multiple selections intersect a line, the line is only toggled once.
16425    cx.set_state(indoc! {"
16426        fn a() {
16427        //    «b();
16428        //    c();
16429        //    ˇ» d();
16430        }
16431    "});
16432
16433    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16434
16435    cx.assert_editor_state(indoc! {"
16436        fn a() {
16437            «b();
16438            c();
16439            ˇ» d();
16440        }
16441    "});
16442
16443    // The comment prefix is inserted at the beginning of each line
16444    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16445
16446    cx.assert_editor_state(indoc! {"
16447        fn a() {
16448        //    «b();
16449        //    c();
16450        //    ˇ» d();
16451        }
16452    "});
16453
16454    // If a selection ends at the beginning of a line, that line is not toggled.
16455    cx.set_selections_state(indoc! {"
16456        fn a() {
16457        //    b();
16458        //    «c();
16459        ˇ»//     d();
16460        }
16461    "});
16462
16463    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16464
16465    cx.assert_editor_state(indoc! {"
16466        fn a() {
16467        //    b();
16468            «c();
16469        ˇ»//     d();
16470        }
16471    "});
16472
16473    // If a selection span a single line and is empty, the line is toggled.
16474    cx.set_state(indoc! {"
16475        fn a() {
16476            a();
16477            b();
16478        ˇ
16479        }
16480    "});
16481
16482    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16483
16484    cx.assert_editor_state(indoc! {"
16485        fn a() {
16486            a();
16487            b();
16488        //ˇ
16489        }
16490    "});
16491
16492    // If a selection span multiple lines, empty lines are not toggled.
16493    cx.set_state(indoc! {"
16494        fn a() {
16495            «a();
16496
16497            c();ˇ»
16498        }
16499    "});
16500
16501    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16502
16503    cx.assert_editor_state(indoc! {"
16504        fn a() {
16505        //    «a();
16506
16507        //    c();ˇ»
16508        }
16509    "});
16510
16511    // If a selection includes multiple comment prefixes, all lines are uncommented.
16512    cx.set_state(indoc! {"
16513        fn a() {
16514        //    «a();
16515        ///    b();
16516        //!    c();ˇ»
16517        }
16518    "});
16519
16520    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16521
16522    cx.assert_editor_state(indoc! {"
16523        fn a() {
16524            «a();
16525            b();
16526            c();ˇ»
16527        }
16528    "});
16529}
16530
16531#[gpui::test]
16532async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16533    init_test(cx, |_| {});
16534
16535    let language = Arc::new(Language::new(
16536        LanguageConfig {
16537            line_comments: vec!["// ".into()],
16538            ..Default::default()
16539        },
16540        Some(tree_sitter_rust::LANGUAGE.into()),
16541    ));
16542
16543    let mut cx = EditorTestContext::new(cx).await;
16544
16545    cx.language_registry().add(language.clone());
16546    cx.update_buffer(|buffer, cx| {
16547        buffer.set_language(Some(language), cx);
16548    });
16549
16550    let toggle_comments = &ToggleComments {
16551        advance_downwards: true,
16552        ignore_indent: false,
16553    };
16554
16555    // Single cursor on one line -> advance
16556    // Cursor moves horizontally 3 characters as well on non-blank line
16557    cx.set_state(indoc!(
16558        "fn a() {
16559             ˇdog();
16560             cat();
16561        }"
16562    ));
16563    cx.update_editor(|editor, window, cx| {
16564        editor.toggle_comments(toggle_comments, window, cx);
16565    });
16566    cx.assert_editor_state(indoc!(
16567        "fn a() {
16568             // dog();
16569             catˇ();
16570        }"
16571    ));
16572
16573    // Single selection on one line -> don't advance
16574    cx.set_state(indoc!(
16575        "fn a() {
16576             «dog()ˇ»;
16577             cat();
16578        }"
16579    ));
16580    cx.update_editor(|editor, window, cx| {
16581        editor.toggle_comments(toggle_comments, window, cx);
16582    });
16583    cx.assert_editor_state(indoc!(
16584        "fn a() {
16585             // «dog()ˇ»;
16586             cat();
16587        }"
16588    ));
16589
16590    // Multiple cursors on one line -> advance
16591    cx.set_state(indoc!(
16592        "fn a() {
16593             ˇdˇog();
16594             cat();
16595        }"
16596    ));
16597    cx.update_editor(|editor, window, cx| {
16598        editor.toggle_comments(toggle_comments, window, cx);
16599    });
16600    cx.assert_editor_state(indoc!(
16601        "fn a() {
16602             // dog();
16603             catˇ(ˇ);
16604        }"
16605    ));
16606
16607    // Multiple cursors on one line, with selection -> don't advance
16608    cx.set_state(indoc!(
16609        "fn a() {
16610             ˇdˇog«()ˇ»;
16611             cat();
16612        }"
16613    ));
16614    cx.update_editor(|editor, window, cx| {
16615        editor.toggle_comments(toggle_comments, window, cx);
16616    });
16617    cx.assert_editor_state(indoc!(
16618        "fn a() {
16619             // ˇdˇog«()ˇ»;
16620             cat();
16621        }"
16622    ));
16623
16624    // Single cursor on one line -> advance
16625    // Cursor moves to column 0 on blank line
16626    cx.set_state(indoc!(
16627        "fn a() {
16628             ˇdog();
16629
16630             cat();
16631        }"
16632    ));
16633    cx.update_editor(|editor, window, cx| {
16634        editor.toggle_comments(toggle_comments, window, cx);
16635    });
16636    cx.assert_editor_state(indoc!(
16637        "fn a() {
16638             // dog();
16639        ˇ
16640             cat();
16641        }"
16642    ));
16643
16644    // Single cursor on one line -> advance
16645    // Cursor starts and ends at column 0
16646    cx.set_state(indoc!(
16647        "fn a() {
16648         ˇ    dog();
16649             cat();
16650        }"
16651    ));
16652    cx.update_editor(|editor, window, cx| {
16653        editor.toggle_comments(toggle_comments, window, cx);
16654    });
16655    cx.assert_editor_state(indoc!(
16656        "fn a() {
16657             // dog();
16658         ˇ    cat();
16659        }"
16660    ));
16661}
16662
16663#[gpui::test]
16664async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16665    init_test(cx, |_| {});
16666
16667    let mut cx = EditorTestContext::new(cx).await;
16668
16669    let html_language = Arc::new(
16670        Language::new(
16671            LanguageConfig {
16672                name: "HTML".into(),
16673                block_comment: Some(BlockCommentConfig {
16674                    start: "<!-- ".into(),
16675                    prefix: "".into(),
16676                    end: " -->".into(),
16677                    tab_size: 0,
16678                }),
16679                ..Default::default()
16680            },
16681            Some(tree_sitter_html::LANGUAGE.into()),
16682        )
16683        .with_injection_query(
16684            r#"
16685            (script_element
16686                (raw_text) @injection.content
16687                (#set! injection.language "javascript"))
16688            "#,
16689        )
16690        .unwrap(),
16691    );
16692
16693    let javascript_language = Arc::new(Language::new(
16694        LanguageConfig {
16695            name: "JavaScript".into(),
16696            line_comments: vec!["// ".into()],
16697            ..Default::default()
16698        },
16699        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16700    ));
16701
16702    cx.language_registry().add(html_language.clone());
16703    cx.language_registry().add(javascript_language);
16704    cx.update_buffer(|buffer, cx| {
16705        buffer.set_language(Some(html_language), cx);
16706    });
16707
16708    // Toggle comments for empty selections
16709    cx.set_state(
16710        &r#"
16711            <p>A</p>ˇ
16712            <p>B</p>ˇ
16713            <p>C</p>ˇ
16714        "#
16715        .unindent(),
16716    );
16717    cx.update_editor(|editor, window, cx| {
16718        editor.toggle_comments(&ToggleComments::default(), window, cx)
16719    });
16720    cx.assert_editor_state(
16721        &r#"
16722            <!-- <p>A</p>ˇ -->
16723            <!-- <p>B</p>ˇ -->
16724            <!-- <p>C</p>ˇ -->
16725        "#
16726        .unindent(),
16727    );
16728    cx.update_editor(|editor, window, cx| {
16729        editor.toggle_comments(&ToggleComments::default(), window, cx)
16730    });
16731    cx.assert_editor_state(
16732        &r#"
16733            <p>A</p>ˇ
16734            <p>B</p>ˇ
16735            <p>C</p>ˇ
16736        "#
16737        .unindent(),
16738    );
16739
16740    // Toggle comments for mixture of empty and non-empty selections, where
16741    // multiple selections occupy a given line.
16742    cx.set_state(
16743        &r#"
16744            <p>A«</p>
16745            <p>ˇ»B</p>ˇ
16746            <p>C«</p>
16747            <p>ˇ»D</p>ˇ
16748        "#
16749        .unindent(),
16750    );
16751
16752    cx.update_editor(|editor, window, cx| {
16753        editor.toggle_comments(&ToggleComments::default(), window, cx)
16754    });
16755    cx.assert_editor_state(
16756        &r#"
16757            <!-- <p>A«</p>
16758            <p>ˇ»B</p>ˇ -->
16759            <!-- <p>C«</p>
16760            <p>ˇ»D</p>ˇ -->
16761        "#
16762        .unindent(),
16763    );
16764    cx.update_editor(|editor, window, cx| {
16765        editor.toggle_comments(&ToggleComments::default(), window, cx)
16766    });
16767    cx.assert_editor_state(
16768        &r#"
16769            <p>A«</p>
16770            <p>ˇ»B</p>ˇ
16771            <p>C«</p>
16772            <p>ˇ»D</p>ˇ
16773        "#
16774        .unindent(),
16775    );
16776
16777    // Toggle comments when different languages are active for different
16778    // selections.
16779    cx.set_state(
16780        &r#"
16781            ˇ<script>
16782                ˇvar x = new Y();
16783            ˇ</script>
16784        "#
16785        .unindent(),
16786    );
16787    cx.executor().run_until_parked();
16788    cx.update_editor(|editor, window, cx| {
16789        editor.toggle_comments(&ToggleComments::default(), window, cx)
16790    });
16791    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16792    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16793    cx.assert_editor_state(
16794        &r#"
16795            <!-- ˇ<script> -->
16796                // ˇvar x = new Y();
16797            <!-- ˇ</script> -->
16798        "#
16799        .unindent(),
16800    );
16801}
16802
16803#[gpui::test]
16804fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16805    init_test(cx, |_| {});
16806
16807    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16808    let multibuffer = cx.new(|cx| {
16809        let mut multibuffer = MultiBuffer::new(ReadWrite);
16810        multibuffer.push_excerpts(
16811            buffer.clone(),
16812            [
16813                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16814                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16815            ],
16816            cx,
16817        );
16818        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16819        multibuffer
16820    });
16821
16822    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16823    editor.update_in(cx, |editor, window, cx| {
16824        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16826            s.select_ranges([
16827                Point::new(0, 0)..Point::new(0, 0),
16828                Point::new(1, 0)..Point::new(1, 0),
16829            ])
16830        });
16831
16832        editor.handle_input("X", window, cx);
16833        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16834        assert_eq!(
16835            editor.selections.ranges(&editor.display_snapshot(cx)),
16836            [
16837                Point::new(0, 1)..Point::new(0, 1),
16838                Point::new(1, 1)..Point::new(1, 1),
16839            ]
16840        );
16841
16842        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16843        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16844            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16845        });
16846        editor.backspace(&Default::default(), window, cx);
16847        assert_eq!(editor.text(cx), "Xa\nbbb");
16848        assert_eq!(
16849            editor.selections.ranges(&editor.display_snapshot(cx)),
16850            [Point::new(1, 0)..Point::new(1, 0)]
16851        );
16852
16853        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16854            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16855        });
16856        editor.backspace(&Default::default(), window, cx);
16857        assert_eq!(editor.text(cx), "X\nbb");
16858        assert_eq!(
16859            editor.selections.ranges(&editor.display_snapshot(cx)),
16860            [Point::new(0, 1)..Point::new(0, 1)]
16861        );
16862    });
16863}
16864
16865#[gpui::test]
16866fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16867    init_test(cx, |_| {});
16868
16869    let markers = vec![('[', ']').into(), ('(', ')').into()];
16870    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16871        indoc! {"
16872            [aaaa
16873            (bbbb]
16874            cccc)",
16875        },
16876        markers.clone(),
16877    );
16878    let excerpt_ranges = markers.into_iter().map(|marker| {
16879        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16880        ExcerptRange::new(context)
16881    });
16882    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16883    let multibuffer = cx.new(|cx| {
16884        let mut multibuffer = MultiBuffer::new(ReadWrite);
16885        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16886        multibuffer
16887    });
16888
16889    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16890    editor.update_in(cx, |editor, window, cx| {
16891        let (expected_text, selection_ranges) = marked_text_ranges(
16892            indoc! {"
16893                aaaa
16894                bˇbbb
16895                bˇbbˇb
16896                cccc"
16897            },
16898            true,
16899        );
16900        assert_eq!(editor.text(cx), expected_text);
16901        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16902            s.select_ranges(
16903                selection_ranges
16904                    .iter()
16905                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16906            )
16907        });
16908
16909        editor.handle_input("X", window, cx);
16910
16911        let (expected_text, expected_selections) = marked_text_ranges(
16912            indoc! {"
16913                aaaa
16914                bXˇbbXb
16915                bXˇbbXˇb
16916                cccc"
16917            },
16918            false,
16919        );
16920        assert_eq!(editor.text(cx), expected_text);
16921        assert_eq!(
16922            editor.selections.ranges(&editor.display_snapshot(cx)),
16923            expected_selections
16924                .iter()
16925                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16926                .collect::<Vec<_>>()
16927        );
16928
16929        editor.newline(&Newline, window, cx);
16930        let (expected_text, expected_selections) = marked_text_ranges(
16931            indoc! {"
16932                aaaa
16933                bX
16934                ˇbbX
16935                b
16936                bX
16937                ˇbbX
16938                ˇb
16939                cccc"
16940            },
16941            false,
16942        );
16943        assert_eq!(editor.text(cx), expected_text);
16944        assert_eq!(
16945            editor.selections.ranges(&editor.display_snapshot(cx)),
16946            expected_selections
16947                .iter()
16948                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16949                .collect::<Vec<_>>()
16950        );
16951    });
16952}
16953
16954#[gpui::test]
16955fn test_refresh_selections(cx: &mut TestAppContext) {
16956    init_test(cx, |_| {});
16957
16958    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16959    let mut excerpt1_id = None;
16960    let multibuffer = cx.new(|cx| {
16961        let mut multibuffer = MultiBuffer::new(ReadWrite);
16962        excerpt1_id = multibuffer
16963            .push_excerpts(
16964                buffer.clone(),
16965                [
16966                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16967                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16968                ],
16969                cx,
16970            )
16971            .into_iter()
16972            .next();
16973        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16974        multibuffer
16975    });
16976
16977    let editor = cx.add_window(|window, cx| {
16978        let mut editor = build_editor(multibuffer.clone(), window, cx);
16979        let snapshot = editor.snapshot(window, cx);
16980        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16981            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16982        });
16983        editor.begin_selection(
16984            Point::new(2, 1).to_display_point(&snapshot),
16985            true,
16986            1,
16987            window,
16988            cx,
16989        );
16990        assert_eq!(
16991            editor.selections.ranges(&editor.display_snapshot(cx)),
16992            [
16993                Point::new(1, 3)..Point::new(1, 3),
16994                Point::new(2, 1)..Point::new(2, 1),
16995            ]
16996        );
16997        editor
16998    });
16999
17000    // Refreshing selections is a no-op when excerpts haven't changed.
17001    _ = editor.update(cx, |editor, window, cx| {
17002        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17003        assert_eq!(
17004            editor.selections.ranges(&editor.display_snapshot(cx)),
17005            [
17006                Point::new(1, 3)..Point::new(1, 3),
17007                Point::new(2, 1)..Point::new(2, 1),
17008            ]
17009        );
17010    });
17011
17012    multibuffer.update(cx, |multibuffer, cx| {
17013        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17014    });
17015    _ = editor.update(cx, |editor, window, cx| {
17016        // Removing an excerpt causes the first selection to become degenerate.
17017        assert_eq!(
17018            editor.selections.ranges(&editor.display_snapshot(cx)),
17019            [
17020                Point::new(0, 0)..Point::new(0, 0),
17021                Point::new(0, 1)..Point::new(0, 1)
17022            ]
17023        );
17024
17025        // Refreshing selections will relocate the first selection to the original buffer
17026        // location.
17027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17028        assert_eq!(
17029            editor.selections.ranges(&editor.display_snapshot(cx)),
17030            [
17031                Point::new(0, 1)..Point::new(0, 1),
17032                Point::new(0, 3)..Point::new(0, 3)
17033            ]
17034        );
17035        assert!(editor.selections.pending_anchor().is_some());
17036    });
17037}
17038
17039#[gpui::test]
17040fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17041    init_test(cx, |_| {});
17042
17043    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17044    let mut excerpt1_id = None;
17045    let multibuffer = cx.new(|cx| {
17046        let mut multibuffer = MultiBuffer::new(ReadWrite);
17047        excerpt1_id = multibuffer
17048            .push_excerpts(
17049                buffer.clone(),
17050                [
17051                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17052                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17053                ],
17054                cx,
17055            )
17056            .into_iter()
17057            .next();
17058        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17059        multibuffer
17060    });
17061
17062    let editor = cx.add_window(|window, cx| {
17063        let mut editor = build_editor(multibuffer.clone(), window, cx);
17064        let snapshot = editor.snapshot(window, cx);
17065        editor.begin_selection(
17066            Point::new(1, 3).to_display_point(&snapshot),
17067            false,
17068            1,
17069            window,
17070            cx,
17071        );
17072        assert_eq!(
17073            editor.selections.ranges(&editor.display_snapshot(cx)),
17074            [Point::new(1, 3)..Point::new(1, 3)]
17075        );
17076        editor
17077    });
17078
17079    multibuffer.update(cx, |multibuffer, cx| {
17080        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17081    });
17082    _ = editor.update(cx, |editor, window, cx| {
17083        assert_eq!(
17084            editor.selections.ranges(&editor.display_snapshot(cx)),
17085            [Point::new(0, 0)..Point::new(0, 0)]
17086        );
17087
17088        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17090        assert_eq!(
17091            editor.selections.ranges(&editor.display_snapshot(cx)),
17092            [Point::new(0, 3)..Point::new(0, 3)]
17093        );
17094        assert!(editor.selections.pending_anchor().is_some());
17095    });
17096}
17097
17098#[gpui::test]
17099async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17100    init_test(cx, |_| {});
17101
17102    let language = Arc::new(
17103        Language::new(
17104            LanguageConfig {
17105                brackets: BracketPairConfig {
17106                    pairs: vec![
17107                        BracketPair {
17108                            start: "{".to_string(),
17109                            end: "}".to_string(),
17110                            close: true,
17111                            surround: true,
17112                            newline: true,
17113                        },
17114                        BracketPair {
17115                            start: "/* ".to_string(),
17116                            end: " */".to_string(),
17117                            close: true,
17118                            surround: true,
17119                            newline: true,
17120                        },
17121                    ],
17122                    ..Default::default()
17123                },
17124                ..Default::default()
17125            },
17126            Some(tree_sitter_rust::LANGUAGE.into()),
17127        )
17128        .with_indents_query("")
17129        .unwrap(),
17130    );
17131
17132    let text = concat!(
17133        "{   }\n",     //
17134        "  x\n",       //
17135        "  /*   */\n", //
17136        "x\n",         //
17137        "{{} }\n",     //
17138    );
17139
17140    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17141    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17142    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17143    editor
17144        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17145        .await;
17146
17147    editor.update_in(cx, |editor, window, cx| {
17148        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17149            s.select_display_ranges([
17150                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17151                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17152                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17153            ])
17154        });
17155        editor.newline(&Newline, window, cx);
17156
17157        assert_eq!(
17158            editor.buffer().read(cx).read(cx).text(),
17159            concat!(
17160                "{ \n",    // Suppress rustfmt
17161                "\n",      //
17162                "}\n",     //
17163                "  x\n",   //
17164                "  /* \n", //
17165                "  \n",    //
17166                "  */\n",  //
17167                "x\n",     //
17168                "{{} \n",  //
17169                "}\n",     //
17170            )
17171        );
17172    });
17173}
17174
17175#[gpui::test]
17176fn test_highlighted_ranges(cx: &mut TestAppContext) {
17177    init_test(cx, |_| {});
17178
17179    let editor = cx.add_window(|window, cx| {
17180        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17181        build_editor(buffer, window, cx)
17182    });
17183
17184    _ = editor.update(cx, |editor, window, cx| {
17185        struct Type1;
17186        struct Type2;
17187
17188        let buffer = editor.buffer.read(cx).snapshot(cx);
17189
17190        let anchor_range =
17191            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17192
17193        editor.highlight_background::<Type1>(
17194            &[
17195                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17196                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17197                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17198                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17199            ],
17200            |_, _| Hsla::red(),
17201            cx,
17202        );
17203        editor.highlight_background::<Type2>(
17204            &[
17205                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17206                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17207                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17208                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17209            ],
17210            |_, _| Hsla::green(),
17211            cx,
17212        );
17213
17214        let snapshot = editor.snapshot(window, cx);
17215        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17216            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17217            &snapshot,
17218            cx.theme(),
17219        );
17220        assert_eq!(
17221            highlighted_ranges,
17222            &[
17223                (
17224                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17225                    Hsla::green(),
17226                ),
17227                (
17228                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17229                    Hsla::red(),
17230                ),
17231                (
17232                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17233                    Hsla::green(),
17234                ),
17235                (
17236                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17237                    Hsla::red(),
17238                ),
17239            ]
17240        );
17241        assert_eq!(
17242            editor.sorted_background_highlights_in_range(
17243                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17244                &snapshot,
17245                cx.theme(),
17246            ),
17247            &[(
17248                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17249                Hsla::red(),
17250            )]
17251        );
17252    });
17253}
17254
17255#[gpui::test]
17256async fn test_following(cx: &mut TestAppContext) {
17257    init_test(cx, |_| {});
17258
17259    let fs = FakeFs::new(cx.executor());
17260    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17261
17262    let buffer = project.update(cx, |project, cx| {
17263        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17264        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17265    });
17266    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17267    let follower = cx.update(|cx| {
17268        cx.open_window(
17269            WindowOptions {
17270                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17271                    gpui::Point::new(px(0.), px(0.)),
17272                    gpui::Point::new(px(10.), px(80.)),
17273                ))),
17274                ..Default::default()
17275            },
17276            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17277        )
17278        .unwrap()
17279    });
17280
17281    let is_still_following = Rc::new(RefCell::new(true));
17282    let follower_edit_event_count = Rc::new(RefCell::new(0));
17283    let pending_update = Rc::new(RefCell::new(None));
17284    let leader_entity = leader.root(cx).unwrap();
17285    let follower_entity = follower.root(cx).unwrap();
17286    _ = follower.update(cx, {
17287        let update = pending_update.clone();
17288        let is_still_following = is_still_following.clone();
17289        let follower_edit_event_count = follower_edit_event_count.clone();
17290        |_, window, cx| {
17291            cx.subscribe_in(
17292                &leader_entity,
17293                window,
17294                move |_, leader, event, window, cx| {
17295                    leader.read(cx).add_event_to_update_proto(
17296                        event,
17297                        &mut update.borrow_mut(),
17298                        window,
17299                        cx,
17300                    );
17301                },
17302            )
17303            .detach();
17304
17305            cx.subscribe_in(
17306                &follower_entity,
17307                window,
17308                move |_, _, event: &EditorEvent, _window, _cx| {
17309                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17310                        *is_still_following.borrow_mut() = false;
17311                    }
17312
17313                    if let EditorEvent::BufferEdited = event {
17314                        *follower_edit_event_count.borrow_mut() += 1;
17315                    }
17316                },
17317            )
17318            .detach();
17319        }
17320    });
17321
17322    // Update the selections only
17323    _ = leader.update(cx, |leader, window, cx| {
17324        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17325            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17326        });
17327    });
17328    follower
17329        .update(cx, |follower, window, cx| {
17330            follower.apply_update_proto(
17331                &project,
17332                pending_update.borrow_mut().take().unwrap(),
17333                window,
17334                cx,
17335            )
17336        })
17337        .unwrap()
17338        .await
17339        .unwrap();
17340    _ = follower.update(cx, |follower, _, cx| {
17341        assert_eq!(
17342            follower.selections.ranges(&follower.display_snapshot(cx)),
17343            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17344        );
17345    });
17346    assert!(*is_still_following.borrow());
17347    assert_eq!(*follower_edit_event_count.borrow(), 0);
17348
17349    // Update the scroll position only
17350    _ = leader.update(cx, |leader, window, cx| {
17351        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17352    });
17353    follower
17354        .update(cx, |follower, window, cx| {
17355            follower.apply_update_proto(
17356                &project,
17357                pending_update.borrow_mut().take().unwrap(),
17358                window,
17359                cx,
17360            )
17361        })
17362        .unwrap()
17363        .await
17364        .unwrap();
17365    assert_eq!(
17366        follower
17367            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17368            .unwrap(),
17369        gpui::Point::new(1.5, 3.5)
17370    );
17371    assert!(*is_still_following.borrow());
17372    assert_eq!(*follower_edit_event_count.borrow(), 0);
17373
17374    // Update the selections and scroll position. The follower's scroll position is updated
17375    // via autoscroll, not via the leader's exact scroll position.
17376    _ = leader.update(cx, |leader, window, cx| {
17377        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17378            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17379        });
17380        leader.request_autoscroll(Autoscroll::newest(), cx);
17381        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17382    });
17383    follower
17384        .update(cx, |follower, window, cx| {
17385            follower.apply_update_proto(
17386                &project,
17387                pending_update.borrow_mut().take().unwrap(),
17388                window,
17389                cx,
17390            )
17391        })
17392        .unwrap()
17393        .await
17394        .unwrap();
17395    _ = follower.update(cx, |follower, _, cx| {
17396        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17397        assert_eq!(
17398            follower.selections.ranges(&follower.display_snapshot(cx)),
17399            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17400        );
17401    });
17402    assert!(*is_still_following.borrow());
17403
17404    // Creating a pending selection that precedes another selection
17405    _ = leader.update(cx, |leader, window, cx| {
17406        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17407            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17408        });
17409        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17410    });
17411    follower
17412        .update(cx, |follower, window, cx| {
17413            follower.apply_update_proto(
17414                &project,
17415                pending_update.borrow_mut().take().unwrap(),
17416                window,
17417                cx,
17418            )
17419        })
17420        .unwrap()
17421        .await
17422        .unwrap();
17423    _ = follower.update(cx, |follower, _, cx| {
17424        assert_eq!(
17425            follower.selections.ranges(&follower.display_snapshot(cx)),
17426            vec![
17427                MultiBufferOffset(0)..MultiBufferOffset(0),
17428                MultiBufferOffset(1)..MultiBufferOffset(1)
17429            ]
17430        );
17431    });
17432    assert!(*is_still_following.borrow());
17433
17434    // Extend the pending selection so that it surrounds another selection
17435    _ = leader.update(cx, |leader, window, cx| {
17436        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17437    });
17438    follower
17439        .update(cx, |follower, window, cx| {
17440            follower.apply_update_proto(
17441                &project,
17442                pending_update.borrow_mut().take().unwrap(),
17443                window,
17444                cx,
17445            )
17446        })
17447        .unwrap()
17448        .await
17449        .unwrap();
17450    _ = follower.update(cx, |follower, _, cx| {
17451        assert_eq!(
17452            follower.selections.ranges(&follower.display_snapshot(cx)),
17453            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17454        );
17455    });
17456
17457    // Scrolling locally breaks the follow
17458    _ = follower.update(cx, |follower, window, cx| {
17459        let top_anchor = follower
17460            .buffer()
17461            .read(cx)
17462            .read(cx)
17463            .anchor_after(MultiBufferOffset(0));
17464        follower.set_scroll_anchor(
17465            ScrollAnchor {
17466                anchor: top_anchor,
17467                offset: gpui::Point::new(0.0, 0.5),
17468            },
17469            window,
17470            cx,
17471        );
17472    });
17473    assert!(!(*is_still_following.borrow()));
17474}
17475
17476#[gpui::test]
17477async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17478    init_test(cx, |_| {});
17479
17480    let fs = FakeFs::new(cx.executor());
17481    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17482    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17483    let pane = workspace
17484        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17485        .unwrap();
17486
17487    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17488
17489    let leader = pane.update_in(cx, |_, window, cx| {
17490        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17491        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17492    });
17493
17494    // Start following the editor when it has no excerpts.
17495    let mut state_message =
17496        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17497    let workspace_entity = workspace.root(cx).unwrap();
17498    let follower_1 = cx
17499        .update_window(*workspace.deref(), |_, window, cx| {
17500            Editor::from_state_proto(
17501                workspace_entity,
17502                ViewId {
17503                    creator: CollaboratorId::PeerId(PeerId::default()),
17504                    id: 0,
17505                },
17506                &mut state_message,
17507                window,
17508                cx,
17509            )
17510        })
17511        .unwrap()
17512        .unwrap()
17513        .await
17514        .unwrap();
17515
17516    let update_message = Rc::new(RefCell::new(None));
17517    follower_1.update_in(cx, {
17518        let update = update_message.clone();
17519        |_, window, cx| {
17520            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17521                leader.read(cx).add_event_to_update_proto(
17522                    event,
17523                    &mut update.borrow_mut(),
17524                    window,
17525                    cx,
17526                );
17527            })
17528            .detach();
17529        }
17530    });
17531
17532    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17533        (
17534            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17535            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17536        )
17537    });
17538
17539    // Insert some excerpts.
17540    leader.update(cx, |leader, cx| {
17541        leader.buffer.update(cx, |multibuffer, cx| {
17542            multibuffer.set_excerpts_for_path(
17543                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17544                buffer_1.clone(),
17545                vec![
17546                    Point::row_range(0..3),
17547                    Point::row_range(1..6),
17548                    Point::row_range(12..15),
17549                ],
17550                0,
17551                cx,
17552            );
17553            multibuffer.set_excerpts_for_path(
17554                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17555                buffer_2.clone(),
17556                vec![Point::row_range(0..6), Point::row_range(8..12)],
17557                0,
17558                cx,
17559            );
17560        });
17561    });
17562
17563    // Apply the update of adding the excerpts.
17564    follower_1
17565        .update_in(cx, |follower, window, cx| {
17566            follower.apply_update_proto(
17567                &project,
17568                update_message.borrow().clone().unwrap(),
17569                window,
17570                cx,
17571            )
17572        })
17573        .await
17574        .unwrap();
17575    assert_eq!(
17576        follower_1.update(cx, |editor, cx| editor.text(cx)),
17577        leader.update(cx, |editor, cx| editor.text(cx))
17578    );
17579    update_message.borrow_mut().take();
17580
17581    // Start following separately after it already has excerpts.
17582    let mut state_message =
17583        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17584    let workspace_entity = workspace.root(cx).unwrap();
17585    let follower_2 = cx
17586        .update_window(*workspace.deref(), |_, window, cx| {
17587            Editor::from_state_proto(
17588                workspace_entity,
17589                ViewId {
17590                    creator: CollaboratorId::PeerId(PeerId::default()),
17591                    id: 0,
17592                },
17593                &mut state_message,
17594                window,
17595                cx,
17596            )
17597        })
17598        .unwrap()
17599        .unwrap()
17600        .await
17601        .unwrap();
17602    assert_eq!(
17603        follower_2.update(cx, |editor, cx| editor.text(cx)),
17604        leader.update(cx, |editor, cx| editor.text(cx))
17605    );
17606
17607    // Remove some excerpts.
17608    leader.update(cx, |leader, cx| {
17609        leader.buffer.update(cx, |multibuffer, cx| {
17610            let excerpt_ids = multibuffer.excerpt_ids();
17611            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17612            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17613        });
17614    });
17615
17616    // Apply the update of removing the excerpts.
17617    follower_1
17618        .update_in(cx, |follower, window, cx| {
17619            follower.apply_update_proto(
17620                &project,
17621                update_message.borrow().clone().unwrap(),
17622                window,
17623                cx,
17624            )
17625        })
17626        .await
17627        .unwrap();
17628    follower_2
17629        .update_in(cx, |follower, window, cx| {
17630            follower.apply_update_proto(
17631                &project,
17632                update_message.borrow().clone().unwrap(),
17633                window,
17634                cx,
17635            )
17636        })
17637        .await
17638        .unwrap();
17639    update_message.borrow_mut().take();
17640    assert_eq!(
17641        follower_1.update(cx, |editor, cx| editor.text(cx)),
17642        leader.update(cx, |editor, cx| editor.text(cx))
17643    );
17644}
17645
17646#[gpui::test]
17647async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17648    init_test(cx, |_| {});
17649
17650    let mut cx = EditorTestContext::new(cx).await;
17651    let lsp_store =
17652        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17653
17654    cx.set_state(indoc! {"
17655        ˇfn func(abc def: i32) -> u32 {
17656        }
17657    "});
17658
17659    cx.update(|_, cx| {
17660        lsp_store.update(cx, |lsp_store, cx| {
17661            lsp_store
17662                .update_diagnostics(
17663                    LanguageServerId(0),
17664                    lsp::PublishDiagnosticsParams {
17665                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17666                        version: None,
17667                        diagnostics: vec![
17668                            lsp::Diagnostic {
17669                                range: lsp::Range::new(
17670                                    lsp::Position::new(0, 11),
17671                                    lsp::Position::new(0, 12),
17672                                ),
17673                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17674                                ..Default::default()
17675                            },
17676                            lsp::Diagnostic {
17677                                range: lsp::Range::new(
17678                                    lsp::Position::new(0, 12),
17679                                    lsp::Position::new(0, 15),
17680                                ),
17681                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17682                                ..Default::default()
17683                            },
17684                            lsp::Diagnostic {
17685                                range: lsp::Range::new(
17686                                    lsp::Position::new(0, 25),
17687                                    lsp::Position::new(0, 28),
17688                                ),
17689                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17690                                ..Default::default()
17691                            },
17692                        ],
17693                    },
17694                    None,
17695                    DiagnosticSourceKind::Pushed,
17696                    &[],
17697                    cx,
17698                )
17699                .unwrap()
17700        });
17701    });
17702
17703    executor.run_until_parked();
17704
17705    cx.update_editor(|editor, window, cx| {
17706        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17707    });
17708
17709    cx.assert_editor_state(indoc! {"
17710        fn func(abc def: i32) -> ˇu32 {
17711        }
17712    "});
17713
17714    cx.update_editor(|editor, window, cx| {
17715        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17716    });
17717
17718    cx.assert_editor_state(indoc! {"
17719        fn func(abc ˇdef: i32) -> u32 {
17720        }
17721    "});
17722
17723    cx.update_editor(|editor, window, cx| {
17724        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17725    });
17726
17727    cx.assert_editor_state(indoc! {"
17728        fn func(abcˇ def: i32) -> u32 {
17729        }
17730    "});
17731
17732    cx.update_editor(|editor, window, cx| {
17733        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17734    });
17735
17736    cx.assert_editor_state(indoc! {"
17737        fn func(abc def: i32) -> ˇu32 {
17738        }
17739    "});
17740}
17741
17742#[gpui::test]
17743async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17744    init_test(cx, |_| {});
17745
17746    let mut cx = EditorTestContext::new(cx).await;
17747
17748    let diff_base = r#"
17749        use some::mod;
17750
17751        const A: u32 = 42;
17752
17753        fn main() {
17754            println!("hello");
17755
17756            println!("world");
17757        }
17758        "#
17759    .unindent();
17760
17761    // Edits are modified, removed, modified, added
17762    cx.set_state(
17763        &r#"
17764        use some::modified;
17765
17766        ˇ
17767        fn main() {
17768            println!("hello there");
17769
17770            println!("around the");
17771            println!("world");
17772        }
17773        "#
17774        .unindent(),
17775    );
17776
17777    cx.set_head_text(&diff_base);
17778    executor.run_until_parked();
17779
17780    cx.update_editor(|editor, window, cx| {
17781        //Wrap around the bottom of the buffer
17782        for _ in 0..3 {
17783            editor.go_to_next_hunk(&GoToHunk, window, cx);
17784        }
17785    });
17786
17787    cx.assert_editor_state(
17788        &r#"
17789        ˇuse some::modified;
17790
17791
17792        fn main() {
17793            println!("hello there");
17794
17795            println!("around the");
17796            println!("world");
17797        }
17798        "#
17799        .unindent(),
17800    );
17801
17802    cx.update_editor(|editor, window, cx| {
17803        //Wrap around the top of the buffer
17804        for _ in 0..2 {
17805            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17806        }
17807    });
17808
17809    cx.assert_editor_state(
17810        &r#"
17811        use some::modified;
17812
17813
17814        fn main() {
17815        ˇ    println!("hello there");
17816
17817            println!("around the");
17818            println!("world");
17819        }
17820        "#
17821        .unindent(),
17822    );
17823
17824    cx.update_editor(|editor, window, cx| {
17825        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17826    });
17827
17828    cx.assert_editor_state(
17829        &r#"
17830        use some::modified;
17831
17832        ˇ
17833        fn main() {
17834            println!("hello there");
17835
17836            println!("around the");
17837            println!("world");
17838        }
17839        "#
17840        .unindent(),
17841    );
17842
17843    cx.update_editor(|editor, window, cx| {
17844        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17845    });
17846
17847    cx.assert_editor_state(
17848        &r#"
17849        ˇuse some::modified;
17850
17851
17852        fn main() {
17853            println!("hello there");
17854
17855            println!("around the");
17856            println!("world");
17857        }
17858        "#
17859        .unindent(),
17860    );
17861
17862    cx.update_editor(|editor, window, cx| {
17863        for _ in 0..2 {
17864            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17865        }
17866    });
17867
17868    cx.assert_editor_state(
17869        &r#"
17870        use some::modified;
17871
17872
17873        fn main() {
17874        ˇ    println!("hello there");
17875
17876            println!("around the");
17877            println!("world");
17878        }
17879        "#
17880        .unindent(),
17881    );
17882
17883    cx.update_editor(|editor, window, cx| {
17884        editor.fold(&Fold, window, cx);
17885    });
17886
17887    cx.update_editor(|editor, window, cx| {
17888        editor.go_to_next_hunk(&GoToHunk, window, cx);
17889    });
17890
17891    cx.assert_editor_state(
17892        &r#"
17893        ˇuse some::modified;
17894
17895
17896        fn main() {
17897            println!("hello there");
17898
17899            println!("around the");
17900            println!("world");
17901        }
17902        "#
17903        .unindent(),
17904    );
17905}
17906
17907#[test]
17908fn test_split_words() {
17909    fn split(text: &str) -> Vec<&str> {
17910        split_words(text).collect()
17911    }
17912
17913    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17914    assert_eq!(split("hello_world"), &["hello_", "world"]);
17915    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17916    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17917    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17918    assert_eq!(split("helloworld"), &["helloworld"]);
17919
17920    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17921}
17922
17923#[test]
17924fn test_split_words_for_snippet_prefix() {
17925    fn split(text: &str) -> Vec<&str> {
17926        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17927    }
17928
17929    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17930    assert_eq!(split("hello_world"), &["hello_world"]);
17931    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17932    assert_eq!(split("Hello_World"), &["Hello_World"]);
17933    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17934    assert_eq!(split("helloworld"), &["helloworld"]);
17935    assert_eq!(
17936        split("this@is!@#$^many   . symbols"),
17937        &[
17938            "symbols",
17939            " symbols",
17940            ". symbols",
17941            " . symbols",
17942            "  . symbols",
17943            "   . symbols",
17944            "many   . symbols",
17945            "^many   . symbols",
17946            "$^many   . symbols",
17947            "#$^many   . symbols",
17948            "@#$^many   . symbols",
17949            "!@#$^many   . symbols",
17950            "is!@#$^many   . symbols",
17951            "@is!@#$^many   . symbols",
17952            "this@is!@#$^many   . symbols",
17953        ],
17954    );
17955    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17956}
17957
17958#[gpui::test]
17959async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17960    init_test(cx, |_| {});
17961
17962    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17963
17964    #[track_caller]
17965    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17966        let _state_context = cx.set_state(before);
17967        cx.run_until_parked();
17968        cx.update_editor(|editor, window, cx| {
17969            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17970        });
17971        cx.run_until_parked();
17972        cx.assert_editor_state(after);
17973    }
17974
17975    // Outside bracket jumps to outside of matching bracket
17976    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17977    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17978
17979    // Inside bracket jumps to inside 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    // When outside a bracket and inside, favor jumping to the inside bracket
17984    assert(
17985        "console.log('foo', [1, 2, 3]ˇ);",
17986        "console.log('foo', ˇ[1, 2, 3]);",
17987        &mut cx,
17988    );
17989    assert(
17990        "console.log(ˇ'foo', [1, 2, 3]);",
17991        "console.log('foo'ˇ, [1, 2, 3]);",
17992        &mut cx,
17993    );
17994
17995    // Bias forward if two options are equally likely
17996    assert(
17997        "let result = curried_fun()ˇ();",
17998        "let result = curried_fun()()ˇ;",
17999        &mut cx,
18000    );
18001
18002    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18003    assert(
18004        indoc! {"
18005            function test() {
18006                console.log('test')ˇ
18007            }"},
18008        indoc! {"
18009            function test() {
18010                console.logˇ('test')
18011            }"},
18012        &mut cx,
18013    );
18014}
18015
18016#[gpui::test]
18017async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18018    init_test(cx, |_| {});
18019    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18020    language_registry.add(markdown_lang());
18021    language_registry.add(rust_lang());
18022    let buffer = cx.new(|cx| {
18023        let mut buffer = language::Buffer::local(
18024            indoc! {"
18025            ```rs
18026            impl Worktree {
18027                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18028                }
18029            }
18030            ```
18031        "},
18032            cx,
18033        );
18034        buffer.set_language_registry(language_registry.clone());
18035        buffer.set_language(Some(markdown_lang()), cx);
18036        buffer
18037    });
18038    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18039    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18040    cx.executor().run_until_parked();
18041    _ = editor.update(cx, |editor, window, cx| {
18042        // Case 1: Test outer enclosing brackets
18043        select_ranges(
18044            editor,
18045            &indoc! {"
18046                ```rs
18047                impl Worktree {
18048                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18049                    }
1805018051                ```
18052            "},
18053            window,
18054            cx,
18055        );
18056        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18057        assert_text_with_selections(
18058            editor,
18059            &indoc! {"
18060                ```rs
18061                impl Worktree ˇ{
18062                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18063                    }
18064                }
18065                ```
18066            "},
18067            cx,
18068        );
18069        // Case 2: Test inner enclosing brackets
18070        select_ranges(
18071            editor,
18072            &indoc! {"
18073                ```rs
18074                impl Worktree {
18075                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1807618077                }
18078                ```
18079            "},
18080            window,
18081            cx,
18082        );
18083        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18084        assert_text_with_selections(
18085            editor,
18086            &indoc! {"
18087                ```rs
18088                impl Worktree {
18089                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18090                    }
18091                }
18092                ```
18093            "},
18094            cx,
18095        );
18096    });
18097}
18098
18099#[gpui::test]
18100async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18101    init_test(cx, |_| {});
18102
18103    let fs = FakeFs::new(cx.executor());
18104    fs.insert_tree(
18105        path!("/a"),
18106        json!({
18107            "main.rs": "fn main() { let a = 5; }",
18108            "other.rs": "// Test file",
18109        }),
18110    )
18111    .await;
18112    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18113
18114    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18115    language_registry.add(Arc::new(Language::new(
18116        LanguageConfig {
18117            name: "Rust".into(),
18118            matcher: LanguageMatcher {
18119                path_suffixes: vec!["rs".to_string()],
18120                ..Default::default()
18121            },
18122            brackets: BracketPairConfig {
18123                pairs: vec![BracketPair {
18124                    start: "{".to_string(),
18125                    end: "}".to_string(),
18126                    close: true,
18127                    surround: true,
18128                    newline: true,
18129                }],
18130                disabled_scopes_by_bracket_ix: Vec::new(),
18131            },
18132            ..Default::default()
18133        },
18134        Some(tree_sitter_rust::LANGUAGE.into()),
18135    )));
18136    let mut fake_servers = language_registry.register_fake_lsp(
18137        "Rust",
18138        FakeLspAdapter {
18139            capabilities: lsp::ServerCapabilities {
18140                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18141                    first_trigger_character: "{".to_string(),
18142                    more_trigger_character: None,
18143                }),
18144                ..Default::default()
18145            },
18146            ..Default::default()
18147        },
18148    );
18149
18150    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18151
18152    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18153
18154    let worktree_id = workspace
18155        .update(cx, |workspace, _, cx| {
18156            workspace.project().update(cx, |project, cx| {
18157                project.worktrees(cx).next().unwrap().read(cx).id()
18158            })
18159        })
18160        .unwrap();
18161
18162    let buffer = project
18163        .update(cx, |project, cx| {
18164            project.open_local_buffer(path!("/a/main.rs"), cx)
18165        })
18166        .await
18167        .unwrap();
18168    let editor_handle = workspace
18169        .update(cx, |workspace, window, cx| {
18170            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18171        })
18172        .unwrap()
18173        .await
18174        .unwrap()
18175        .downcast::<Editor>()
18176        .unwrap();
18177
18178    cx.executor().start_waiting();
18179    let fake_server = fake_servers.next().await.unwrap();
18180
18181    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18182        |params, _| async move {
18183            assert_eq!(
18184                params.text_document_position.text_document.uri,
18185                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18186            );
18187            assert_eq!(
18188                params.text_document_position.position,
18189                lsp::Position::new(0, 21),
18190            );
18191
18192            Ok(Some(vec![lsp::TextEdit {
18193                new_text: "]".to_string(),
18194                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18195            }]))
18196        },
18197    );
18198
18199    editor_handle.update_in(cx, |editor, window, cx| {
18200        window.focus(&editor.focus_handle(cx), cx);
18201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18202            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18203        });
18204        editor.handle_input("{", window, cx);
18205    });
18206
18207    cx.executor().run_until_parked();
18208
18209    buffer.update(cx, |buffer, _| {
18210        assert_eq!(
18211            buffer.text(),
18212            "fn main() { let a = {5}; }",
18213            "No extra braces from on type formatting should appear in the buffer"
18214        )
18215    });
18216}
18217
18218#[gpui::test(iterations = 20, seeds(31))]
18219async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18220    init_test(cx, |_| {});
18221
18222    let mut cx = EditorLspTestContext::new_rust(
18223        lsp::ServerCapabilities {
18224            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18225                first_trigger_character: ".".to_string(),
18226                more_trigger_character: None,
18227            }),
18228            ..Default::default()
18229        },
18230        cx,
18231    )
18232    .await;
18233
18234    cx.update_buffer(|buffer, _| {
18235        // This causes autoindent to be async.
18236        buffer.set_sync_parse_timeout(None)
18237    });
18238
18239    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18240    cx.simulate_keystroke("\n");
18241    cx.run_until_parked();
18242
18243    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18244    let mut request =
18245        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18246            let buffer_cloned = buffer_cloned.clone();
18247            async move {
18248                buffer_cloned.update(&mut cx, |buffer, _| {
18249                    assert_eq!(
18250                        buffer.text(),
18251                        "fn c() {\n    d()\n        .\n}\n",
18252                        "OnTypeFormatting should triggered after autoindent applied"
18253                    )
18254                })?;
18255
18256                Ok(Some(vec![]))
18257            }
18258        });
18259
18260    cx.simulate_keystroke(".");
18261    cx.run_until_parked();
18262
18263    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18264    assert!(request.next().await.is_some());
18265    request.close();
18266    assert!(request.next().await.is_none());
18267}
18268
18269#[gpui::test]
18270async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18271    init_test(cx, |_| {});
18272
18273    let fs = FakeFs::new(cx.executor());
18274    fs.insert_tree(
18275        path!("/a"),
18276        json!({
18277            "main.rs": "fn main() { let a = 5; }",
18278            "other.rs": "// Test file",
18279        }),
18280    )
18281    .await;
18282
18283    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18284
18285    let server_restarts = Arc::new(AtomicUsize::new(0));
18286    let closure_restarts = Arc::clone(&server_restarts);
18287    let language_server_name = "test language server";
18288    let language_name: LanguageName = "Rust".into();
18289
18290    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18291    language_registry.add(Arc::new(Language::new(
18292        LanguageConfig {
18293            name: language_name.clone(),
18294            matcher: LanguageMatcher {
18295                path_suffixes: vec!["rs".to_string()],
18296                ..Default::default()
18297            },
18298            ..Default::default()
18299        },
18300        Some(tree_sitter_rust::LANGUAGE.into()),
18301    )));
18302    let mut fake_servers = language_registry.register_fake_lsp(
18303        "Rust",
18304        FakeLspAdapter {
18305            name: language_server_name,
18306            initialization_options: Some(json!({
18307                "testOptionValue": true
18308            })),
18309            initializer: Some(Box::new(move |fake_server| {
18310                let task_restarts = Arc::clone(&closure_restarts);
18311                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18312                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18313                    futures::future::ready(Ok(()))
18314                });
18315            })),
18316            ..Default::default()
18317        },
18318    );
18319
18320    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18321    let _buffer = project
18322        .update(cx, |project, cx| {
18323            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18324        })
18325        .await
18326        .unwrap();
18327    let _fake_server = fake_servers.next().await.unwrap();
18328    update_test_language_settings(cx, |language_settings| {
18329        language_settings.languages.0.insert(
18330            language_name.clone().0,
18331            LanguageSettingsContent {
18332                tab_size: NonZeroU32::new(8),
18333                ..Default::default()
18334            },
18335        );
18336    });
18337    cx.executor().run_until_parked();
18338    assert_eq!(
18339        server_restarts.load(atomic::Ordering::Acquire),
18340        0,
18341        "Should not restart LSP server on an unrelated change"
18342    );
18343
18344    update_test_project_settings(cx, |project_settings| {
18345        project_settings.lsp.0.insert(
18346            "Some other server name".into(),
18347            LspSettings {
18348                binary: None,
18349                settings: None,
18350                initialization_options: Some(json!({
18351                    "some other init value": false
18352                })),
18353                enable_lsp_tasks: false,
18354                fetch: None,
18355            },
18356        );
18357    });
18358    cx.executor().run_until_parked();
18359    assert_eq!(
18360        server_restarts.load(atomic::Ordering::Acquire),
18361        0,
18362        "Should not restart LSP server on an unrelated LSP settings change"
18363    );
18364
18365    update_test_project_settings(cx, |project_settings| {
18366        project_settings.lsp.0.insert(
18367            language_server_name.into(),
18368            LspSettings {
18369                binary: None,
18370                settings: None,
18371                initialization_options: Some(json!({
18372                    "anotherInitValue": false
18373                })),
18374                enable_lsp_tasks: false,
18375                fetch: None,
18376            },
18377        );
18378    });
18379    cx.executor().run_until_parked();
18380    assert_eq!(
18381        server_restarts.load(atomic::Ordering::Acquire),
18382        1,
18383        "Should restart LSP server on a related LSP settings change"
18384    );
18385
18386    update_test_project_settings(cx, |project_settings| {
18387        project_settings.lsp.0.insert(
18388            language_server_name.into(),
18389            LspSettings {
18390                binary: None,
18391                settings: None,
18392                initialization_options: Some(json!({
18393                    "anotherInitValue": false
18394                })),
18395                enable_lsp_tasks: false,
18396                fetch: None,
18397            },
18398        );
18399    });
18400    cx.executor().run_until_parked();
18401    assert_eq!(
18402        server_restarts.load(atomic::Ordering::Acquire),
18403        1,
18404        "Should not restart LSP server on a related LSP settings change that is the same"
18405    );
18406
18407    update_test_project_settings(cx, |project_settings| {
18408        project_settings.lsp.0.insert(
18409            language_server_name.into(),
18410            LspSettings {
18411                binary: None,
18412                settings: None,
18413                initialization_options: None,
18414                enable_lsp_tasks: false,
18415                fetch: None,
18416            },
18417        );
18418    });
18419    cx.executor().run_until_parked();
18420    assert_eq!(
18421        server_restarts.load(atomic::Ordering::Acquire),
18422        2,
18423        "Should restart LSP server on another related LSP settings change"
18424    );
18425}
18426
18427#[gpui::test]
18428async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18429    init_test(cx, |_| {});
18430
18431    let mut cx = EditorLspTestContext::new_rust(
18432        lsp::ServerCapabilities {
18433            completion_provider: Some(lsp::CompletionOptions {
18434                trigger_characters: Some(vec![".".to_string()]),
18435                resolve_provider: Some(true),
18436                ..Default::default()
18437            }),
18438            ..Default::default()
18439        },
18440        cx,
18441    )
18442    .await;
18443
18444    cx.set_state("fn main() { let a = 2ˇ; }");
18445    cx.simulate_keystroke(".");
18446    let completion_item = lsp::CompletionItem {
18447        label: "some".into(),
18448        kind: Some(lsp::CompletionItemKind::SNIPPET),
18449        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18450        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18451            kind: lsp::MarkupKind::Markdown,
18452            value: "```rust\nSome(2)\n```".to_string(),
18453        })),
18454        deprecated: Some(false),
18455        sort_text: Some("fffffff2".to_string()),
18456        filter_text: Some("some".to_string()),
18457        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18458        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18459            range: lsp::Range {
18460                start: lsp::Position {
18461                    line: 0,
18462                    character: 22,
18463                },
18464                end: lsp::Position {
18465                    line: 0,
18466                    character: 22,
18467                },
18468            },
18469            new_text: "Some(2)".to_string(),
18470        })),
18471        additional_text_edits: Some(vec![lsp::TextEdit {
18472            range: lsp::Range {
18473                start: lsp::Position {
18474                    line: 0,
18475                    character: 20,
18476                },
18477                end: lsp::Position {
18478                    line: 0,
18479                    character: 22,
18480                },
18481            },
18482            new_text: "".to_string(),
18483        }]),
18484        ..Default::default()
18485    };
18486
18487    let closure_completion_item = completion_item.clone();
18488    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18489        let task_completion_item = closure_completion_item.clone();
18490        async move {
18491            Ok(Some(lsp::CompletionResponse::Array(vec![
18492                task_completion_item,
18493            ])))
18494        }
18495    });
18496
18497    request.next().await;
18498
18499    cx.condition(|editor, _| editor.context_menu_visible())
18500        .await;
18501    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18502        editor
18503            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18504            .unwrap()
18505    });
18506    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18507
18508    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18509        let task_completion_item = completion_item.clone();
18510        async move { Ok(task_completion_item) }
18511    })
18512    .next()
18513    .await
18514    .unwrap();
18515    apply_additional_edits.await.unwrap();
18516    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18517}
18518
18519#[gpui::test]
18520async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18521    init_test(cx, |_| {});
18522
18523    let mut cx = EditorLspTestContext::new_rust(
18524        lsp::ServerCapabilities {
18525            completion_provider: Some(lsp::CompletionOptions {
18526                trigger_characters: Some(vec![".".to_string()]),
18527                resolve_provider: Some(true),
18528                ..Default::default()
18529            }),
18530            ..Default::default()
18531        },
18532        cx,
18533    )
18534    .await;
18535
18536    cx.set_state("fn main() { let a = 2ˇ; }");
18537    cx.simulate_keystroke(".");
18538
18539    let item1 = lsp::CompletionItem {
18540        label: "method id()".to_string(),
18541        filter_text: Some("id".to_string()),
18542        detail: None,
18543        documentation: None,
18544        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18545            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18546            new_text: ".id".to_string(),
18547        })),
18548        ..lsp::CompletionItem::default()
18549    };
18550
18551    let item2 = lsp::CompletionItem {
18552        label: "other".to_string(),
18553        filter_text: Some("other".to_string()),
18554        detail: None,
18555        documentation: None,
18556        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18557            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18558            new_text: ".other".to_string(),
18559        })),
18560        ..lsp::CompletionItem::default()
18561    };
18562
18563    let item1 = item1.clone();
18564    cx.set_request_handler::<lsp::request::Completion, _, _>({
18565        let item1 = item1.clone();
18566        move |_, _, _| {
18567            let item1 = item1.clone();
18568            let item2 = item2.clone();
18569            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18570        }
18571    })
18572    .next()
18573    .await;
18574
18575    cx.condition(|editor, _| editor.context_menu_visible())
18576        .await;
18577    cx.update_editor(|editor, _, _| {
18578        let context_menu = editor.context_menu.borrow_mut();
18579        let context_menu = context_menu
18580            .as_ref()
18581            .expect("Should have the context menu deployed");
18582        match context_menu {
18583            CodeContextMenu::Completions(completions_menu) => {
18584                let completions = completions_menu.completions.borrow_mut();
18585                assert_eq!(
18586                    completions
18587                        .iter()
18588                        .map(|completion| &completion.label.text)
18589                        .collect::<Vec<_>>(),
18590                    vec!["method id()", "other"]
18591                )
18592            }
18593            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18594        }
18595    });
18596
18597    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18598        let item1 = item1.clone();
18599        move |_, item_to_resolve, _| {
18600            let item1 = item1.clone();
18601            async move {
18602                if item1 == item_to_resolve {
18603                    Ok(lsp::CompletionItem {
18604                        label: "method id()".to_string(),
18605                        filter_text: Some("id".to_string()),
18606                        detail: Some("Now resolved!".to_string()),
18607                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18608                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18609                            range: lsp::Range::new(
18610                                lsp::Position::new(0, 22),
18611                                lsp::Position::new(0, 22),
18612                            ),
18613                            new_text: ".id".to_string(),
18614                        })),
18615                        ..lsp::CompletionItem::default()
18616                    })
18617                } else {
18618                    Ok(item_to_resolve)
18619                }
18620            }
18621        }
18622    })
18623    .next()
18624    .await
18625    .unwrap();
18626    cx.run_until_parked();
18627
18628    cx.update_editor(|editor, window, cx| {
18629        editor.context_menu_next(&Default::default(), window, cx);
18630    });
18631
18632    cx.update_editor(|editor, _, _| {
18633        let context_menu = editor.context_menu.borrow_mut();
18634        let context_menu = context_menu
18635            .as_ref()
18636            .expect("Should have the context menu deployed");
18637        match context_menu {
18638            CodeContextMenu::Completions(completions_menu) => {
18639                let completions = completions_menu.completions.borrow_mut();
18640                assert_eq!(
18641                    completions
18642                        .iter()
18643                        .map(|completion| &completion.label.text)
18644                        .collect::<Vec<_>>(),
18645                    vec!["method id() Now resolved!", "other"],
18646                    "Should update first completion label, but not second as the filter text did not match."
18647                );
18648            }
18649            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18650        }
18651    });
18652}
18653
18654#[gpui::test]
18655async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18656    init_test(cx, |_| {});
18657    let mut cx = EditorLspTestContext::new_rust(
18658        lsp::ServerCapabilities {
18659            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18660            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18661            completion_provider: Some(lsp::CompletionOptions {
18662                resolve_provider: Some(true),
18663                ..Default::default()
18664            }),
18665            ..Default::default()
18666        },
18667        cx,
18668    )
18669    .await;
18670    cx.set_state(indoc! {"
18671        struct TestStruct {
18672            field: i32
18673        }
18674
18675        fn mainˇ() {
18676            let unused_var = 42;
18677            let test_struct = TestStruct { field: 42 };
18678        }
18679    "});
18680    let symbol_range = cx.lsp_range(indoc! {"
18681        struct TestStruct {
18682            field: i32
18683        }
18684
18685        «fn main»() {
18686            let unused_var = 42;
18687            let test_struct = TestStruct { field: 42 };
18688        }
18689    "});
18690    let mut hover_requests =
18691        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18692            Ok(Some(lsp::Hover {
18693                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18694                    kind: lsp::MarkupKind::Markdown,
18695                    value: "Function documentation".to_string(),
18696                }),
18697                range: Some(symbol_range),
18698            }))
18699        });
18700
18701    // Case 1: Test that code action menu hide hover popover
18702    cx.dispatch_action(Hover);
18703    hover_requests.next().await;
18704    cx.condition(|editor, _| editor.hover_state.visible()).await;
18705    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18706        move |_, _, _| async move {
18707            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18708                lsp::CodeAction {
18709                    title: "Remove unused variable".to_string(),
18710                    kind: Some(CodeActionKind::QUICKFIX),
18711                    edit: Some(lsp::WorkspaceEdit {
18712                        changes: Some(
18713                            [(
18714                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18715                                vec![lsp::TextEdit {
18716                                    range: lsp::Range::new(
18717                                        lsp::Position::new(5, 4),
18718                                        lsp::Position::new(5, 27),
18719                                    ),
18720                                    new_text: "".to_string(),
18721                                }],
18722                            )]
18723                            .into_iter()
18724                            .collect(),
18725                        ),
18726                        ..Default::default()
18727                    }),
18728                    ..Default::default()
18729                },
18730            )]))
18731        },
18732    );
18733    cx.update_editor(|editor, window, cx| {
18734        editor.toggle_code_actions(
18735            &ToggleCodeActions {
18736                deployed_from: None,
18737                quick_launch: false,
18738            },
18739            window,
18740            cx,
18741        );
18742    });
18743    code_action_requests.next().await;
18744    cx.run_until_parked();
18745    cx.condition(|editor, _| editor.context_menu_visible())
18746        .await;
18747    cx.update_editor(|editor, _, _| {
18748        assert!(
18749            !editor.hover_state.visible(),
18750            "Hover popover should be hidden when code action menu is shown"
18751        );
18752        // Hide code actions
18753        editor.context_menu.take();
18754    });
18755
18756    // Case 2: Test that code completions hide hover popover
18757    cx.dispatch_action(Hover);
18758    hover_requests.next().await;
18759    cx.condition(|editor, _| editor.hover_state.visible()).await;
18760    let counter = Arc::new(AtomicUsize::new(0));
18761    let mut completion_requests =
18762        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18763            let counter = counter.clone();
18764            async move {
18765                counter.fetch_add(1, atomic::Ordering::Release);
18766                Ok(Some(lsp::CompletionResponse::Array(vec![
18767                    lsp::CompletionItem {
18768                        label: "main".into(),
18769                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18770                        detail: Some("() -> ()".to_string()),
18771                        ..Default::default()
18772                    },
18773                    lsp::CompletionItem {
18774                        label: "TestStruct".into(),
18775                        kind: Some(lsp::CompletionItemKind::STRUCT),
18776                        detail: Some("struct TestStruct".to_string()),
18777                        ..Default::default()
18778                    },
18779                ])))
18780            }
18781        });
18782    cx.update_editor(|editor, window, cx| {
18783        editor.show_completions(&ShowCompletions, window, cx);
18784    });
18785    completion_requests.next().await;
18786    cx.condition(|editor, _| editor.context_menu_visible())
18787        .await;
18788    cx.update_editor(|editor, _, _| {
18789        assert!(
18790            !editor.hover_state.visible(),
18791            "Hover popover should be hidden when completion menu is shown"
18792        );
18793    });
18794}
18795
18796#[gpui::test]
18797async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18798    init_test(cx, |_| {});
18799
18800    let mut cx = EditorLspTestContext::new_rust(
18801        lsp::ServerCapabilities {
18802            completion_provider: Some(lsp::CompletionOptions {
18803                trigger_characters: Some(vec![".".to_string()]),
18804                resolve_provider: Some(true),
18805                ..Default::default()
18806            }),
18807            ..Default::default()
18808        },
18809        cx,
18810    )
18811    .await;
18812
18813    cx.set_state("fn main() { let a = 2ˇ; }");
18814    cx.simulate_keystroke(".");
18815
18816    let unresolved_item_1 = lsp::CompletionItem {
18817        label: "id".to_string(),
18818        filter_text: Some("id".to_string()),
18819        detail: None,
18820        documentation: None,
18821        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18822            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18823            new_text: ".id".to_string(),
18824        })),
18825        ..lsp::CompletionItem::default()
18826    };
18827    let resolved_item_1 = lsp::CompletionItem {
18828        additional_text_edits: Some(vec![lsp::TextEdit {
18829            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18830            new_text: "!!".to_string(),
18831        }]),
18832        ..unresolved_item_1.clone()
18833    };
18834    let unresolved_item_2 = lsp::CompletionItem {
18835        label: "other".to_string(),
18836        filter_text: Some("other".to_string()),
18837        detail: None,
18838        documentation: None,
18839        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18840            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18841            new_text: ".other".to_string(),
18842        })),
18843        ..lsp::CompletionItem::default()
18844    };
18845    let resolved_item_2 = lsp::CompletionItem {
18846        additional_text_edits: Some(vec![lsp::TextEdit {
18847            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18848            new_text: "??".to_string(),
18849        }]),
18850        ..unresolved_item_2.clone()
18851    };
18852
18853    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18854    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18855    cx.lsp
18856        .server
18857        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18858            let unresolved_item_1 = unresolved_item_1.clone();
18859            let resolved_item_1 = resolved_item_1.clone();
18860            let unresolved_item_2 = unresolved_item_2.clone();
18861            let resolved_item_2 = resolved_item_2.clone();
18862            let resolve_requests_1 = resolve_requests_1.clone();
18863            let resolve_requests_2 = resolve_requests_2.clone();
18864            move |unresolved_request, _| {
18865                let unresolved_item_1 = unresolved_item_1.clone();
18866                let resolved_item_1 = resolved_item_1.clone();
18867                let unresolved_item_2 = unresolved_item_2.clone();
18868                let resolved_item_2 = resolved_item_2.clone();
18869                let resolve_requests_1 = resolve_requests_1.clone();
18870                let resolve_requests_2 = resolve_requests_2.clone();
18871                async move {
18872                    if unresolved_request == unresolved_item_1 {
18873                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18874                        Ok(resolved_item_1.clone())
18875                    } else if unresolved_request == unresolved_item_2 {
18876                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18877                        Ok(resolved_item_2.clone())
18878                    } else {
18879                        panic!("Unexpected completion item {unresolved_request:?}")
18880                    }
18881                }
18882            }
18883        })
18884        .detach();
18885
18886    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18887        let unresolved_item_1 = unresolved_item_1.clone();
18888        let unresolved_item_2 = unresolved_item_2.clone();
18889        async move {
18890            Ok(Some(lsp::CompletionResponse::Array(vec![
18891                unresolved_item_1,
18892                unresolved_item_2,
18893            ])))
18894        }
18895    })
18896    .next()
18897    .await;
18898
18899    cx.condition(|editor, _| editor.context_menu_visible())
18900        .await;
18901    cx.update_editor(|editor, _, _| {
18902        let context_menu = editor.context_menu.borrow_mut();
18903        let context_menu = context_menu
18904            .as_ref()
18905            .expect("Should have the context menu deployed");
18906        match context_menu {
18907            CodeContextMenu::Completions(completions_menu) => {
18908                let completions = completions_menu.completions.borrow_mut();
18909                assert_eq!(
18910                    completions
18911                        .iter()
18912                        .map(|completion| &completion.label.text)
18913                        .collect::<Vec<_>>(),
18914                    vec!["id", "other"]
18915                )
18916            }
18917            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18918        }
18919    });
18920    cx.run_until_parked();
18921
18922    cx.update_editor(|editor, window, cx| {
18923        editor.context_menu_next(&ContextMenuNext, window, cx);
18924    });
18925    cx.run_until_parked();
18926    cx.update_editor(|editor, window, cx| {
18927        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18928    });
18929    cx.run_until_parked();
18930    cx.update_editor(|editor, window, cx| {
18931        editor.context_menu_next(&ContextMenuNext, window, cx);
18932    });
18933    cx.run_until_parked();
18934    cx.update_editor(|editor, window, cx| {
18935        editor
18936            .compose_completion(&ComposeCompletion::default(), window, cx)
18937            .expect("No task returned")
18938    })
18939    .await
18940    .expect("Completion failed");
18941    cx.run_until_parked();
18942
18943    cx.update_editor(|editor, _, cx| {
18944        assert_eq!(
18945            resolve_requests_1.load(atomic::Ordering::Acquire),
18946            1,
18947            "Should always resolve once despite multiple selections"
18948        );
18949        assert_eq!(
18950            resolve_requests_2.load(atomic::Ordering::Acquire),
18951            1,
18952            "Should always resolve once after multiple selections and applying the completion"
18953        );
18954        assert_eq!(
18955            editor.text(cx),
18956            "fn main() { let a = ??.other; }",
18957            "Should use resolved data when applying the completion"
18958        );
18959    });
18960}
18961
18962#[gpui::test]
18963async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18964    init_test(cx, |_| {});
18965
18966    let item_0 = lsp::CompletionItem {
18967        label: "abs".into(),
18968        insert_text: Some("abs".into()),
18969        data: Some(json!({ "very": "special"})),
18970        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18971        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18972            lsp::InsertReplaceEdit {
18973                new_text: "abs".to_string(),
18974                insert: lsp::Range::default(),
18975                replace: lsp::Range::default(),
18976            },
18977        )),
18978        ..lsp::CompletionItem::default()
18979    };
18980    let items = iter::once(item_0.clone())
18981        .chain((11..51).map(|i| lsp::CompletionItem {
18982            label: format!("item_{}", i),
18983            insert_text: Some(format!("item_{}", i)),
18984            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18985            ..lsp::CompletionItem::default()
18986        }))
18987        .collect::<Vec<_>>();
18988
18989    let default_commit_characters = vec!["?".to_string()];
18990    let default_data = json!({ "default": "data"});
18991    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18992    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18993    let default_edit_range = lsp::Range {
18994        start: lsp::Position {
18995            line: 0,
18996            character: 5,
18997        },
18998        end: lsp::Position {
18999            line: 0,
19000            character: 5,
19001        },
19002    };
19003
19004    let mut cx = EditorLspTestContext::new_rust(
19005        lsp::ServerCapabilities {
19006            completion_provider: Some(lsp::CompletionOptions {
19007                trigger_characters: Some(vec![".".to_string()]),
19008                resolve_provider: Some(true),
19009                ..Default::default()
19010            }),
19011            ..Default::default()
19012        },
19013        cx,
19014    )
19015    .await;
19016
19017    cx.set_state("fn main() { let a = 2ˇ; }");
19018    cx.simulate_keystroke(".");
19019
19020    let completion_data = default_data.clone();
19021    let completion_characters = default_commit_characters.clone();
19022    let completion_items = items.clone();
19023    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19024        let default_data = completion_data.clone();
19025        let default_commit_characters = completion_characters.clone();
19026        let items = completion_items.clone();
19027        async move {
19028            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19029                items,
19030                item_defaults: Some(lsp::CompletionListItemDefaults {
19031                    data: Some(default_data.clone()),
19032                    commit_characters: Some(default_commit_characters.clone()),
19033                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19034                        default_edit_range,
19035                    )),
19036                    insert_text_format: Some(default_insert_text_format),
19037                    insert_text_mode: Some(default_insert_text_mode),
19038                }),
19039                ..lsp::CompletionList::default()
19040            })))
19041        }
19042    })
19043    .next()
19044    .await;
19045
19046    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19047    cx.lsp
19048        .server
19049        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19050            let closure_resolved_items = resolved_items.clone();
19051            move |item_to_resolve, _| {
19052                let closure_resolved_items = closure_resolved_items.clone();
19053                async move {
19054                    closure_resolved_items.lock().push(item_to_resolve.clone());
19055                    Ok(item_to_resolve)
19056                }
19057            }
19058        })
19059        .detach();
19060
19061    cx.condition(|editor, _| editor.context_menu_visible())
19062        .await;
19063    cx.run_until_parked();
19064    cx.update_editor(|editor, _, _| {
19065        let menu = editor.context_menu.borrow_mut();
19066        match menu.as_ref().expect("should have the completions menu") {
19067            CodeContextMenu::Completions(completions_menu) => {
19068                assert_eq!(
19069                    completions_menu
19070                        .entries
19071                        .borrow()
19072                        .iter()
19073                        .map(|mat| mat.string.clone())
19074                        .collect::<Vec<String>>(),
19075                    items
19076                        .iter()
19077                        .map(|completion| completion.label.clone())
19078                        .collect::<Vec<String>>()
19079                );
19080            }
19081            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19082        }
19083    });
19084    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19085    // with 4 from the end.
19086    assert_eq!(
19087        *resolved_items.lock(),
19088        [&items[0..16], &items[items.len() - 4..items.len()]]
19089            .concat()
19090            .iter()
19091            .cloned()
19092            .map(|mut item| {
19093                if item.data.is_none() {
19094                    item.data = Some(default_data.clone());
19095                }
19096                item
19097            })
19098            .collect::<Vec<lsp::CompletionItem>>(),
19099        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19100    );
19101    resolved_items.lock().clear();
19102
19103    cx.update_editor(|editor, window, cx| {
19104        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19105    });
19106    cx.run_until_parked();
19107    // Completions that have already been resolved are skipped.
19108    assert_eq!(
19109        *resolved_items.lock(),
19110        items[items.len() - 17..items.len() - 4]
19111            .iter()
19112            .cloned()
19113            .map(|mut item| {
19114                if item.data.is_none() {
19115                    item.data = Some(default_data.clone());
19116                }
19117                item
19118            })
19119            .collect::<Vec<lsp::CompletionItem>>()
19120    );
19121    resolved_items.lock().clear();
19122}
19123
19124#[gpui::test]
19125async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19126    init_test(cx, |_| {});
19127
19128    let mut cx = EditorLspTestContext::new(
19129        Language::new(
19130            LanguageConfig {
19131                matcher: LanguageMatcher {
19132                    path_suffixes: vec!["jsx".into()],
19133                    ..Default::default()
19134                },
19135                overrides: [(
19136                    "element".into(),
19137                    LanguageConfigOverride {
19138                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19139                        ..Default::default()
19140                    },
19141                )]
19142                .into_iter()
19143                .collect(),
19144                ..Default::default()
19145            },
19146            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19147        )
19148        .with_override_query("(jsx_self_closing_element) @element")
19149        .unwrap(),
19150        lsp::ServerCapabilities {
19151            completion_provider: Some(lsp::CompletionOptions {
19152                trigger_characters: Some(vec![":".to_string()]),
19153                ..Default::default()
19154            }),
19155            ..Default::default()
19156        },
19157        cx,
19158    )
19159    .await;
19160
19161    cx.lsp
19162        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19163            Ok(Some(lsp::CompletionResponse::Array(vec![
19164                lsp::CompletionItem {
19165                    label: "bg-blue".into(),
19166                    ..Default::default()
19167                },
19168                lsp::CompletionItem {
19169                    label: "bg-red".into(),
19170                    ..Default::default()
19171                },
19172                lsp::CompletionItem {
19173                    label: "bg-yellow".into(),
19174                    ..Default::default()
19175                },
19176            ])))
19177        });
19178
19179    cx.set_state(r#"<p class="bgˇ" />"#);
19180
19181    // Trigger completion when typing a dash, because the dash is an extra
19182    // word character in the 'element' scope, which contains the cursor.
19183    cx.simulate_keystroke("-");
19184    cx.executor().run_until_parked();
19185    cx.update_editor(|editor, _, _| {
19186        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19187        {
19188            assert_eq!(
19189                completion_menu_entries(menu),
19190                &["bg-blue", "bg-red", "bg-yellow"]
19191            );
19192        } else {
19193            panic!("expected completion menu to be open");
19194        }
19195    });
19196
19197    cx.simulate_keystroke("l");
19198    cx.executor().run_until_parked();
19199    cx.update_editor(|editor, _, _| {
19200        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19201        {
19202            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19203        } else {
19204            panic!("expected completion menu to be open");
19205        }
19206    });
19207
19208    // When filtering completions, consider the character after the '-' to
19209    // be the start of a subword.
19210    cx.set_state(r#"<p class="yelˇ" />"#);
19211    cx.simulate_keystroke("l");
19212    cx.executor().run_until_parked();
19213    cx.update_editor(|editor, _, _| {
19214        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19215        {
19216            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19217        } else {
19218            panic!("expected completion menu to be open");
19219        }
19220    });
19221}
19222
19223fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19224    let entries = menu.entries.borrow();
19225    entries.iter().map(|mat| mat.string.clone()).collect()
19226}
19227
19228#[gpui::test]
19229async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19230    init_test(cx, |settings| {
19231        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19232    });
19233
19234    let fs = FakeFs::new(cx.executor());
19235    fs.insert_file(path!("/file.ts"), Default::default()).await;
19236
19237    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19238    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19239
19240    language_registry.add(Arc::new(Language::new(
19241        LanguageConfig {
19242            name: "TypeScript".into(),
19243            matcher: LanguageMatcher {
19244                path_suffixes: vec!["ts".to_string()],
19245                ..Default::default()
19246            },
19247            ..Default::default()
19248        },
19249        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19250    )));
19251    update_test_language_settings(cx, |settings| {
19252        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19253    });
19254
19255    let test_plugin = "test_plugin";
19256    let _ = language_registry.register_fake_lsp(
19257        "TypeScript",
19258        FakeLspAdapter {
19259            prettier_plugins: vec![test_plugin],
19260            ..Default::default()
19261        },
19262    );
19263
19264    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19265    let buffer = project
19266        .update(cx, |project, cx| {
19267            project.open_local_buffer(path!("/file.ts"), cx)
19268        })
19269        .await
19270        .unwrap();
19271
19272    let buffer_text = "one\ntwo\nthree\n";
19273    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19274    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19275    editor.update_in(cx, |editor, window, cx| {
19276        editor.set_text(buffer_text, window, cx)
19277    });
19278
19279    editor
19280        .update_in(cx, |editor, window, cx| {
19281            editor.perform_format(
19282                project.clone(),
19283                FormatTrigger::Manual,
19284                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19285                window,
19286                cx,
19287            )
19288        })
19289        .unwrap()
19290        .await;
19291    assert_eq!(
19292        editor.update(cx, |editor, cx| editor.text(cx)),
19293        buffer_text.to_string() + prettier_format_suffix,
19294        "Test prettier formatting was not applied to the original buffer text",
19295    );
19296
19297    update_test_language_settings(cx, |settings| {
19298        settings.defaults.formatter = Some(FormatterList::default())
19299    });
19300    let format = editor.update_in(cx, |editor, window, cx| {
19301        editor.perform_format(
19302            project.clone(),
19303            FormatTrigger::Manual,
19304            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19305            window,
19306            cx,
19307        )
19308    });
19309    format.await.unwrap();
19310    assert_eq!(
19311        editor.update(cx, |editor, cx| editor.text(cx)),
19312        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19313        "Autoformatting (via test prettier) was not applied to the original buffer text",
19314    );
19315}
19316
19317#[gpui::test]
19318async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19319    init_test(cx, |settings| {
19320        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19321    });
19322
19323    let fs = FakeFs::new(cx.executor());
19324    fs.insert_file(path!("/file.settings"), Default::default())
19325        .await;
19326
19327    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19328    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19329
19330    let ts_lang = Arc::new(Language::new(
19331        LanguageConfig {
19332            name: "TypeScript".into(),
19333            matcher: LanguageMatcher {
19334                path_suffixes: vec!["ts".to_string()],
19335                ..LanguageMatcher::default()
19336            },
19337            prettier_parser_name: Some("typescript".to_string()),
19338            ..LanguageConfig::default()
19339        },
19340        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19341    ));
19342
19343    language_registry.add(ts_lang.clone());
19344
19345    update_test_language_settings(cx, |settings| {
19346        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19347    });
19348
19349    let test_plugin = "test_plugin";
19350    let _ = language_registry.register_fake_lsp(
19351        "TypeScript",
19352        FakeLspAdapter {
19353            prettier_plugins: vec![test_plugin],
19354            ..Default::default()
19355        },
19356    );
19357
19358    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19359    let buffer = project
19360        .update(cx, |project, cx| {
19361            project.open_local_buffer(path!("/file.settings"), cx)
19362        })
19363        .await
19364        .unwrap();
19365
19366    project.update(cx, |project, cx| {
19367        project.set_language_for_buffer(&buffer, ts_lang, cx)
19368    });
19369
19370    let buffer_text = "one\ntwo\nthree\n";
19371    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19372    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19373    editor.update_in(cx, |editor, window, cx| {
19374        editor.set_text(buffer_text, window, cx)
19375    });
19376
19377    editor
19378        .update_in(cx, |editor, window, cx| {
19379            editor.perform_format(
19380                project.clone(),
19381                FormatTrigger::Manual,
19382                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19383                window,
19384                cx,
19385            )
19386        })
19387        .unwrap()
19388        .await;
19389    assert_eq!(
19390        editor.update(cx, |editor, cx| editor.text(cx)),
19391        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19392        "Test prettier formatting was not applied to the original buffer text",
19393    );
19394
19395    update_test_language_settings(cx, |settings| {
19396        settings.defaults.formatter = Some(FormatterList::default())
19397    });
19398    let format = editor.update_in(cx, |editor, window, cx| {
19399        editor.perform_format(
19400            project.clone(),
19401            FormatTrigger::Manual,
19402            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19403            window,
19404            cx,
19405        )
19406    });
19407    format.await.unwrap();
19408
19409    assert_eq!(
19410        editor.update(cx, |editor, cx| editor.text(cx)),
19411        buffer_text.to_string()
19412            + prettier_format_suffix
19413            + "\ntypescript\n"
19414            + prettier_format_suffix
19415            + "\ntypescript",
19416        "Autoformatting (via test prettier) was not applied to the original buffer text",
19417    );
19418}
19419
19420#[gpui::test]
19421async fn test_addition_reverts(cx: &mut TestAppContext) {
19422    init_test(cx, |_| {});
19423    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19424    let base_text = indoc! {r#"
19425        struct Row;
19426        struct Row1;
19427        struct Row2;
19428
19429        struct Row4;
19430        struct Row5;
19431        struct Row6;
19432
19433        struct Row8;
19434        struct Row9;
19435        struct Row10;"#};
19436
19437    // When addition hunks are not adjacent to carets, no hunk revert is performed
19438    assert_hunk_revert(
19439        indoc! {r#"struct Row;
19440                   struct Row1;
19441                   struct Row1.1;
19442                   struct Row1.2;
19443                   struct Row2;ˇ
19444
19445                   struct Row4;
19446                   struct Row5;
19447                   struct Row6;
19448
19449                   struct Row8;
19450                   ˇstruct Row9;
19451                   struct Row9.1;
19452                   struct Row9.2;
19453                   struct Row9.3;
19454                   struct Row10;"#},
19455        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19456        indoc! {r#"struct Row;
19457                   struct Row1;
19458                   struct Row1.1;
19459                   struct Row1.2;
19460                   struct Row2;ˇ
19461
19462                   struct Row4;
19463                   struct Row5;
19464                   struct Row6;
19465
19466                   struct Row8;
19467                   ˇstruct Row9;
19468                   struct Row9.1;
19469                   struct Row9.2;
19470                   struct Row9.3;
19471                   struct Row10;"#},
19472        base_text,
19473        &mut cx,
19474    );
19475    // Same for selections
19476    assert_hunk_revert(
19477        indoc! {r#"struct Row;
19478                   struct Row1;
19479                   struct Row2;
19480                   struct Row2.1;
19481                   struct Row2.2;
19482                   «ˇ
19483                   struct Row4;
19484                   struct» Row5;
19485                   «struct Row6;
19486                   ˇ»
19487                   struct Row9.1;
19488                   struct Row9.2;
19489                   struct Row9.3;
19490                   struct Row8;
19491                   struct Row9;
19492                   struct Row10;"#},
19493        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19494        indoc! {r#"struct Row;
19495                   struct Row1;
19496                   struct Row2;
19497                   struct Row2.1;
19498                   struct Row2.2;
19499                   «ˇ
19500                   struct Row4;
19501                   struct» Row5;
19502                   «struct Row6;
19503                   ˇ»
19504                   struct Row9.1;
19505                   struct Row9.2;
19506                   struct Row9.3;
19507                   struct Row8;
19508                   struct Row9;
19509                   struct Row10;"#},
19510        base_text,
19511        &mut cx,
19512    );
19513
19514    // When carets and selections intersect the addition hunks, those are reverted.
19515    // Adjacent carets got merged.
19516    assert_hunk_revert(
19517        indoc! {r#"struct Row;
19518                   ˇ// something on the top
19519                   struct Row1;
19520                   struct Row2;
19521                   struct Roˇw3.1;
19522                   struct Row2.2;
19523                   struct Row2.3;ˇ
19524
19525                   struct Row4;
19526                   struct ˇRow5.1;
19527                   struct Row5.2;
19528                   struct «Rowˇ»5.3;
19529                   struct Row5;
19530                   struct Row6;
19531                   ˇ
19532                   struct Row9.1;
19533                   struct «Rowˇ»9.2;
19534                   struct «ˇRow»9.3;
19535                   struct Row8;
19536                   struct Row9;
19537                   «ˇ// something on bottom»
19538                   struct Row10;"#},
19539        vec![
19540            DiffHunkStatusKind::Added,
19541            DiffHunkStatusKind::Added,
19542            DiffHunkStatusKind::Added,
19543            DiffHunkStatusKind::Added,
19544            DiffHunkStatusKind::Added,
19545        ],
19546        indoc! {r#"struct Row;
19547                   ˇstruct Row1;
19548                   struct Row2;
19549                   ˇ
19550                   struct Row4;
19551                   ˇstruct Row5;
19552                   struct Row6;
19553                   ˇ
19554                   ˇstruct Row8;
19555                   struct Row9;
19556                   ˇstruct Row10;"#},
19557        base_text,
19558        &mut cx,
19559    );
19560}
19561
19562#[gpui::test]
19563async fn test_modification_reverts(cx: &mut TestAppContext) {
19564    init_test(cx, |_| {});
19565    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19566    let base_text = indoc! {r#"
19567        struct Row;
19568        struct Row1;
19569        struct Row2;
19570
19571        struct Row4;
19572        struct Row5;
19573        struct Row6;
19574
19575        struct Row8;
19576        struct Row9;
19577        struct Row10;"#};
19578
19579    // Modification hunks behave the same as the addition ones.
19580    assert_hunk_revert(
19581        indoc! {r#"struct Row;
19582                   struct Row1;
19583                   struct Row33;
19584                   ˇ
19585                   struct Row4;
19586                   struct Row5;
19587                   struct Row6;
19588                   ˇ
19589                   struct Row99;
19590                   struct Row9;
19591                   struct Row10;"#},
19592        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19593        indoc! {r#"struct Row;
19594                   struct Row1;
19595                   struct Row33;
19596                   ˇ
19597                   struct Row4;
19598                   struct Row5;
19599                   struct Row6;
19600                   ˇ
19601                   struct Row99;
19602                   struct Row9;
19603                   struct Row10;"#},
19604        base_text,
19605        &mut cx,
19606    );
19607    assert_hunk_revert(
19608        indoc! {r#"struct Row;
19609                   struct Row1;
19610                   struct Row33;
19611                   «ˇ
19612                   struct Row4;
19613                   struct» Row5;
19614                   «struct Row6;
19615                   ˇ»
19616                   struct Row99;
19617                   struct Row9;
19618                   struct Row10;"#},
19619        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19620        indoc! {r#"struct Row;
19621                   struct Row1;
19622                   struct Row33;
19623                   «ˇ
19624                   struct Row4;
19625                   struct» Row5;
19626                   «struct Row6;
19627                   ˇ»
19628                   struct Row99;
19629                   struct Row9;
19630                   struct Row10;"#},
19631        base_text,
19632        &mut cx,
19633    );
19634
19635    assert_hunk_revert(
19636        indoc! {r#"ˇstruct Row1.1;
19637                   struct Row1;
19638                   «ˇstr»uct Row22;
19639
19640                   struct ˇRow44;
19641                   struct Row5;
19642                   struct «Rˇ»ow66;ˇ
19643
19644                   «struˇ»ct Row88;
19645                   struct Row9;
19646                   struct Row1011;ˇ"#},
19647        vec![
19648            DiffHunkStatusKind::Modified,
19649            DiffHunkStatusKind::Modified,
19650            DiffHunkStatusKind::Modified,
19651            DiffHunkStatusKind::Modified,
19652            DiffHunkStatusKind::Modified,
19653            DiffHunkStatusKind::Modified,
19654        ],
19655        indoc! {r#"struct Row;
19656                   ˇstruct Row1;
19657                   struct Row2;
19658                   ˇ
19659                   struct Row4;
19660                   ˇstruct Row5;
19661                   struct Row6;
19662                   ˇ
19663                   struct Row8;
19664                   ˇstruct Row9;
19665                   struct Row10;ˇ"#},
19666        base_text,
19667        &mut cx,
19668    );
19669}
19670
19671#[gpui::test]
19672async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19673    init_test(cx, |_| {});
19674    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19675    let base_text = indoc! {r#"
19676        one
19677
19678        two
19679        three
19680        "#};
19681
19682    cx.set_head_text(base_text);
19683    cx.set_state("\nˇ\n");
19684    cx.executor().run_until_parked();
19685    cx.update_editor(|editor, _window, cx| {
19686        editor.expand_selected_diff_hunks(cx);
19687    });
19688    cx.executor().run_until_parked();
19689    cx.update_editor(|editor, window, cx| {
19690        editor.backspace(&Default::default(), window, cx);
19691    });
19692    cx.run_until_parked();
19693    cx.assert_state_with_diff(
19694        indoc! {r#"
19695
19696        - two
19697        - threeˇ
19698        +
19699        "#}
19700        .to_string(),
19701    );
19702}
19703
19704#[gpui::test]
19705async fn test_deletion_reverts(cx: &mut TestAppContext) {
19706    init_test(cx, |_| {});
19707    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19708    let base_text = indoc! {r#"struct Row;
19709struct Row1;
19710struct Row2;
19711
19712struct Row4;
19713struct Row5;
19714struct Row6;
19715
19716struct Row8;
19717struct Row9;
19718struct Row10;"#};
19719
19720    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19721    assert_hunk_revert(
19722        indoc! {r#"struct Row;
19723                   struct Row2;
19724
19725                   ˇstruct Row4;
19726                   struct Row5;
19727                   struct Row6;
19728                   ˇ
19729                   struct Row8;
19730                   struct Row10;"#},
19731        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19732        indoc! {r#"struct Row;
19733                   struct Row2;
19734
19735                   ˇstruct Row4;
19736                   struct Row5;
19737                   struct Row6;
19738                   ˇ
19739                   struct Row8;
19740                   struct Row10;"#},
19741        base_text,
19742        &mut cx,
19743    );
19744    assert_hunk_revert(
19745        indoc! {r#"struct Row;
19746                   struct Row2;
19747
19748                   «ˇstruct Row4;
19749                   struct» Row5;
19750                   «struct Row6;
19751                   ˇ»
19752                   struct Row8;
19753                   struct Row10;"#},
19754        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19755        indoc! {r#"struct Row;
19756                   struct Row2;
19757
19758                   «ˇstruct Row4;
19759                   struct» Row5;
19760                   «struct Row6;
19761                   ˇ»
19762                   struct Row8;
19763                   struct Row10;"#},
19764        base_text,
19765        &mut cx,
19766    );
19767
19768    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19769    assert_hunk_revert(
19770        indoc! {r#"struct Row;
19771                   ˇstruct Row2;
19772
19773                   struct Row4;
19774                   struct Row5;
19775                   struct Row6;
19776
19777                   struct Row8;ˇ
19778                   struct Row10;"#},
19779        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19780        indoc! {r#"struct Row;
19781                   struct Row1;
19782                   ˇstruct Row2;
19783
19784                   struct Row4;
19785                   struct Row5;
19786                   struct Row6;
19787
19788                   struct Row8;ˇ
19789                   struct Row9;
19790                   struct Row10;"#},
19791        base_text,
19792        &mut cx,
19793    );
19794    assert_hunk_revert(
19795        indoc! {r#"struct Row;
19796                   struct Row2«ˇ;
19797                   struct Row4;
19798                   struct» Row5;
19799                   «struct Row6;
19800
19801                   struct Row8;ˇ»
19802                   struct Row10;"#},
19803        vec![
19804            DiffHunkStatusKind::Deleted,
19805            DiffHunkStatusKind::Deleted,
19806            DiffHunkStatusKind::Deleted,
19807        ],
19808        indoc! {r#"struct Row;
19809                   struct Row1;
19810                   struct Row2«ˇ;
19811
19812                   struct Row4;
19813                   struct» Row5;
19814                   «struct Row6;
19815
19816                   struct Row8;ˇ»
19817                   struct Row9;
19818                   struct Row10;"#},
19819        base_text,
19820        &mut cx,
19821    );
19822}
19823
19824#[gpui::test]
19825async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19826    init_test(cx, |_| {});
19827
19828    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19829    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19830    let base_text_3 =
19831        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19832
19833    let text_1 = edit_first_char_of_every_line(base_text_1);
19834    let text_2 = edit_first_char_of_every_line(base_text_2);
19835    let text_3 = edit_first_char_of_every_line(base_text_3);
19836
19837    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19838    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19839    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19840
19841    let multibuffer = cx.new(|cx| {
19842        let mut multibuffer = MultiBuffer::new(ReadWrite);
19843        multibuffer.push_excerpts(
19844            buffer_1.clone(),
19845            [
19846                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19847                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19848                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19849            ],
19850            cx,
19851        );
19852        multibuffer.push_excerpts(
19853            buffer_2.clone(),
19854            [
19855                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19856                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19857                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19858            ],
19859            cx,
19860        );
19861        multibuffer.push_excerpts(
19862            buffer_3.clone(),
19863            [
19864                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19865                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19866                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19867            ],
19868            cx,
19869        );
19870        multibuffer
19871    });
19872
19873    let fs = FakeFs::new(cx.executor());
19874    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19875    let (editor, cx) = cx
19876        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19877    editor.update_in(cx, |editor, _window, cx| {
19878        for (buffer, diff_base) in [
19879            (buffer_1.clone(), base_text_1),
19880            (buffer_2.clone(), base_text_2),
19881            (buffer_3.clone(), base_text_3),
19882        ] {
19883            let diff = cx.new(|cx| {
19884                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
19885            });
19886            editor
19887                .buffer
19888                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19889        }
19890    });
19891    cx.executor().run_until_parked();
19892
19893    editor.update_in(cx, |editor, window, cx| {
19894        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}");
19895        editor.select_all(&SelectAll, window, cx);
19896        editor.git_restore(&Default::default(), window, cx);
19897    });
19898    cx.executor().run_until_parked();
19899
19900    // When all ranges are selected, all buffer hunks are reverted.
19901    editor.update(cx, |editor, cx| {
19902        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");
19903    });
19904    buffer_1.update(cx, |buffer, _| {
19905        assert_eq!(buffer.text(), base_text_1);
19906    });
19907    buffer_2.update(cx, |buffer, _| {
19908        assert_eq!(buffer.text(), base_text_2);
19909    });
19910    buffer_3.update(cx, |buffer, _| {
19911        assert_eq!(buffer.text(), base_text_3);
19912    });
19913
19914    editor.update_in(cx, |editor, window, cx| {
19915        editor.undo(&Default::default(), window, cx);
19916    });
19917
19918    editor.update_in(cx, |editor, window, cx| {
19919        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19920            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19921        });
19922        editor.git_restore(&Default::default(), window, cx);
19923    });
19924
19925    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19926    // but not affect buffer_2 and its related excerpts.
19927    editor.update(cx, |editor, cx| {
19928        assert_eq!(
19929            editor.text(cx),
19930            "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}"
19931        );
19932    });
19933    buffer_1.update(cx, |buffer, _| {
19934        assert_eq!(buffer.text(), base_text_1);
19935    });
19936    buffer_2.update(cx, |buffer, _| {
19937        assert_eq!(
19938            buffer.text(),
19939            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19940        );
19941    });
19942    buffer_3.update(cx, |buffer, _| {
19943        assert_eq!(
19944            buffer.text(),
19945            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19946        );
19947    });
19948
19949    fn edit_first_char_of_every_line(text: &str) -> String {
19950        text.split('\n')
19951            .map(|line| format!("X{}", &line[1..]))
19952            .collect::<Vec<_>>()
19953            .join("\n")
19954    }
19955}
19956
19957#[gpui::test]
19958async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19959    init_test(cx, |_| {});
19960
19961    let cols = 4;
19962    let rows = 10;
19963    let sample_text_1 = sample_text(rows, cols, 'a');
19964    assert_eq!(
19965        sample_text_1,
19966        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19967    );
19968    let sample_text_2 = sample_text(rows, cols, 'l');
19969    assert_eq!(
19970        sample_text_2,
19971        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19972    );
19973    let sample_text_3 = sample_text(rows, cols, 'v');
19974    assert_eq!(
19975        sample_text_3,
19976        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19977    );
19978
19979    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19980    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19981    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19982
19983    let multi_buffer = cx.new(|cx| {
19984        let mut multibuffer = MultiBuffer::new(ReadWrite);
19985        multibuffer.push_excerpts(
19986            buffer_1.clone(),
19987            [
19988                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19989                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19990                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19991            ],
19992            cx,
19993        );
19994        multibuffer.push_excerpts(
19995            buffer_2.clone(),
19996            [
19997                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19998                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19999                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20000            ],
20001            cx,
20002        );
20003        multibuffer.push_excerpts(
20004            buffer_3.clone(),
20005            [
20006                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20007                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20008                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20009            ],
20010            cx,
20011        );
20012        multibuffer
20013    });
20014
20015    let fs = FakeFs::new(cx.executor());
20016    fs.insert_tree(
20017        "/a",
20018        json!({
20019            "main.rs": sample_text_1,
20020            "other.rs": sample_text_2,
20021            "lib.rs": sample_text_3,
20022        }),
20023    )
20024    .await;
20025    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20026    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20027    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20028    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20029        Editor::new(
20030            EditorMode::full(),
20031            multi_buffer,
20032            Some(project.clone()),
20033            window,
20034            cx,
20035        )
20036    });
20037    let multibuffer_item_id = workspace
20038        .update(cx, |workspace, window, cx| {
20039            assert!(
20040                workspace.active_item(cx).is_none(),
20041                "active item should be None before the first item is added"
20042            );
20043            workspace.add_item_to_active_pane(
20044                Box::new(multi_buffer_editor.clone()),
20045                None,
20046                true,
20047                window,
20048                cx,
20049            );
20050            let active_item = workspace
20051                .active_item(cx)
20052                .expect("should have an active item after adding the multi buffer");
20053            assert_eq!(
20054                active_item.buffer_kind(cx),
20055                ItemBufferKind::Multibuffer,
20056                "A multi buffer was expected to active after adding"
20057            );
20058            active_item.item_id()
20059        })
20060        .unwrap();
20061    cx.executor().run_until_parked();
20062
20063    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20064        editor.change_selections(
20065            SelectionEffects::scroll(Autoscroll::Next),
20066            window,
20067            cx,
20068            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20069        );
20070        editor.open_excerpts(&OpenExcerpts, window, cx);
20071    });
20072    cx.executor().run_until_parked();
20073    let first_item_id = workspace
20074        .update(cx, |workspace, window, cx| {
20075            let active_item = workspace
20076                .active_item(cx)
20077                .expect("should have an active item after navigating into the 1st buffer");
20078            let first_item_id = active_item.item_id();
20079            assert_ne!(
20080                first_item_id, multibuffer_item_id,
20081                "Should navigate into the 1st buffer and activate it"
20082            );
20083            assert_eq!(
20084                active_item.buffer_kind(cx),
20085                ItemBufferKind::Singleton,
20086                "New active item should be a singleton buffer"
20087            );
20088            assert_eq!(
20089                active_item
20090                    .act_as::<Editor>(cx)
20091                    .expect("should have navigated into an editor for the 1st buffer")
20092                    .read(cx)
20093                    .text(cx),
20094                sample_text_1
20095            );
20096
20097            workspace
20098                .go_back(workspace.active_pane().downgrade(), window, cx)
20099                .detach_and_log_err(cx);
20100
20101            first_item_id
20102        })
20103        .unwrap();
20104    cx.executor().run_until_parked();
20105    workspace
20106        .update(cx, |workspace, _, cx| {
20107            let active_item = workspace
20108                .active_item(cx)
20109                .expect("should have an active item after navigating back");
20110            assert_eq!(
20111                active_item.item_id(),
20112                multibuffer_item_id,
20113                "Should navigate back to the multi buffer"
20114            );
20115            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20116        })
20117        .unwrap();
20118
20119    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20120        editor.change_selections(
20121            SelectionEffects::scroll(Autoscroll::Next),
20122            window,
20123            cx,
20124            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20125        );
20126        editor.open_excerpts(&OpenExcerpts, window, cx);
20127    });
20128    cx.executor().run_until_parked();
20129    let second_item_id = workspace
20130        .update(cx, |workspace, window, cx| {
20131            let active_item = workspace
20132                .active_item(cx)
20133                .expect("should have an active item after navigating into the 2nd buffer");
20134            let second_item_id = active_item.item_id();
20135            assert_ne!(
20136                second_item_id, multibuffer_item_id,
20137                "Should navigate away from the multibuffer"
20138            );
20139            assert_ne!(
20140                second_item_id, first_item_id,
20141                "Should navigate into the 2nd buffer and activate it"
20142            );
20143            assert_eq!(
20144                active_item.buffer_kind(cx),
20145                ItemBufferKind::Singleton,
20146                "New active item should be a singleton buffer"
20147            );
20148            assert_eq!(
20149                active_item
20150                    .act_as::<Editor>(cx)
20151                    .expect("should have navigated into an editor")
20152                    .read(cx)
20153                    .text(cx),
20154                sample_text_2
20155            );
20156
20157            workspace
20158                .go_back(workspace.active_pane().downgrade(), window, cx)
20159                .detach_and_log_err(cx);
20160
20161            second_item_id
20162        })
20163        .unwrap();
20164    cx.executor().run_until_parked();
20165    workspace
20166        .update(cx, |workspace, _, cx| {
20167            let active_item = workspace
20168                .active_item(cx)
20169                .expect("should have an active item after navigating back from the 2nd buffer");
20170            assert_eq!(
20171                active_item.item_id(),
20172                multibuffer_item_id,
20173                "Should navigate back from the 2nd buffer to the multi buffer"
20174            );
20175            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20176        })
20177        .unwrap();
20178
20179    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20180        editor.change_selections(
20181            SelectionEffects::scroll(Autoscroll::Next),
20182            window,
20183            cx,
20184            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20185        );
20186        editor.open_excerpts(&OpenExcerpts, window, cx);
20187    });
20188    cx.executor().run_until_parked();
20189    workspace
20190        .update(cx, |workspace, window, cx| {
20191            let active_item = workspace
20192                .active_item(cx)
20193                .expect("should have an active item after navigating into the 3rd buffer");
20194            let third_item_id = active_item.item_id();
20195            assert_ne!(
20196                third_item_id, multibuffer_item_id,
20197                "Should navigate into the 3rd buffer and activate it"
20198            );
20199            assert_ne!(third_item_id, first_item_id);
20200            assert_ne!(third_item_id, second_item_id);
20201            assert_eq!(
20202                active_item.buffer_kind(cx),
20203                ItemBufferKind::Singleton,
20204                "New active item should be a singleton buffer"
20205            );
20206            assert_eq!(
20207                active_item
20208                    .act_as::<Editor>(cx)
20209                    .expect("should have navigated into an editor")
20210                    .read(cx)
20211                    .text(cx),
20212                sample_text_3
20213            );
20214
20215            workspace
20216                .go_back(workspace.active_pane().downgrade(), window, cx)
20217                .detach_and_log_err(cx);
20218        })
20219        .unwrap();
20220    cx.executor().run_until_parked();
20221    workspace
20222        .update(cx, |workspace, _, cx| {
20223            let active_item = workspace
20224                .active_item(cx)
20225                .expect("should have an active item after navigating back from the 3rd buffer");
20226            assert_eq!(
20227                active_item.item_id(),
20228                multibuffer_item_id,
20229                "Should navigate back from the 3rd buffer to the multi buffer"
20230            );
20231            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20232        })
20233        .unwrap();
20234}
20235
20236#[gpui::test]
20237async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20238    init_test(cx, |_| {});
20239
20240    let mut cx = EditorTestContext::new(cx).await;
20241
20242    let diff_base = r#"
20243        use some::mod;
20244
20245        const A: u32 = 42;
20246
20247        fn main() {
20248            println!("hello");
20249
20250            println!("world");
20251        }
20252        "#
20253    .unindent();
20254
20255    cx.set_state(
20256        &r#"
20257        use some::modified;
20258
20259        ˇ
20260        fn main() {
20261            println!("hello there");
20262
20263            println!("around the");
20264            println!("world");
20265        }
20266        "#
20267        .unindent(),
20268    );
20269
20270    cx.set_head_text(&diff_base);
20271    executor.run_until_parked();
20272
20273    cx.update_editor(|editor, window, cx| {
20274        editor.go_to_next_hunk(&GoToHunk, window, cx);
20275        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20276    });
20277    executor.run_until_parked();
20278    cx.assert_state_with_diff(
20279        r#"
20280          use some::modified;
20281
20282
20283          fn main() {
20284        -     println!("hello");
20285        + ˇ    println!("hello there");
20286
20287              println!("around the");
20288              println!("world");
20289          }
20290        "#
20291        .unindent(),
20292    );
20293
20294    cx.update_editor(|editor, window, cx| {
20295        for _ in 0..2 {
20296            editor.go_to_next_hunk(&GoToHunk, window, cx);
20297            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20298        }
20299    });
20300    executor.run_until_parked();
20301    cx.assert_state_with_diff(
20302        r#"
20303        - use some::mod;
20304        + ˇuse some::modified;
20305
20306
20307          fn main() {
20308        -     println!("hello");
20309        +     println!("hello there");
20310
20311        +     println!("around the");
20312              println!("world");
20313          }
20314        "#
20315        .unindent(),
20316    );
20317
20318    cx.update_editor(|editor, window, cx| {
20319        editor.go_to_next_hunk(&GoToHunk, window, cx);
20320        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20321    });
20322    executor.run_until_parked();
20323    cx.assert_state_with_diff(
20324        r#"
20325        - use some::mod;
20326        + use some::modified;
20327
20328        - const A: u32 = 42;
20329          ˇ
20330          fn main() {
20331        -     println!("hello");
20332        +     println!("hello there");
20333
20334        +     println!("around the");
20335              println!("world");
20336          }
20337        "#
20338        .unindent(),
20339    );
20340
20341    cx.update_editor(|editor, window, cx| {
20342        editor.cancel(&Cancel, window, cx);
20343    });
20344
20345    cx.assert_state_with_diff(
20346        r#"
20347          use some::modified;
20348
20349          ˇ
20350          fn main() {
20351              println!("hello there");
20352
20353              println!("around the");
20354              println!("world");
20355          }
20356        "#
20357        .unindent(),
20358    );
20359}
20360
20361#[gpui::test]
20362async fn test_diff_base_change_with_expanded_diff_hunks(
20363    executor: BackgroundExecutor,
20364    cx: &mut TestAppContext,
20365) {
20366    init_test(cx, |_| {});
20367
20368    let mut cx = EditorTestContext::new(cx).await;
20369
20370    let diff_base = r#"
20371        use some::mod1;
20372        use some::mod2;
20373
20374        const A: u32 = 42;
20375        const B: u32 = 42;
20376        const C: u32 = 42;
20377
20378        fn main() {
20379            println!("hello");
20380
20381            println!("world");
20382        }
20383        "#
20384    .unindent();
20385
20386    cx.set_state(
20387        &r#"
20388        use some::mod2;
20389
20390        const A: u32 = 42;
20391        const C: u32 = 42;
20392
20393        fn main(ˇ) {
20394            //println!("hello");
20395
20396            println!("world");
20397            //
20398            //
20399        }
20400        "#
20401        .unindent(),
20402    );
20403
20404    cx.set_head_text(&diff_base);
20405    executor.run_until_parked();
20406
20407    cx.update_editor(|editor, window, cx| {
20408        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20409    });
20410    executor.run_until_parked();
20411    cx.assert_state_with_diff(
20412        r#"
20413        - use some::mod1;
20414          use some::mod2;
20415
20416          const A: u32 = 42;
20417        - const B: u32 = 42;
20418          const C: u32 = 42;
20419
20420          fn main(ˇ) {
20421        -     println!("hello");
20422        +     //println!("hello");
20423
20424              println!("world");
20425        +     //
20426        +     //
20427          }
20428        "#
20429        .unindent(),
20430    );
20431
20432    cx.set_head_text("new diff base!");
20433    executor.run_until_parked();
20434    cx.assert_state_with_diff(
20435        r#"
20436        - new diff base!
20437        + use some::mod2;
20438        +
20439        + const A: u32 = 42;
20440        + const C: u32 = 42;
20441        +
20442        + fn main(ˇ) {
20443        +     //println!("hello");
20444        +
20445        +     println!("world");
20446        +     //
20447        +     //
20448        + }
20449        "#
20450        .unindent(),
20451    );
20452}
20453
20454#[gpui::test]
20455async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20456    init_test(cx, |_| {});
20457
20458    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20459    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20460    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20461    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20462    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20463    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20464
20465    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20466    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20467    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20468
20469    let multi_buffer = cx.new(|cx| {
20470        let mut multibuffer = MultiBuffer::new(ReadWrite);
20471        multibuffer.push_excerpts(
20472            buffer_1.clone(),
20473            [
20474                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20475                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20476                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20477            ],
20478            cx,
20479        );
20480        multibuffer.push_excerpts(
20481            buffer_2.clone(),
20482            [
20483                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20484                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20485                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20486            ],
20487            cx,
20488        );
20489        multibuffer.push_excerpts(
20490            buffer_3.clone(),
20491            [
20492                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20493                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20494                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20495            ],
20496            cx,
20497        );
20498        multibuffer
20499    });
20500
20501    let editor =
20502        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20503    editor
20504        .update(cx, |editor, _window, cx| {
20505            for (buffer, diff_base) in [
20506                (buffer_1.clone(), file_1_old),
20507                (buffer_2.clone(), file_2_old),
20508                (buffer_3.clone(), file_3_old),
20509            ] {
20510                let diff = cx.new(|cx| {
20511                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20512                });
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| {
20619                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
20620            });
20621            editor
20622                .buffer
20623                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20624        })
20625        .unwrap();
20626
20627    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20628    cx.run_until_parked();
20629
20630    cx.update_editor(|editor, window, cx| {
20631        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20632    });
20633    cx.executor().run_until_parked();
20634
20635    // When the start of a hunk coincides with the start of its excerpt,
20636    // the hunk is expanded. When the start of a hunk is earlier than
20637    // the start of its excerpt, the hunk is not expanded.
20638    cx.assert_state_with_diff(
20639        "
20640            ˇaaa
20641          - bbb
20642          + BBB
20643
20644          - ddd
20645          - eee
20646          + DDD
20647          + EEE
20648            fff
20649
20650            iii
20651        "
20652        .unindent(),
20653    );
20654}
20655
20656#[gpui::test]
20657async fn test_edits_around_expanded_insertion_hunks(
20658    executor: BackgroundExecutor,
20659    cx: &mut TestAppContext,
20660) {
20661    init_test(cx, |_| {});
20662
20663    let mut cx = EditorTestContext::new(cx).await;
20664
20665    let diff_base = r#"
20666        use some::mod1;
20667        use some::mod2;
20668
20669        const A: u32 = 42;
20670
20671        fn main() {
20672            println!("hello");
20673
20674            println!("world");
20675        }
20676        "#
20677    .unindent();
20678    executor.run_until_parked();
20679    cx.set_state(
20680        &r#"
20681        use some::mod1;
20682        use some::mod2;
20683
20684        const A: u32 = 42;
20685        const B: u32 = 42;
20686        const C: u32 = 42;
20687        ˇ
20688
20689        fn main() {
20690            println!("hello");
20691
20692            println!("world");
20693        }
20694        "#
20695        .unindent(),
20696    );
20697
20698    cx.set_head_text(&diff_base);
20699    executor.run_until_parked();
20700
20701    cx.update_editor(|editor, window, cx| {
20702        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20703    });
20704    executor.run_until_parked();
20705
20706    cx.assert_state_with_diff(
20707        r#"
20708        use some::mod1;
20709        use some::mod2;
20710
20711        const A: u32 = 42;
20712      + const B: u32 = 42;
20713      + const C: u32 = 42;
20714      + ˇ
20715
20716        fn main() {
20717            println!("hello");
20718
20719            println!("world");
20720        }
20721      "#
20722        .unindent(),
20723    );
20724
20725    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20726    executor.run_until_parked();
20727
20728    cx.assert_state_with_diff(
20729        r#"
20730        use some::mod1;
20731        use some::mod2;
20732
20733        const A: u32 = 42;
20734      + const B: u32 = 42;
20735      + const C: u32 = 42;
20736      + const D: u32 = 42;
20737      + ˇ
20738
20739        fn main() {
20740            println!("hello");
20741
20742            println!("world");
20743        }
20744      "#
20745        .unindent(),
20746    );
20747
20748    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20749    executor.run_until_parked();
20750
20751    cx.assert_state_with_diff(
20752        r#"
20753        use some::mod1;
20754        use some::mod2;
20755
20756        const A: u32 = 42;
20757      + const B: u32 = 42;
20758      + const C: u32 = 42;
20759      + const D: u32 = 42;
20760      + const E: u32 = 42;
20761      + ˇ
20762
20763        fn main() {
20764            println!("hello");
20765
20766            println!("world");
20767        }
20768      "#
20769        .unindent(),
20770    );
20771
20772    cx.update_editor(|editor, window, cx| {
20773        editor.delete_line(&DeleteLine, window, cx);
20774    });
20775    executor.run_until_parked();
20776
20777    cx.assert_state_with_diff(
20778        r#"
20779        use some::mod1;
20780        use some::mod2;
20781
20782        const A: u32 = 42;
20783      + const B: u32 = 42;
20784      + const C: u32 = 42;
20785      + const D: u32 = 42;
20786      + const E: u32 = 42;
20787        ˇ
20788        fn main() {
20789            println!("hello");
20790
20791            println!("world");
20792        }
20793      "#
20794        .unindent(),
20795    );
20796
20797    cx.update_editor(|editor, 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        editor.move_up(&MoveUp, window, cx);
20803        editor.delete_line(&DeleteLine, window, cx);
20804    });
20805    executor.run_until_parked();
20806    cx.assert_state_with_diff(
20807        r#"
20808        use some::mod1;
20809        use some::mod2;
20810
20811        const A: u32 = 42;
20812      + const B: u32 = 42;
20813        ˇ
20814        fn main() {
20815            println!("hello");
20816
20817            println!("world");
20818        }
20819      "#
20820        .unindent(),
20821    );
20822
20823    cx.update_editor(|editor, window, cx| {
20824        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20825        editor.delete_line(&DeleteLine, window, cx);
20826    });
20827    executor.run_until_parked();
20828    cx.assert_state_with_diff(
20829        r#"
20830        ˇ
20831        fn main() {
20832            println!("hello");
20833
20834            println!("world");
20835        }
20836      "#
20837        .unindent(),
20838    );
20839}
20840
20841#[gpui::test]
20842async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20843    init_test(cx, |_| {});
20844
20845    let mut cx = EditorTestContext::new(cx).await;
20846    cx.set_head_text(indoc! { "
20847        one
20848        two
20849        three
20850        four
20851        five
20852        "
20853    });
20854    cx.set_state(indoc! { "
20855        one
20856        ˇthree
20857        five
20858    "});
20859    cx.run_until_parked();
20860    cx.update_editor(|editor, window, cx| {
20861        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20862    });
20863    cx.assert_state_with_diff(
20864        indoc! { "
20865        one
20866      - two
20867        ˇthree
20868      - four
20869        five
20870    "}
20871        .to_string(),
20872    );
20873    cx.update_editor(|editor, window, cx| {
20874        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20875    });
20876
20877    cx.assert_state_with_diff(
20878        indoc! { "
20879        one
20880        ˇthree
20881        five
20882    "}
20883        .to_string(),
20884    );
20885
20886    cx.update_editor(|editor, window, cx| {
20887        editor.move_up(&MoveUp, window, cx);
20888        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20889    });
20890    cx.assert_state_with_diff(
20891        indoc! { "
20892        ˇone
20893      - two
20894        three
20895        five
20896    "}
20897        .to_string(),
20898    );
20899
20900    cx.update_editor(|editor, window, cx| {
20901        editor.move_down(&MoveDown, window, cx);
20902        editor.move_down(&MoveDown, window, cx);
20903        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20904    });
20905    cx.assert_state_with_diff(
20906        indoc! { "
20907        one
20908      - two
20909        ˇthree
20910      - four
20911        five
20912    "}
20913        .to_string(),
20914    );
20915
20916    cx.set_state(indoc! { "
20917        one
20918        ˇTWO
20919        three
20920        four
20921        five
20922    "});
20923    cx.run_until_parked();
20924    cx.update_editor(|editor, window, cx| {
20925        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20926    });
20927
20928    cx.assert_state_with_diff(
20929        indoc! { "
20930            one
20931          - two
20932          + ˇTWO
20933            three
20934            four
20935            five
20936        "}
20937        .to_string(),
20938    );
20939    cx.update_editor(|editor, window, cx| {
20940        editor.move_up(&Default::default(), window, cx);
20941        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20942    });
20943    cx.assert_state_with_diff(
20944        indoc! { "
20945            one
20946            ˇTWO
20947            three
20948            four
20949            five
20950        "}
20951        .to_string(),
20952    );
20953}
20954
20955#[gpui::test]
20956async fn test_toggling_adjacent_diff_hunks_2(
20957    executor: BackgroundExecutor,
20958    cx: &mut TestAppContext,
20959) {
20960    init_test(cx, |_| {});
20961
20962    let mut cx = EditorTestContext::new(cx).await;
20963
20964    let diff_base = r#"
20965        lineA
20966        lineB
20967        lineC
20968        lineD
20969        "#
20970    .unindent();
20971
20972    cx.set_state(
20973        &r#"
20974        ˇlineA1
20975        lineB
20976        lineD
20977        "#
20978        .unindent(),
20979    );
20980    cx.set_head_text(&diff_base);
20981    executor.run_until_parked();
20982
20983    cx.update_editor(|editor, window, cx| {
20984        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20985    });
20986    executor.run_until_parked();
20987    cx.assert_state_with_diff(
20988        r#"
20989        - lineA
20990        + ˇlineA1
20991          lineB
20992          lineD
20993        "#
20994        .unindent(),
20995    );
20996
20997    cx.update_editor(|editor, window, cx| {
20998        editor.move_down(&MoveDown, window, cx);
20999        editor.move_right(&MoveRight, window, cx);
21000        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21001    });
21002    executor.run_until_parked();
21003    cx.assert_state_with_diff(
21004        r#"
21005        - lineA
21006        + lineA1
21007          lˇineB
21008        - lineC
21009          lineD
21010        "#
21011        .unindent(),
21012    );
21013}
21014
21015#[gpui::test]
21016async fn test_edits_around_expanded_deletion_hunks(
21017    executor: BackgroundExecutor,
21018    cx: &mut TestAppContext,
21019) {
21020    init_test(cx, |_| {});
21021
21022    let mut cx = EditorTestContext::new(cx).await;
21023
21024    let diff_base = r#"
21025        use some::mod1;
21026        use some::mod2;
21027
21028        const A: u32 = 42;
21029        const B: u32 = 42;
21030        const C: u32 = 42;
21031
21032
21033        fn main() {
21034            println!("hello");
21035
21036            println!("world");
21037        }
21038    "#
21039    .unindent();
21040    executor.run_until_parked();
21041    cx.set_state(
21042        &r#"
21043        use some::mod1;
21044        use some::mod2;
21045
21046        ˇconst B: u32 = 42;
21047        const C: u32 = 42;
21048
21049
21050        fn main() {
21051            println!("hello");
21052
21053            println!("world");
21054        }
21055        "#
21056        .unindent(),
21057    );
21058
21059    cx.set_head_text(&diff_base);
21060    executor.run_until_parked();
21061
21062    cx.update_editor(|editor, window, cx| {
21063        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21064    });
21065    executor.run_until_parked();
21066
21067    cx.assert_state_with_diff(
21068        r#"
21069        use some::mod1;
21070        use some::mod2;
21071
21072      - const A: u32 = 42;
21073        ˇconst B: u32 = 42;
21074        const C: u32 = 42;
21075
21076
21077        fn main() {
21078            println!("hello");
21079
21080            println!("world");
21081        }
21082      "#
21083        .unindent(),
21084    );
21085
21086    cx.update_editor(|editor, window, cx| {
21087        editor.delete_line(&DeleteLine, window, cx);
21088    });
21089    executor.run_until_parked();
21090    cx.assert_state_with_diff(
21091        r#"
21092        use some::mod1;
21093        use some::mod2;
21094
21095      - const A: u32 = 42;
21096      - const B: u32 = 42;
21097        ˇconst C: u32 = 42;
21098
21099
21100        fn main() {
21101            println!("hello");
21102
21103            println!("world");
21104        }
21105      "#
21106        .unindent(),
21107    );
21108
21109    cx.update_editor(|editor, window, cx| {
21110        editor.delete_line(&DeleteLine, window, cx);
21111    });
21112    executor.run_until_parked();
21113    cx.assert_state_with_diff(
21114        r#"
21115        use some::mod1;
21116        use some::mod2;
21117
21118      - const A: u32 = 42;
21119      - const B: u32 = 42;
21120      - const C: u32 = 42;
21121        ˇ
21122
21123        fn main() {
21124            println!("hello");
21125
21126            println!("world");
21127        }
21128      "#
21129        .unindent(),
21130    );
21131
21132    cx.update_editor(|editor, window, cx| {
21133        editor.handle_input("replacement", window, cx);
21134    });
21135    executor.run_until_parked();
21136    cx.assert_state_with_diff(
21137        r#"
21138        use some::mod1;
21139        use some::mod2;
21140
21141      - const A: u32 = 42;
21142      - const B: u32 = 42;
21143      - const C: u32 = 42;
21144      -
21145      + replacementˇ
21146
21147        fn main() {
21148            println!("hello");
21149
21150            println!("world");
21151        }
21152      "#
21153        .unindent(),
21154    );
21155}
21156
21157#[gpui::test]
21158async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21159    init_test(cx, |_| {});
21160
21161    let mut cx = EditorTestContext::new(cx).await;
21162
21163    let base_text = r#"
21164        one
21165        two
21166        three
21167        four
21168        five
21169    "#
21170    .unindent();
21171    executor.run_until_parked();
21172    cx.set_state(
21173        &r#"
21174        one
21175        two
21176        fˇour
21177        five
21178        "#
21179        .unindent(),
21180    );
21181
21182    cx.set_head_text(&base_text);
21183    executor.run_until_parked();
21184
21185    cx.update_editor(|editor, window, cx| {
21186        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21187    });
21188    executor.run_until_parked();
21189
21190    cx.assert_state_with_diff(
21191        r#"
21192          one
21193          two
21194        - three
21195          fˇour
21196          five
21197        "#
21198        .unindent(),
21199    );
21200
21201    cx.update_editor(|editor, window, cx| {
21202        editor.backspace(&Backspace, window, cx);
21203        editor.backspace(&Backspace, window, cx);
21204    });
21205    executor.run_until_parked();
21206    cx.assert_state_with_diff(
21207        r#"
21208          one
21209          two
21210        - threeˇ
21211        - four
21212        + our
21213          five
21214        "#
21215        .unindent(),
21216    );
21217}
21218
21219#[gpui::test]
21220async fn test_edit_after_expanded_modification_hunk(
21221    executor: BackgroundExecutor,
21222    cx: &mut TestAppContext,
21223) {
21224    init_test(cx, |_| {});
21225
21226    let mut cx = EditorTestContext::new(cx).await;
21227
21228    let diff_base = r#"
21229        use some::mod1;
21230        use some::mod2;
21231
21232        const A: u32 = 42;
21233        const B: u32 = 42;
21234        const C: u32 = 42;
21235        const D: u32 = 42;
21236
21237
21238        fn main() {
21239            println!("hello");
21240
21241            println!("world");
21242        }"#
21243    .unindent();
21244
21245    cx.set_state(
21246        &r#"
21247        use some::mod1;
21248        use some::mod2;
21249
21250        const A: u32 = 42;
21251        const B: u32 = 42;
21252        const C: u32 = 43ˇ
21253        const D: u32 = 42;
21254
21255
21256        fn main() {
21257            println!("hello");
21258
21259            println!("world");
21260        }"#
21261        .unindent(),
21262    );
21263
21264    cx.set_head_text(&diff_base);
21265    executor.run_until_parked();
21266    cx.update_editor(|editor, window, cx| {
21267        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21268    });
21269    executor.run_until_parked();
21270
21271    cx.assert_state_with_diff(
21272        r#"
21273        use some::mod1;
21274        use some::mod2;
21275
21276        const A: u32 = 42;
21277        const B: u32 = 42;
21278      - const C: u32 = 42;
21279      + const C: u32 = 43ˇ
21280        const D: u32 = 42;
21281
21282
21283        fn main() {
21284            println!("hello");
21285
21286            println!("world");
21287        }"#
21288        .unindent(),
21289    );
21290
21291    cx.update_editor(|editor, window, cx| {
21292        editor.handle_input("\nnew_line\n", window, cx);
21293    });
21294    executor.run_until_parked();
21295
21296    cx.assert_state_with_diff(
21297        r#"
21298        use some::mod1;
21299        use some::mod2;
21300
21301        const A: u32 = 42;
21302        const B: u32 = 42;
21303      - const C: u32 = 42;
21304      + const C: u32 = 43
21305      + new_line
21306      + ˇ
21307        const D: u32 = 42;
21308
21309
21310        fn main() {
21311            println!("hello");
21312
21313            println!("world");
21314        }"#
21315        .unindent(),
21316    );
21317}
21318
21319#[gpui::test]
21320async fn test_stage_and_unstage_added_file_hunk(
21321    executor: BackgroundExecutor,
21322    cx: &mut TestAppContext,
21323) {
21324    init_test(cx, |_| {});
21325
21326    let mut cx = EditorTestContext::new(cx).await;
21327    cx.update_editor(|editor, _, cx| {
21328        editor.set_expand_all_diff_hunks(cx);
21329    });
21330
21331    let working_copy = r#"
21332            ˇfn main() {
21333                println!("hello, world!");
21334            }
21335        "#
21336    .unindent();
21337
21338    cx.set_state(&working_copy);
21339    executor.run_until_parked();
21340
21341    cx.assert_state_with_diff(
21342        r#"
21343            + ˇfn main() {
21344            +     println!("hello, world!");
21345            + }
21346        "#
21347        .unindent(),
21348    );
21349    cx.assert_index_text(None);
21350
21351    cx.update_editor(|editor, window, cx| {
21352        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21353    });
21354    executor.run_until_parked();
21355    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21356    cx.assert_state_with_diff(
21357        r#"
21358            + ˇfn main() {
21359            +     println!("hello, world!");
21360            + }
21361        "#
21362        .unindent(),
21363    );
21364
21365    cx.update_editor(|editor, window, cx| {
21366        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21367    });
21368    executor.run_until_parked();
21369    cx.assert_index_text(None);
21370}
21371
21372async fn setup_indent_guides_editor(
21373    text: &str,
21374    cx: &mut TestAppContext,
21375) -> (BufferId, EditorTestContext) {
21376    init_test(cx, |_| {});
21377
21378    let mut cx = EditorTestContext::new(cx).await;
21379
21380    let buffer_id = cx.update_editor(|editor, window, cx| {
21381        editor.set_text(text, window, cx);
21382        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21383
21384        buffer_ids[0]
21385    });
21386
21387    (buffer_id, cx)
21388}
21389
21390fn assert_indent_guides(
21391    range: Range<u32>,
21392    expected: Vec<IndentGuide>,
21393    active_indices: Option<Vec<usize>>,
21394    cx: &mut EditorTestContext,
21395) {
21396    let indent_guides = cx.update_editor(|editor, window, cx| {
21397        let snapshot = editor.snapshot(window, cx).display_snapshot;
21398        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21399            editor,
21400            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21401            true,
21402            &snapshot,
21403            cx,
21404        );
21405
21406        indent_guides.sort_by(|a, b| {
21407            a.depth.cmp(&b.depth).then(
21408                a.start_row
21409                    .cmp(&b.start_row)
21410                    .then(a.end_row.cmp(&b.end_row)),
21411            )
21412        });
21413        indent_guides
21414    });
21415
21416    if let Some(expected) = active_indices {
21417        let active_indices = cx.update_editor(|editor, window, cx| {
21418            let snapshot = editor.snapshot(window, cx).display_snapshot;
21419            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21420        });
21421
21422        assert_eq!(
21423            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21424            expected,
21425            "Active indent guide indices do not match"
21426        );
21427    }
21428
21429    assert_eq!(indent_guides, expected, "Indent guides do not match");
21430}
21431
21432fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21433    IndentGuide {
21434        buffer_id,
21435        start_row: MultiBufferRow(start_row),
21436        end_row: MultiBufferRow(end_row),
21437        depth,
21438        tab_size: 4,
21439        settings: IndentGuideSettings {
21440            enabled: true,
21441            line_width: 1,
21442            active_line_width: 1,
21443            coloring: IndentGuideColoring::default(),
21444            background_coloring: IndentGuideBackgroundColoring::default(),
21445        },
21446    }
21447}
21448
21449#[gpui::test]
21450async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21451    let (buffer_id, mut cx) = setup_indent_guides_editor(
21452        &"
21453        fn main() {
21454            let a = 1;
21455        }"
21456        .unindent(),
21457        cx,
21458    )
21459    .await;
21460
21461    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21462}
21463
21464#[gpui::test]
21465async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21466    let (buffer_id, mut cx) = setup_indent_guides_editor(
21467        &"
21468        fn main() {
21469            let a = 1;
21470            let b = 2;
21471        }"
21472        .unindent(),
21473        cx,
21474    )
21475    .await;
21476
21477    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21478}
21479
21480#[gpui::test]
21481async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21482    let (buffer_id, mut cx) = setup_indent_guides_editor(
21483        &"
21484        fn main() {
21485            let a = 1;
21486            if a == 3 {
21487                let b = 2;
21488            } else {
21489                let c = 3;
21490            }
21491        }"
21492        .unindent(),
21493        cx,
21494    )
21495    .await;
21496
21497    assert_indent_guides(
21498        0..8,
21499        vec![
21500            indent_guide(buffer_id, 1, 6, 0),
21501            indent_guide(buffer_id, 3, 3, 1),
21502            indent_guide(buffer_id, 5, 5, 1),
21503        ],
21504        None,
21505        &mut cx,
21506    );
21507}
21508
21509#[gpui::test]
21510async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21511    let (buffer_id, mut cx) = setup_indent_guides_editor(
21512        &"
21513        fn main() {
21514            let a = 1;
21515                let b = 2;
21516            let c = 3;
21517        }"
21518        .unindent(),
21519        cx,
21520    )
21521    .await;
21522
21523    assert_indent_guides(
21524        0..5,
21525        vec![
21526            indent_guide(buffer_id, 1, 3, 0),
21527            indent_guide(buffer_id, 2, 2, 1),
21528        ],
21529        None,
21530        &mut cx,
21531    );
21532}
21533
21534#[gpui::test]
21535async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21536    let (buffer_id, mut cx) = setup_indent_guides_editor(
21537        &"
21538        fn main() {
21539            let a = 1;
21540
21541            let c = 3;
21542        }"
21543        .unindent(),
21544        cx,
21545    )
21546    .await;
21547
21548    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21549}
21550
21551#[gpui::test]
21552async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21553    let (buffer_id, mut cx) = setup_indent_guides_editor(
21554        &"
21555        fn main() {
21556            let a = 1;
21557
21558            let c = 3;
21559
21560            if a == 3 {
21561                let b = 2;
21562            } else {
21563                let c = 3;
21564            }
21565        }"
21566        .unindent(),
21567        cx,
21568    )
21569    .await;
21570
21571    assert_indent_guides(
21572        0..11,
21573        vec![
21574            indent_guide(buffer_id, 1, 9, 0),
21575            indent_guide(buffer_id, 6, 6, 1),
21576            indent_guide(buffer_id, 8, 8, 1),
21577        ],
21578        None,
21579        &mut cx,
21580    );
21581}
21582
21583#[gpui::test]
21584async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21585    let (buffer_id, mut cx) = setup_indent_guides_editor(
21586        &"
21587        fn main() {
21588            let a = 1;
21589
21590            let c = 3;
21591
21592            if a == 3 {
21593                let b = 2;
21594            } else {
21595                let c = 3;
21596            }
21597        }"
21598        .unindent(),
21599        cx,
21600    )
21601    .await;
21602
21603    assert_indent_guides(
21604        1..11,
21605        vec![
21606            indent_guide(buffer_id, 1, 9, 0),
21607            indent_guide(buffer_id, 6, 6, 1),
21608            indent_guide(buffer_id, 8, 8, 1),
21609        ],
21610        None,
21611        &mut cx,
21612    );
21613}
21614
21615#[gpui::test]
21616async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21617    let (buffer_id, mut cx) = setup_indent_guides_editor(
21618        &"
21619        fn main() {
21620            let a = 1;
21621
21622            let c = 3;
21623
21624            if a == 3 {
21625                let b = 2;
21626            } else {
21627                let c = 3;
21628            }
21629        }"
21630        .unindent(),
21631        cx,
21632    )
21633    .await;
21634
21635    assert_indent_guides(
21636        1..10,
21637        vec![
21638            indent_guide(buffer_id, 1, 9, 0),
21639            indent_guide(buffer_id, 6, 6, 1),
21640            indent_guide(buffer_id, 8, 8, 1),
21641        ],
21642        None,
21643        &mut cx,
21644    );
21645}
21646
21647#[gpui::test]
21648async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21649    let (buffer_id, mut cx) = setup_indent_guides_editor(
21650        &"
21651        fn main() {
21652            if a {
21653                b(
21654                    c,
21655                    d,
21656                )
21657            } else {
21658                e(
21659                    f
21660                )
21661            }
21662        }"
21663        .unindent(),
21664        cx,
21665    )
21666    .await;
21667
21668    assert_indent_guides(
21669        0..11,
21670        vec![
21671            indent_guide(buffer_id, 1, 10, 0),
21672            indent_guide(buffer_id, 2, 5, 1),
21673            indent_guide(buffer_id, 7, 9, 1),
21674            indent_guide(buffer_id, 3, 4, 2),
21675            indent_guide(buffer_id, 8, 8, 2),
21676        ],
21677        None,
21678        &mut cx,
21679    );
21680
21681    cx.update_editor(|editor, window, cx| {
21682        editor.fold_at(MultiBufferRow(2), window, cx);
21683        assert_eq!(
21684            editor.display_text(cx),
21685            "
21686            fn main() {
21687                if a {
21688                    b(⋯
21689                    )
21690                } else {
21691                    e(
21692                        f
21693                    )
21694                }
21695            }"
21696            .unindent()
21697        );
21698    });
21699
21700    assert_indent_guides(
21701        0..11,
21702        vec![
21703            indent_guide(buffer_id, 1, 10, 0),
21704            indent_guide(buffer_id, 2, 5, 1),
21705            indent_guide(buffer_id, 7, 9, 1),
21706            indent_guide(buffer_id, 8, 8, 2),
21707        ],
21708        None,
21709        &mut cx,
21710    );
21711}
21712
21713#[gpui::test]
21714async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21715    let (buffer_id, mut cx) = setup_indent_guides_editor(
21716        &"
21717        block1
21718            block2
21719                block3
21720                    block4
21721            block2
21722        block1
21723        block1"
21724            .unindent(),
21725        cx,
21726    )
21727    .await;
21728
21729    assert_indent_guides(
21730        1..10,
21731        vec![
21732            indent_guide(buffer_id, 1, 4, 0),
21733            indent_guide(buffer_id, 2, 3, 1),
21734            indent_guide(buffer_id, 3, 3, 2),
21735        ],
21736        None,
21737        &mut cx,
21738    );
21739}
21740
21741#[gpui::test]
21742async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21743    let (buffer_id, mut cx) = setup_indent_guides_editor(
21744        &"
21745        block1
21746            block2
21747                block3
21748
21749        block1
21750        block1"
21751            .unindent(),
21752        cx,
21753    )
21754    .await;
21755
21756    assert_indent_guides(
21757        0..6,
21758        vec![
21759            indent_guide(buffer_id, 1, 2, 0),
21760            indent_guide(buffer_id, 2, 2, 1),
21761        ],
21762        None,
21763        &mut cx,
21764    );
21765}
21766
21767#[gpui::test]
21768async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21769    let (buffer_id, mut cx) = setup_indent_guides_editor(
21770        &"
21771        function component() {
21772        \treturn (
21773        \t\t\t
21774        \t\t<div>
21775        \t\t\t<abc></abc>
21776        \t\t</div>
21777        \t)
21778        }"
21779        .unindent(),
21780        cx,
21781    )
21782    .await;
21783
21784    assert_indent_guides(
21785        0..8,
21786        vec![
21787            indent_guide(buffer_id, 1, 6, 0),
21788            indent_guide(buffer_id, 2, 5, 1),
21789            indent_guide(buffer_id, 4, 4, 2),
21790        ],
21791        None,
21792        &mut cx,
21793    );
21794}
21795
21796#[gpui::test]
21797async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21798    let (buffer_id, mut cx) = setup_indent_guides_editor(
21799        &"
21800        function component() {
21801        \treturn (
21802        \t
21803        \t\t<div>
21804        \t\t\t<abc></abc>
21805        \t\t</div>
21806        \t)
21807        }"
21808        .unindent(),
21809        cx,
21810    )
21811    .await;
21812
21813    assert_indent_guides(
21814        0..8,
21815        vec![
21816            indent_guide(buffer_id, 1, 6, 0),
21817            indent_guide(buffer_id, 2, 5, 1),
21818            indent_guide(buffer_id, 4, 4, 2),
21819        ],
21820        None,
21821        &mut cx,
21822    );
21823}
21824
21825#[gpui::test]
21826async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21827    let (buffer_id, mut cx) = setup_indent_guides_editor(
21828        &"
21829        block1
21830
21831
21832
21833            block2
21834        "
21835        .unindent(),
21836        cx,
21837    )
21838    .await;
21839
21840    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21841}
21842
21843#[gpui::test]
21844async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21845    let (buffer_id, mut cx) = setup_indent_guides_editor(
21846        &"
21847        def a:
21848        \tb = 3
21849        \tif True:
21850        \t\tc = 4
21851        \t\td = 5
21852        \tprint(b)
21853        "
21854        .unindent(),
21855        cx,
21856    )
21857    .await;
21858
21859    assert_indent_guides(
21860        0..6,
21861        vec![
21862            indent_guide(buffer_id, 1, 5, 0),
21863            indent_guide(buffer_id, 3, 4, 1),
21864        ],
21865        None,
21866        &mut cx,
21867    );
21868}
21869
21870#[gpui::test]
21871async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21872    let (buffer_id, mut cx) = setup_indent_guides_editor(
21873        &"
21874    fn main() {
21875        let a = 1;
21876    }"
21877        .unindent(),
21878        cx,
21879    )
21880    .await;
21881
21882    cx.update_editor(|editor, window, cx| {
21883        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21884            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21885        });
21886    });
21887
21888    assert_indent_guides(
21889        0..3,
21890        vec![indent_guide(buffer_id, 1, 1, 0)],
21891        Some(vec![0]),
21892        &mut cx,
21893    );
21894}
21895
21896#[gpui::test]
21897async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21898    let (buffer_id, mut cx) = setup_indent_guides_editor(
21899        &"
21900    fn main() {
21901        if 1 == 2 {
21902            let a = 1;
21903        }
21904    }"
21905        .unindent(),
21906        cx,
21907    )
21908    .await;
21909
21910    cx.update_editor(|editor, window, cx| {
21911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21912            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21913        });
21914    });
21915
21916    assert_indent_guides(
21917        0..4,
21918        vec![
21919            indent_guide(buffer_id, 1, 3, 0),
21920            indent_guide(buffer_id, 2, 2, 1),
21921        ],
21922        Some(vec![1]),
21923        &mut cx,
21924    );
21925
21926    cx.update_editor(|editor, window, cx| {
21927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21928            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21929        });
21930    });
21931
21932    assert_indent_guides(
21933        0..4,
21934        vec![
21935            indent_guide(buffer_id, 1, 3, 0),
21936            indent_guide(buffer_id, 2, 2, 1),
21937        ],
21938        Some(vec![1]),
21939        &mut cx,
21940    );
21941
21942    cx.update_editor(|editor, window, cx| {
21943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21944            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21945        });
21946    });
21947
21948    assert_indent_guides(
21949        0..4,
21950        vec![
21951            indent_guide(buffer_id, 1, 3, 0),
21952            indent_guide(buffer_id, 2, 2, 1),
21953        ],
21954        Some(vec![0]),
21955        &mut cx,
21956    );
21957}
21958
21959#[gpui::test]
21960async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21961    let (buffer_id, mut cx) = setup_indent_guides_editor(
21962        &"
21963    fn main() {
21964        let a = 1;
21965
21966        let b = 2;
21967    }"
21968        .unindent(),
21969        cx,
21970    )
21971    .await;
21972
21973    cx.update_editor(|editor, window, cx| {
21974        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21975            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21976        });
21977    });
21978
21979    assert_indent_guides(
21980        0..5,
21981        vec![indent_guide(buffer_id, 1, 3, 0)],
21982        Some(vec![0]),
21983        &mut cx,
21984    );
21985}
21986
21987#[gpui::test]
21988async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21989    let (buffer_id, mut cx) = setup_indent_guides_editor(
21990        &"
21991    def m:
21992        a = 1
21993        pass"
21994            .unindent(),
21995        cx,
21996    )
21997    .await;
21998
21999    cx.update_editor(|editor, window, cx| {
22000        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22001            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22002        });
22003    });
22004
22005    assert_indent_guides(
22006        0..3,
22007        vec![indent_guide(buffer_id, 1, 2, 0)],
22008        Some(vec![0]),
22009        &mut cx,
22010    );
22011}
22012
22013#[gpui::test]
22014async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22015    init_test(cx, |_| {});
22016    let mut cx = EditorTestContext::new(cx).await;
22017    let text = indoc! {
22018        "
22019        impl A {
22020            fn b() {
22021                0;
22022                3;
22023                5;
22024                6;
22025                7;
22026            }
22027        }
22028        "
22029    };
22030    let base_text = indoc! {
22031        "
22032        impl A {
22033            fn b() {
22034                0;
22035                1;
22036                2;
22037                3;
22038                4;
22039            }
22040            fn c() {
22041                5;
22042                6;
22043                7;
22044            }
22045        }
22046        "
22047    };
22048
22049    cx.update_editor(|editor, window, cx| {
22050        editor.set_text(text, window, cx);
22051
22052        editor.buffer().update(cx, |multibuffer, cx| {
22053            let buffer = multibuffer.as_singleton().unwrap();
22054            let diff = cx.new(|cx| {
22055                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22056            });
22057
22058            multibuffer.set_all_diff_hunks_expanded(cx);
22059            multibuffer.add_diff(diff, cx);
22060
22061            buffer.read(cx).remote_id()
22062        })
22063    });
22064    cx.run_until_parked();
22065
22066    cx.assert_state_with_diff(
22067        indoc! { "
22068          impl A {
22069              fn b() {
22070                  0;
22071        -         1;
22072        -         2;
22073                  3;
22074        -         4;
22075        -     }
22076        -     fn c() {
22077                  5;
22078                  6;
22079                  7;
22080              }
22081          }
22082          ˇ"
22083        }
22084        .to_string(),
22085    );
22086
22087    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22088        editor
22089            .snapshot(window, cx)
22090            .buffer_snapshot()
22091            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22092            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22093            .collect::<Vec<_>>()
22094    });
22095    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22096    assert_eq!(
22097        actual_guides,
22098        vec![
22099            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22100            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22101            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22102        ]
22103    );
22104}
22105
22106#[gpui::test]
22107async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22108    init_test(cx, |_| {});
22109    let mut cx = EditorTestContext::new(cx).await;
22110
22111    let diff_base = r#"
22112        a
22113        b
22114        c
22115        "#
22116    .unindent();
22117
22118    cx.set_state(
22119        &r#"
22120        ˇA
22121        b
22122        C
22123        "#
22124        .unindent(),
22125    );
22126    cx.set_head_text(&diff_base);
22127    cx.update_editor(|editor, window, cx| {
22128        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22129    });
22130    executor.run_until_parked();
22131
22132    let both_hunks_expanded = r#"
22133        - a
22134        + ˇA
22135          b
22136        - c
22137        + C
22138        "#
22139    .unindent();
22140
22141    cx.assert_state_with_diff(both_hunks_expanded.clone());
22142
22143    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22144        let snapshot = editor.snapshot(window, cx);
22145        let hunks = editor
22146            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22147            .collect::<Vec<_>>();
22148        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22149        hunks
22150            .into_iter()
22151            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22152            .collect::<Vec<_>>()
22153    });
22154    assert_eq!(hunk_ranges.len(), 2);
22155
22156    cx.update_editor(|editor, _, cx| {
22157        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22158    });
22159    executor.run_until_parked();
22160
22161    let second_hunk_expanded = r#"
22162          ˇA
22163          b
22164        - c
22165        + C
22166        "#
22167    .unindent();
22168
22169    cx.assert_state_with_diff(second_hunk_expanded);
22170
22171    cx.update_editor(|editor, _, cx| {
22172        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22173    });
22174    executor.run_until_parked();
22175
22176    cx.assert_state_with_diff(both_hunks_expanded.clone());
22177
22178    cx.update_editor(|editor, _, cx| {
22179        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22180    });
22181    executor.run_until_parked();
22182
22183    let first_hunk_expanded = r#"
22184        - a
22185        + ˇA
22186          b
22187          C
22188        "#
22189    .unindent();
22190
22191    cx.assert_state_with_diff(first_hunk_expanded);
22192
22193    cx.update_editor(|editor, _, cx| {
22194        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22195    });
22196    executor.run_until_parked();
22197
22198    cx.assert_state_with_diff(both_hunks_expanded);
22199
22200    cx.set_state(
22201        &r#"
22202        ˇA
22203        b
22204        "#
22205        .unindent(),
22206    );
22207    cx.run_until_parked();
22208
22209    // TODO this cursor position seems bad
22210    cx.assert_state_with_diff(
22211        r#"
22212        - ˇa
22213        + A
22214          b
22215        "#
22216        .unindent(),
22217    );
22218
22219    cx.update_editor(|editor, window, cx| {
22220        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22221    });
22222
22223    cx.assert_state_with_diff(
22224        r#"
22225            - ˇa
22226            + A
22227              b
22228            - c
22229            "#
22230        .unindent(),
22231    );
22232
22233    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22234        let snapshot = editor.snapshot(window, cx);
22235        let hunks = editor
22236            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22237            .collect::<Vec<_>>();
22238        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22239        hunks
22240            .into_iter()
22241            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22242            .collect::<Vec<_>>()
22243    });
22244    assert_eq!(hunk_ranges.len(), 2);
22245
22246    cx.update_editor(|editor, _, cx| {
22247        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22248    });
22249    executor.run_until_parked();
22250
22251    cx.assert_state_with_diff(
22252        r#"
22253        - ˇa
22254        + A
22255          b
22256        "#
22257        .unindent(),
22258    );
22259}
22260
22261#[gpui::test]
22262async fn test_toggle_deletion_hunk_at_start_of_file(
22263    executor: BackgroundExecutor,
22264    cx: &mut TestAppContext,
22265) {
22266    init_test(cx, |_| {});
22267    let mut cx = EditorTestContext::new(cx).await;
22268
22269    let diff_base = r#"
22270        a
22271        b
22272        c
22273        "#
22274    .unindent();
22275
22276    cx.set_state(
22277        &r#"
22278        ˇb
22279        c
22280        "#
22281        .unindent(),
22282    );
22283    cx.set_head_text(&diff_base);
22284    cx.update_editor(|editor, window, cx| {
22285        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22286    });
22287    executor.run_until_parked();
22288
22289    let hunk_expanded = r#"
22290        - a
22291          ˇb
22292          c
22293        "#
22294    .unindent();
22295
22296    cx.assert_state_with_diff(hunk_expanded.clone());
22297
22298    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22299        let snapshot = editor.snapshot(window, cx);
22300        let hunks = editor
22301            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22302            .collect::<Vec<_>>();
22303        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22304        hunks
22305            .into_iter()
22306            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22307            .collect::<Vec<_>>()
22308    });
22309    assert_eq!(hunk_ranges.len(), 1);
22310
22311    cx.update_editor(|editor, _, cx| {
22312        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22313    });
22314    executor.run_until_parked();
22315
22316    let hunk_collapsed = r#"
22317          ˇb
22318          c
22319        "#
22320    .unindent();
22321
22322    cx.assert_state_with_diff(hunk_collapsed);
22323
22324    cx.update_editor(|editor, _, cx| {
22325        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22326    });
22327    executor.run_until_parked();
22328
22329    cx.assert_state_with_diff(hunk_expanded);
22330}
22331
22332#[gpui::test]
22333async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22334    executor: BackgroundExecutor,
22335    cx: &mut TestAppContext,
22336) {
22337    init_test(cx, |_| {});
22338    let mut cx = EditorTestContext::new(cx).await;
22339
22340    cx.set_state("ˇnew\nsecond\nthird\n");
22341    cx.set_head_text("old\nsecond\nthird\n");
22342    cx.update_editor(|editor, window, cx| {
22343        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22344    });
22345    executor.run_until_parked();
22346    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22347
22348    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22349    cx.update_editor(|editor, window, cx| {
22350        let snapshot = editor.snapshot(window, cx);
22351        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22352        let hunks = editor
22353            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22354            .collect::<Vec<_>>();
22355        assert_eq!(hunks.len(), 1);
22356        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22357        editor.toggle_single_diff_hunk(hunk_range, cx)
22358    });
22359    executor.run_until_parked();
22360    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22361
22362    // Keep the editor scrolled to the top so the full hunk remains visible.
22363    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22364}
22365
22366#[gpui::test]
22367async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22368    init_test(cx, |_| {});
22369
22370    let fs = FakeFs::new(cx.executor());
22371    fs.insert_tree(
22372        path!("/test"),
22373        json!({
22374            ".git": {},
22375            "file-1": "ONE\n",
22376            "file-2": "TWO\n",
22377            "file-3": "THREE\n",
22378        }),
22379    )
22380    .await;
22381
22382    fs.set_head_for_repo(
22383        path!("/test/.git").as_ref(),
22384        &[
22385            ("file-1", "one\n".into()),
22386            ("file-2", "two\n".into()),
22387            ("file-3", "three\n".into()),
22388        ],
22389        "deadbeef",
22390    );
22391
22392    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22393    let mut buffers = vec![];
22394    for i in 1..=3 {
22395        let buffer = project
22396            .update(cx, |project, cx| {
22397                let path = format!(path!("/test/file-{}"), i);
22398                project.open_local_buffer(path, cx)
22399            })
22400            .await
22401            .unwrap();
22402        buffers.push(buffer);
22403    }
22404
22405    let multibuffer = cx.new(|cx| {
22406        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22407        multibuffer.set_all_diff_hunks_expanded(cx);
22408        for buffer in &buffers {
22409            let snapshot = buffer.read(cx).snapshot();
22410            multibuffer.set_excerpts_for_path(
22411                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22412                buffer.clone(),
22413                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22414                2,
22415                cx,
22416            );
22417        }
22418        multibuffer
22419    });
22420
22421    let editor = cx.add_window(|window, cx| {
22422        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22423    });
22424    cx.run_until_parked();
22425
22426    let snapshot = editor
22427        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22428        .unwrap();
22429    let hunks = snapshot
22430        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22431        .map(|hunk| match hunk {
22432            DisplayDiffHunk::Unfolded {
22433                display_row_range, ..
22434            } => display_row_range,
22435            DisplayDiffHunk::Folded { .. } => unreachable!(),
22436        })
22437        .collect::<Vec<_>>();
22438    assert_eq!(
22439        hunks,
22440        [
22441            DisplayRow(2)..DisplayRow(4),
22442            DisplayRow(7)..DisplayRow(9),
22443            DisplayRow(12)..DisplayRow(14),
22444        ]
22445    );
22446}
22447
22448#[gpui::test]
22449async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22450    init_test(cx, |_| {});
22451
22452    let mut cx = EditorTestContext::new(cx).await;
22453    cx.set_head_text(indoc! { "
22454        one
22455        two
22456        three
22457        four
22458        five
22459        "
22460    });
22461    cx.set_index_text(indoc! { "
22462        one
22463        two
22464        three
22465        four
22466        five
22467        "
22468    });
22469    cx.set_state(indoc! {"
22470        one
22471        TWO
22472        ˇTHREE
22473        FOUR
22474        five
22475    "});
22476    cx.run_until_parked();
22477    cx.update_editor(|editor, window, cx| {
22478        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22479    });
22480    cx.run_until_parked();
22481    cx.assert_index_text(Some(indoc! {"
22482        one
22483        TWO
22484        THREE
22485        FOUR
22486        five
22487    "}));
22488    cx.set_state(indoc! { "
22489        one
22490        TWO
22491        ˇTHREE-HUNDRED
22492        FOUR
22493        five
22494    "});
22495    cx.run_until_parked();
22496    cx.update_editor(|editor, window, cx| {
22497        let snapshot = editor.snapshot(window, cx);
22498        let hunks = editor
22499            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22500            .collect::<Vec<_>>();
22501        assert_eq!(hunks.len(), 1);
22502        assert_eq!(
22503            hunks[0].status(),
22504            DiffHunkStatus {
22505                kind: DiffHunkStatusKind::Modified,
22506                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22507            }
22508        );
22509
22510        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22511    });
22512    cx.run_until_parked();
22513    cx.assert_index_text(Some(indoc! {"
22514        one
22515        TWO
22516        THREE-HUNDRED
22517        FOUR
22518        five
22519    "}));
22520}
22521
22522#[gpui::test]
22523fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22524    init_test(cx, |_| {});
22525
22526    let editor = cx.add_window(|window, cx| {
22527        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22528        build_editor(buffer, window, cx)
22529    });
22530
22531    let render_args = Arc::new(Mutex::new(None));
22532    let snapshot = editor
22533        .update(cx, |editor, window, cx| {
22534            let snapshot = editor.buffer().read(cx).snapshot(cx);
22535            let range =
22536                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22537
22538            struct RenderArgs {
22539                row: MultiBufferRow,
22540                folded: bool,
22541                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22542            }
22543
22544            let crease = Crease::inline(
22545                range,
22546                FoldPlaceholder::test(),
22547                {
22548                    let toggle_callback = render_args.clone();
22549                    move |row, folded, callback, _window, _cx| {
22550                        *toggle_callback.lock() = Some(RenderArgs {
22551                            row,
22552                            folded,
22553                            callback,
22554                        });
22555                        div()
22556                    }
22557                },
22558                |_row, _folded, _window, _cx| div(),
22559            );
22560
22561            editor.insert_creases(Some(crease), cx);
22562            let snapshot = editor.snapshot(window, cx);
22563            let _div =
22564                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22565            snapshot
22566        })
22567        .unwrap();
22568
22569    let render_args = render_args.lock().take().unwrap();
22570    assert_eq!(render_args.row, MultiBufferRow(1));
22571    assert!(!render_args.folded);
22572    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22573
22574    cx.update_window(*editor, |_, window, cx| {
22575        (render_args.callback)(true, window, cx)
22576    })
22577    .unwrap();
22578    let snapshot = editor
22579        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22580        .unwrap();
22581    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22582
22583    cx.update_window(*editor, |_, window, cx| {
22584        (render_args.callback)(false, window, cx)
22585    })
22586    .unwrap();
22587    let snapshot = editor
22588        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22589        .unwrap();
22590    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22591}
22592
22593#[gpui::test]
22594async fn test_input_text(cx: &mut TestAppContext) {
22595    init_test(cx, |_| {});
22596    let mut cx = EditorTestContext::new(cx).await;
22597
22598    cx.set_state(
22599        &r#"ˇone
22600        two
22601
22602        three
22603        fourˇ
22604        five
22605
22606        siˇx"#
22607            .unindent(),
22608    );
22609
22610    cx.dispatch_action(HandleInput(String::new()));
22611    cx.assert_editor_state(
22612        &r#"ˇone
22613        two
22614
22615        three
22616        fourˇ
22617        five
22618
22619        siˇx"#
22620            .unindent(),
22621    );
22622
22623    cx.dispatch_action(HandleInput("AAAA".to_string()));
22624    cx.assert_editor_state(
22625        &r#"AAAAˇone
22626        two
22627
22628        three
22629        fourAAAAˇ
22630        five
22631
22632        siAAAAˇx"#
22633            .unindent(),
22634    );
22635}
22636
22637#[gpui::test]
22638async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22639    init_test(cx, |_| {});
22640
22641    let mut cx = EditorTestContext::new(cx).await;
22642    cx.set_state(
22643        r#"let foo = 1;
22644let foo = 2;
22645let foo = 3;
22646let fooˇ = 4;
22647let foo = 5;
22648let foo = 6;
22649let foo = 7;
22650let foo = 8;
22651let foo = 9;
22652let foo = 10;
22653let foo = 11;
22654let foo = 12;
22655let foo = 13;
22656let foo = 14;
22657let foo = 15;"#,
22658    );
22659
22660    cx.update_editor(|e, window, cx| {
22661        assert_eq!(
22662            e.next_scroll_position,
22663            NextScrollCursorCenterTopBottom::Center,
22664            "Default next scroll direction is center",
22665        );
22666
22667        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22668        assert_eq!(
22669            e.next_scroll_position,
22670            NextScrollCursorCenterTopBottom::Top,
22671            "After center, next scroll direction should be top",
22672        );
22673
22674        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22675        assert_eq!(
22676            e.next_scroll_position,
22677            NextScrollCursorCenterTopBottom::Bottom,
22678            "After top, next scroll direction should be bottom",
22679        );
22680
22681        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22682        assert_eq!(
22683            e.next_scroll_position,
22684            NextScrollCursorCenterTopBottom::Center,
22685            "After bottom, scrolling should start over",
22686        );
22687
22688        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22689        assert_eq!(
22690            e.next_scroll_position,
22691            NextScrollCursorCenterTopBottom::Top,
22692            "Scrolling continues if retriggered fast enough"
22693        );
22694    });
22695
22696    cx.executor()
22697        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22698    cx.executor().run_until_parked();
22699    cx.update_editor(|e, _, _| {
22700        assert_eq!(
22701            e.next_scroll_position,
22702            NextScrollCursorCenterTopBottom::Center,
22703            "If scrolling is not triggered fast enough, it should reset"
22704        );
22705    });
22706}
22707
22708#[gpui::test]
22709async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22710    init_test(cx, |_| {});
22711    let mut cx = EditorLspTestContext::new_rust(
22712        lsp::ServerCapabilities {
22713            definition_provider: Some(lsp::OneOf::Left(true)),
22714            references_provider: Some(lsp::OneOf::Left(true)),
22715            ..lsp::ServerCapabilities::default()
22716        },
22717        cx,
22718    )
22719    .await;
22720
22721    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22722        let go_to_definition = cx
22723            .lsp
22724            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22725                move |params, _| async move {
22726                    if empty_go_to_definition {
22727                        Ok(None)
22728                    } else {
22729                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22730                            uri: params.text_document_position_params.text_document.uri,
22731                            range: lsp::Range::new(
22732                                lsp::Position::new(4, 3),
22733                                lsp::Position::new(4, 6),
22734                            ),
22735                        })))
22736                    }
22737                },
22738            );
22739        let references = cx
22740            .lsp
22741            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22742                Ok(Some(vec![lsp::Location {
22743                    uri: params.text_document_position.text_document.uri,
22744                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22745                }]))
22746            });
22747        (go_to_definition, references)
22748    };
22749
22750    cx.set_state(
22751        &r#"fn one() {
22752            let mut a = ˇtwo();
22753        }
22754
22755        fn two() {}"#
22756            .unindent(),
22757    );
22758    set_up_lsp_handlers(false, &mut cx);
22759    let navigated = cx
22760        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22761        .await
22762        .expect("Failed to navigate to definition");
22763    assert_eq!(
22764        navigated,
22765        Navigated::Yes,
22766        "Should have navigated to definition from the GetDefinition response"
22767    );
22768    cx.assert_editor_state(
22769        &r#"fn one() {
22770            let mut a = two();
22771        }
22772
22773        fn «twoˇ»() {}"#
22774            .unindent(),
22775    );
22776
22777    let editors = cx.update_workspace(|workspace, _, cx| {
22778        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22779    });
22780    cx.update_editor(|_, _, test_editor_cx| {
22781        assert_eq!(
22782            editors.len(),
22783            1,
22784            "Initially, only one, test, editor should be open in the workspace"
22785        );
22786        assert_eq!(
22787            test_editor_cx.entity(),
22788            editors.last().expect("Asserted len is 1").clone()
22789        );
22790    });
22791
22792    set_up_lsp_handlers(true, &mut cx);
22793    let navigated = cx
22794        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22795        .await
22796        .expect("Failed to navigate to lookup references");
22797    assert_eq!(
22798        navigated,
22799        Navigated::Yes,
22800        "Should have navigated to references as a fallback after empty GoToDefinition response"
22801    );
22802    // We should not change the selections in the existing file,
22803    // if opening another milti buffer with the references
22804    cx.assert_editor_state(
22805        &r#"fn one() {
22806            let mut a = two();
22807        }
22808
22809        fn «twoˇ»() {}"#
22810            .unindent(),
22811    );
22812    let editors = cx.update_workspace(|workspace, _, cx| {
22813        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22814    });
22815    cx.update_editor(|_, _, test_editor_cx| {
22816        assert_eq!(
22817            editors.len(),
22818            2,
22819            "After falling back to references search, we open a new editor with the results"
22820        );
22821        let references_fallback_text = editors
22822            .into_iter()
22823            .find(|new_editor| *new_editor != test_editor_cx.entity())
22824            .expect("Should have one non-test editor now")
22825            .read(test_editor_cx)
22826            .text(test_editor_cx);
22827        assert_eq!(
22828            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22829            "Should use the range from the references response and not the GoToDefinition one"
22830        );
22831    });
22832}
22833
22834#[gpui::test]
22835async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22836    init_test(cx, |_| {});
22837    cx.update(|cx| {
22838        let mut editor_settings = EditorSettings::get_global(cx).clone();
22839        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22840        EditorSettings::override_global(editor_settings, cx);
22841    });
22842    let mut cx = EditorLspTestContext::new_rust(
22843        lsp::ServerCapabilities {
22844            definition_provider: Some(lsp::OneOf::Left(true)),
22845            references_provider: Some(lsp::OneOf::Left(true)),
22846            ..lsp::ServerCapabilities::default()
22847        },
22848        cx,
22849    )
22850    .await;
22851    let original_state = r#"fn one() {
22852        let mut a = ˇtwo();
22853    }
22854
22855    fn two() {}"#
22856        .unindent();
22857    cx.set_state(&original_state);
22858
22859    let mut go_to_definition = cx
22860        .lsp
22861        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22862            move |_, _| async move { Ok(None) },
22863        );
22864    let _references = cx
22865        .lsp
22866        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22867            panic!("Should not call for references with no go to definition fallback")
22868        });
22869
22870    let navigated = cx
22871        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22872        .await
22873        .expect("Failed to navigate to lookup references");
22874    go_to_definition
22875        .next()
22876        .await
22877        .expect("Should have called the go_to_definition handler");
22878
22879    assert_eq!(
22880        navigated,
22881        Navigated::No,
22882        "Should have navigated to references as a fallback after empty GoToDefinition response"
22883    );
22884    cx.assert_editor_state(&original_state);
22885    let editors = cx.update_workspace(|workspace, _, cx| {
22886        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22887    });
22888    cx.update_editor(|_, _, _| {
22889        assert_eq!(
22890            editors.len(),
22891            1,
22892            "After unsuccessful fallback, no other editor should have been opened"
22893        );
22894    });
22895}
22896
22897#[gpui::test]
22898async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22899    init_test(cx, |_| {});
22900    let mut cx = EditorLspTestContext::new_rust(
22901        lsp::ServerCapabilities {
22902            references_provider: Some(lsp::OneOf::Left(true)),
22903            ..lsp::ServerCapabilities::default()
22904        },
22905        cx,
22906    )
22907    .await;
22908
22909    cx.set_state(
22910        &r#"
22911        fn one() {
22912            let mut a = two();
22913        }
22914
22915        fn ˇtwo() {}"#
22916            .unindent(),
22917    );
22918    cx.lsp
22919        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22920            Ok(Some(vec![
22921                lsp::Location {
22922                    uri: params.text_document_position.text_document.uri.clone(),
22923                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22924                },
22925                lsp::Location {
22926                    uri: params.text_document_position.text_document.uri,
22927                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22928                },
22929            ]))
22930        });
22931    let navigated = cx
22932        .update_editor(|editor, window, cx| {
22933            editor.find_all_references(&FindAllReferences::default(), window, cx)
22934        })
22935        .unwrap()
22936        .await
22937        .expect("Failed to navigate to references");
22938    assert_eq!(
22939        navigated,
22940        Navigated::Yes,
22941        "Should have navigated to references from the FindAllReferences response"
22942    );
22943    cx.assert_editor_state(
22944        &r#"fn one() {
22945            let mut a = two();
22946        }
22947
22948        fn ˇtwo() {}"#
22949            .unindent(),
22950    );
22951
22952    let editors = cx.update_workspace(|workspace, _, cx| {
22953        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22954    });
22955    cx.update_editor(|_, _, _| {
22956        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22957    });
22958
22959    cx.set_state(
22960        &r#"fn one() {
22961            let mut a = ˇtwo();
22962        }
22963
22964        fn two() {}"#
22965            .unindent(),
22966    );
22967    let navigated = cx
22968        .update_editor(|editor, window, cx| {
22969            editor.find_all_references(&FindAllReferences::default(), window, cx)
22970        })
22971        .unwrap()
22972        .await
22973        .expect("Failed to navigate to references");
22974    assert_eq!(
22975        navigated,
22976        Navigated::Yes,
22977        "Should have navigated to references from the FindAllReferences response"
22978    );
22979    cx.assert_editor_state(
22980        &r#"fn one() {
22981            let mut a = ˇtwo();
22982        }
22983
22984        fn two() {}"#
22985            .unindent(),
22986    );
22987    let editors = cx.update_workspace(|workspace, _, cx| {
22988        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22989    });
22990    cx.update_editor(|_, _, _| {
22991        assert_eq!(
22992            editors.len(),
22993            2,
22994            "should have re-used the previous multibuffer"
22995        );
22996    });
22997
22998    cx.set_state(
22999        &r#"fn one() {
23000            let mut a = ˇtwo();
23001        }
23002        fn three() {}
23003        fn two() {}"#
23004            .unindent(),
23005    );
23006    cx.lsp
23007        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23008            Ok(Some(vec![
23009                lsp::Location {
23010                    uri: params.text_document_position.text_document.uri.clone(),
23011                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23012                },
23013                lsp::Location {
23014                    uri: params.text_document_position.text_document.uri,
23015                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23016                },
23017            ]))
23018        });
23019    let navigated = cx
23020        .update_editor(|editor, window, cx| {
23021            editor.find_all_references(&FindAllReferences::default(), window, cx)
23022        })
23023        .unwrap()
23024        .await
23025        .expect("Failed to navigate to references");
23026    assert_eq!(
23027        navigated,
23028        Navigated::Yes,
23029        "Should have navigated to references from the FindAllReferences response"
23030    );
23031    cx.assert_editor_state(
23032        &r#"fn one() {
23033                let mut a = ˇtwo();
23034            }
23035            fn three() {}
23036            fn two() {}"#
23037            .unindent(),
23038    );
23039    let editors = cx.update_workspace(|workspace, _, cx| {
23040        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23041    });
23042    cx.update_editor(|_, _, _| {
23043        assert_eq!(
23044            editors.len(),
23045            3,
23046            "should have used a new multibuffer as offsets changed"
23047        );
23048    });
23049}
23050#[gpui::test]
23051async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23052    init_test(cx, |_| {});
23053
23054    let language = Arc::new(Language::new(
23055        LanguageConfig::default(),
23056        Some(tree_sitter_rust::LANGUAGE.into()),
23057    ));
23058
23059    let text = r#"
23060        #[cfg(test)]
23061        mod tests() {
23062            #[test]
23063            fn runnable_1() {
23064                let a = 1;
23065            }
23066
23067            #[test]
23068            fn runnable_2() {
23069                let a = 1;
23070                let b = 2;
23071            }
23072        }
23073    "#
23074    .unindent();
23075
23076    let fs = FakeFs::new(cx.executor());
23077    fs.insert_file("/file.rs", Default::default()).await;
23078
23079    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23080    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23081    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23082    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23083    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23084
23085    let editor = cx.new_window_entity(|window, cx| {
23086        Editor::new(
23087            EditorMode::full(),
23088            multi_buffer,
23089            Some(project.clone()),
23090            window,
23091            cx,
23092        )
23093    });
23094
23095    editor.update_in(cx, |editor, window, cx| {
23096        let snapshot = editor.buffer().read(cx).snapshot(cx);
23097        editor.tasks.insert(
23098            (buffer.read(cx).remote_id(), 3),
23099            RunnableTasks {
23100                templates: vec![],
23101                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23102                column: 0,
23103                extra_variables: HashMap::default(),
23104                context_range: BufferOffset(43)..BufferOffset(85),
23105            },
23106        );
23107        editor.tasks.insert(
23108            (buffer.read(cx).remote_id(), 8),
23109            RunnableTasks {
23110                templates: vec![],
23111                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23112                column: 0,
23113                extra_variables: HashMap::default(),
23114                context_range: BufferOffset(86)..BufferOffset(191),
23115            },
23116        );
23117
23118        // Test finding task when cursor is inside function body
23119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23120            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23121        });
23122        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23123        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23124
23125        // Test finding task when cursor is on function name
23126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23127            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23128        });
23129        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23130        assert_eq!(row, 8, "Should find task when cursor is on function name");
23131    });
23132}
23133
23134#[gpui::test]
23135async fn test_folding_buffers(cx: &mut TestAppContext) {
23136    init_test(cx, |_| {});
23137
23138    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23139    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23140    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23141
23142    let fs = FakeFs::new(cx.executor());
23143    fs.insert_tree(
23144        path!("/a"),
23145        json!({
23146            "first.rs": sample_text_1,
23147            "second.rs": sample_text_2,
23148            "third.rs": sample_text_3,
23149        }),
23150    )
23151    .await;
23152    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23153    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23154    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23155    let worktree = project.update(cx, |project, cx| {
23156        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23157        assert_eq!(worktrees.len(), 1);
23158        worktrees.pop().unwrap()
23159    });
23160    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23161
23162    let buffer_1 = project
23163        .update(cx, |project, cx| {
23164            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23165        })
23166        .await
23167        .unwrap();
23168    let buffer_2 = project
23169        .update(cx, |project, cx| {
23170            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23171        })
23172        .await
23173        .unwrap();
23174    let buffer_3 = project
23175        .update(cx, |project, cx| {
23176            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23177        })
23178        .await
23179        .unwrap();
23180
23181    let multi_buffer = cx.new(|cx| {
23182        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23183        multi_buffer.push_excerpts(
23184            buffer_1.clone(),
23185            [
23186                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23187                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23188                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23189            ],
23190            cx,
23191        );
23192        multi_buffer.push_excerpts(
23193            buffer_2.clone(),
23194            [
23195                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23196                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23197                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23198            ],
23199            cx,
23200        );
23201        multi_buffer.push_excerpts(
23202            buffer_3.clone(),
23203            [
23204                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23205                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23206                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23207            ],
23208            cx,
23209        );
23210        multi_buffer
23211    });
23212    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23213        Editor::new(
23214            EditorMode::full(),
23215            multi_buffer.clone(),
23216            Some(project.clone()),
23217            window,
23218            cx,
23219        )
23220    });
23221
23222    assert_eq!(
23223        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23224        "\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",
23225    );
23226
23227    multi_buffer_editor.update(cx, |editor, cx| {
23228        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23229    });
23230    assert_eq!(
23231        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23232        "\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",
23233        "After folding the first buffer, its text should not be displayed"
23234    );
23235
23236    multi_buffer_editor.update(cx, |editor, cx| {
23237        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23238    });
23239    assert_eq!(
23240        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23241        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23242        "After folding the second buffer, its text should not be displayed"
23243    );
23244
23245    multi_buffer_editor.update(cx, |editor, cx| {
23246        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23247    });
23248    assert_eq!(
23249        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23250        "\n\n\n\n\n",
23251        "After folding the third buffer, its text should not be displayed"
23252    );
23253
23254    // Emulate selection inside the fold logic, that should work
23255    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23256        editor
23257            .snapshot(window, cx)
23258            .next_line_boundary(Point::new(0, 4));
23259    });
23260
23261    multi_buffer_editor.update(cx, |editor, cx| {
23262        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23263    });
23264    assert_eq!(
23265        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23266        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23267        "After unfolding the second buffer, its text should be displayed"
23268    );
23269
23270    // Typing inside of buffer 1 causes that buffer to be unfolded.
23271    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23272        assert_eq!(
23273            multi_buffer
23274                .read(cx)
23275                .snapshot(cx)
23276                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23277                .collect::<String>(),
23278            "bbbb"
23279        );
23280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23281            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23282        });
23283        editor.handle_input("B", window, cx);
23284    });
23285
23286    assert_eq!(
23287        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23288        "\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",
23289        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23290    );
23291
23292    multi_buffer_editor.update(cx, |editor, cx| {
23293        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23294    });
23295    assert_eq!(
23296        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23297        "\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",
23298        "After unfolding the all buffers, all original text should be displayed"
23299    );
23300}
23301
23302#[gpui::test]
23303async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23304    init_test(cx, |_| {});
23305
23306    let sample_text_1 = "1111\n2222\n3333".to_string();
23307    let sample_text_2 = "4444\n5555\n6666".to_string();
23308    let sample_text_3 = "7777\n8888\n9999".to_string();
23309
23310    let fs = FakeFs::new(cx.executor());
23311    fs.insert_tree(
23312        path!("/a"),
23313        json!({
23314            "first.rs": sample_text_1,
23315            "second.rs": sample_text_2,
23316            "third.rs": sample_text_3,
23317        }),
23318    )
23319    .await;
23320    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23321    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23322    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23323    let worktree = project.update(cx, |project, cx| {
23324        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23325        assert_eq!(worktrees.len(), 1);
23326        worktrees.pop().unwrap()
23327    });
23328    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23329
23330    let buffer_1 = project
23331        .update(cx, |project, cx| {
23332            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23333        })
23334        .await
23335        .unwrap();
23336    let buffer_2 = project
23337        .update(cx, |project, cx| {
23338            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23339        })
23340        .await
23341        .unwrap();
23342    let buffer_3 = project
23343        .update(cx, |project, cx| {
23344            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23345        })
23346        .await
23347        .unwrap();
23348
23349    let multi_buffer = cx.new(|cx| {
23350        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23351        multi_buffer.push_excerpts(
23352            buffer_1.clone(),
23353            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23354            cx,
23355        );
23356        multi_buffer.push_excerpts(
23357            buffer_2.clone(),
23358            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23359            cx,
23360        );
23361        multi_buffer.push_excerpts(
23362            buffer_3.clone(),
23363            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23364            cx,
23365        );
23366        multi_buffer
23367    });
23368
23369    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23370        Editor::new(
23371            EditorMode::full(),
23372            multi_buffer,
23373            Some(project.clone()),
23374            window,
23375            cx,
23376        )
23377    });
23378
23379    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23380    assert_eq!(
23381        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23382        full_text,
23383    );
23384
23385    multi_buffer_editor.update(cx, |editor, cx| {
23386        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23387    });
23388    assert_eq!(
23389        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23390        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23391        "After folding the first buffer, its text should not be displayed"
23392    );
23393
23394    multi_buffer_editor.update(cx, |editor, cx| {
23395        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23396    });
23397
23398    assert_eq!(
23399        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23400        "\n\n\n\n\n\n7777\n8888\n9999",
23401        "After folding the second buffer, its text should not be displayed"
23402    );
23403
23404    multi_buffer_editor.update(cx, |editor, cx| {
23405        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23406    });
23407    assert_eq!(
23408        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23409        "\n\n\n\n\n",
23410        "After folding the third buffer, its text should not be displayed"
23411    );
23412
23413    multi_buffer_editor.update(cx, |editor, cx| {
23414        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23415    });
23416    assert_eq!(
23417        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23418        "\n\n\n\n4444\n5555\n6666\n\n",
23419        "After unfolding the second buffer, its text should be displayed"
23420    );
23421
23422    multi_buffer_editor.update(cx, |editor, cx| {
23423        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23424    });
23425    assert_eq!(
23426        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23427        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23428        "After unfolding the first buffer, its text should be displayed"
23429    );
23430
23431    multi_buffer_editor.update(cx, |editor, cx| {
23432        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23433    });
23434    assert_eq!(
23435        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23436        full_text,
23437        "After unfolding all buffers, all original text should be displayed"
23438    );
23439}
23440
23441#[gpui::test]
23442async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23443    init_test(cx, |_| {});
23444
23445    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23446
23447    let fs = FakeFs::new(cx.executor());
23448    fs.insert_tree(
23449        path!("/a"),
23450        json!({
23451            "main.rs": sample_text,
23452        }),
23453    )
23454    .await;
23455    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23456    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23457    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23458    let worktree = project.update(cx, |project, cx| {
23459        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23460        assert_eq!(worktrees.len(), 1);
23461        worktrees.pop().unwrap()
23462    });
23463    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23464
23465    let buffer_1 = project
23466        .update(cx, |project, cx| {
23467            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23468        })
23469        .await
23470        .unwrap();
23471
23472    let multi_buffer = cx.new(|cx| {
23473        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23474        multi_buffer.push_excerpts(
23475            buffer_1.clone(),
23476            [ExcerptRange::new(
23477                Point::new(0, 0)
23478                    ..Point::new(
23479                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23480                        0,
23481                    ),
23482            )],
23483            cx,
23484        );
23485        multi_buffer
23486    });
23487    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23488        Editor::new(
23489            EditorMode::full(),
23490            multi_buffer,
23491            Some(project.clone()),
23492            window,
23493            cx,
23494        )
23495    });
23496
23497    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23498    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23499        enum TestHighlight {}
23500        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23501        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23502        editor.highlight_text::<TestHighlight>(
23503            vec![highlight_range.clone()],
23504            HighlightStyle::color(Hsla::green()),
23505            cx,
23506        );
23507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23508            s.select_ranges(Some(highlight_range))
23509        });
23510    });
23511
23512    let full_text = format!("\n\n{sample_text}");
23513    assert_eq!(
23514        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23515        full_text,
23516    );
23517}
23518
23519#[gpui::test]
23520async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23521    init_test(cx, |_| {});
23522    cx.update(|cx| {
23523        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23524            "keymaps/default-linux.json",
23525            cx,
23526        )
23527        .unwrap();
23528        cx.bind_keys(default_key_bindings);
23529    });
23530
23531    let (editor, cx) = cx.add_window_view(|window, cx| {
23532        let multi_buffer = MultiBuffer::build_multi(
23533            [
23534                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23535                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23536                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23537                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23538            ],
23539            cx,
23540        );
23541        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23542
23543        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23544        // fold all but the second buffer, so that we test navigating between two
23545        // adjacent folded buffers, as well as folded buffers at the start and
23546        // end the multibuffer
23547        editor.fold_buffer(buffer_ids[0], cx);
23548        editor.fold_buffer(buffer_ids[2], cx);
23549        editor.fold_buffer(buffer_ids[3], cx);
23550
23551        editor
23552    });
23553    cx.simulate_resize(size(px(1000.), px(1000.)));
23554
23555    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23556    cx.assert_excerpts_with_selections(indoc! {"
23557        [EXCERPT]
23558        ˇ[FOLDED]
23559        [EXCERPT]
23560        a1
23561        b1
23562        [EXCERPT]
23563        [FOLDED]
23564        [EXCERPT]
23565        [FOLDED]
23566        "
23567    });
23568    cx.simulate_keystroke("down");
23569    cx.assert_excerpts_with_selections(indoc! {"
23570        [EXCERPT]
23571        [FOLDED]
23572        [EXCERPT]
23573        ˇa1
23574        b1
23575        [EXCERPT]
23576        [FOLDED]
23577        [EXCERPT]
23578        [FOLDED]
23579        "
23580    });
23581    cx.simulate_keystroke("down");
23582    cx.assert_excerpts_with_selections(indoc! {"
23583        [EXCERPT]
23584        [FOLDED]
23585        [EXCERPT]
23586        a1
23587        ˇb1
23588        [EXCERPT]
23589        [FOLDED]
23590        [EXCERPT]
23591        [FOLDED]
23592        "
23593    });
23594    cx.simulate_keystroke("down");
23595    cx.assert_excerpts_with_selections(indoc! {"
23596        [EXCERPT]
23597        [FOLDED]
23598        [EXCERPT]
23599        a1
23600        b1
23601        ˇ[EXCERPT]
23602        [FOLDED]
23603        [EXCERPT]
23604        [FOLDED]
23605        "
23606    });
23607    cx.simulate_keystroke("down");
23608    cx.assert_excerpts_with_selections(indoc! {"
23609        [EXCERPT]
23610        [FOLDED]
23611        [EXCERPT]
23612        a1
23613        b1
23614        [EXCERPT]
23615        ˇ[FOLDED]
23616        [EXCERPT]
23617        [FOLDED]
23618        "
23619    });
23620    for _ in 0..5 {
23621        cx.simulate_keystroke("down");
23622        cx.assert_excerpts_with_selections(indoc! {"
23623            [EXCERPT]
23624            [FOLDED]
23625            [EXCERPT]
23626            a1
23627            b1
23628            [EXCERPT]
23629            [FOLDED]
23630            [EXCERPT]
23631            ˇ[FOLDED]
23632            "
23633        });
23634    }
23635
23636    cx.simulate_keystroke("up");
23637    cx.assert_excerpts_with_selections(indoc! {"
23638        [EXCERPT]
23639        [FOLDED]
23640        [EXCERPT]
23641        a1
23642        b1
23643        [EXCERPT]
23644        ˇ[FOLDED]
23645        [EXCERPT]
23646        [FOLDED]
23647        "
23648    });
23649    cx.simulate_keystroke("up");
23650    cx.assert_excerpts_with_selections(indoc! {"
23651        [EXCERPT]
23652        [FOLDED]
23653        [EXCERPT]
23654        a1
23655        b1
23656        ˇ[EXCERPT]
23657        [FOLDED]
23658        [EXCERPT]
23659        [FOLDED]
23660        "
23661    });
23662    cx.simulate_keystroke("up");
23663    cx.assert_excerpts_with_selections(indoc! {"
23664        [EXCERPT]
23665        [FOLDED]
23666        [EXCERPT]
23667        a1
23668        ˇb1
23669        [EXCERPT]
23670        [FOLDED]
23671        [EXCERPT]
23672        [FOLDED]
23673        "
23674    });
23675    cx.simulate_keystroke("up");
23676    cx.assert_excerpts_with_selections(indoc! {"
23677        [EXCERPT]
23678        [FOLDED]
23679        [EXCERPT]
23680        ˇa1
23681        b1
23682        [EXCERPT]
23683        [FOLDED]
23684        [EXCERPT]
23685        [FOLDED]
23686        "
23687    });
23688    for _ in 0..5 {
23689        cx.simulate_keystroke("up");
23690        cx.assert_excerpts_with_selections(indoc! {"
23691            [EXCERPT]
23692            ˇ[FOLDED]
23693            [EXCERPT]
23694            a1
23695            b1
23696            [EXCERPT]
23697            [FOLDED]
23698            [EXCERPT]
23699            [FOLDED]
23700            "
23701        });
23702    }
23703}
23704
23705#[gpui::test]
23706async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23707    init_test(cx, |_| {});
23708
23709    // Simple insertion
23710    assert_highlighted_edits(
23711        "Hello, world!",
23712        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23713        true,
23714        cx,
23715        |highlighted_edits, cx| {
23716            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23717            assert_eq!(highlighted_edits.highlights.len(), 1);
23718            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23719            assert_eq!(
23720                highlighted_edits.highlights[0].1.background_color,
23721                Some(cx.theme().status().created_background)
23722            );
23723        },
23724    )
23725    .await;
23726
23727    // Replacement
23728    assert_highlighted_edits(
23729        "This is a test.",
23730        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23731        false,
23732        cx,
23733        |highlighted_edits, cx| {
23734            assert_eq!(highlighted_edits.text, "That is a test.");
23735            assert_eq!(highlighted_edits.highlights.len(), 1);
23736            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23737            assert_eq!(
23738                highlighted_edits.highlights[0].1.background_color,
23739                Some(cx.theme().status().created_background)
23740            );
23741        },
23742    )
23743    .await;
23744
23745    // Multiple edits
23746    assert_highlighted_edits(
23747        "Hello, world!",
23748        vec![
23749            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23750            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23751        ],
23752        false,
23753        cx,
23754        |highlighted_edits, cx| {
23755            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23756            assert_eq!(highlighted_edits.highlights.len(), 2);
23757            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23758            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23759            assert_eq!(
23760                highlighted_edits.highlights[0].1.background_color,
23761                Some(cx.theme().status().created_background)
23762            );
23763            assert_eq!(
23764                highlighted_edits.highlights[1].1.background_color,
23765                Some(cx.theme().status().created_background)
23766            );
23767        },
23768    )
23769    .await;
23770
23771    // Multiple lines with edits
23772    assert_highlighted_edits(
23773        "First line\nSecond line\nThird line\nFourth line",
23774        vec![
23775            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23776            (
23777                Point::new(2, 0)..Point::new(2, 10),
23778                "New third line".to_string(),
23779            ),
23780            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23781        ],
23782        false,
23783        cx,
23784        |highlighted_edits, cx| {
23785            assert_eq!(
23786                highlighted_edits.text,
23787                "Second modified\nNew third line\nFourth updated line"
23788            );
23789            assert_eq!(highlighted_edits.highlights.len(), 3);
23790            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23791            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23792            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23793            for highlight in &highlighted_edits.highlights {
23794                assert_eq!(
23795                    highlight.1.background_color,
23796                    Some(cx.theme().status().created_background)
23797                );
23798            }
23799        },
23800    )
23801    .await;
23802}
23803
23804#[gpui::test]
23805async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23806    init_test(cx, |_| {});
23807
23808    // Deletion
23809    assert_highlighted_edits(
23810        "Hello, world!",
23811        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23812        true,
23813        cx,
23814        |highlighted_edits, cx| {
23815            assert_eq!(highlighted_edits.text, "Hello, world!");
23816            assert_eq!(highlighted_edits.highlights.len(), 1);
23817            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23818            assert_eq!(
23819                highlighted_edits.highlights[0].1.background_color,
23820                Some(cx.theme().status().deleted_background)
23821            );
23822        },
23823    )
23824    .await;
23825
23826    // Insertion
23827    assert_highlighted_edits(
23828        "Hello, world!",
23829        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23830        true,
23831        cx,
23832        |highlighted_edits, cx| {
23833            assert_eq!(highlighted_edits.highlights.len(), 1);
23834            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23835            assert_eq!(
23836                highlighted_edits.highlights[0].1.background_color,
23837                Some(cx.theme().status().created_background)
23838            );
23839        },
23840    )
23841    .await;
23842}
23843
23844async fn assert_highlighted_edits(
23845    text: &str,
23846    edits: Vec<(Range<Point>, String)>,
23847    include_deletions: bool,
23848    cx: &mut TestAppContext,
23849    assertion_fn: impl Fn(HighlightedText, &App),
23850) {
23851    let window = cx.add_window(|window, cx| {
23852        let buffer = MultiBuffer::build_simple(text, cx);
23853        Editor::new(EditorMode::full(), buffer, None, window, cx)
23854    });
23855    let cx = &mut VisualTestContext::from_window(*window, cx);
23856
23857    let (buffer, snapshot) = window
23858        .update(cx, |editor, _window, cx| {
23859            (
23860                editor.buffer().clone(),
23861                editor.buffer().read(cx).snapshot(cx),
23862            )
23863        })
23864        .unwrap();
23865
23866    let edits = edits
23867        .into_iter()
23868        .map(|(range, edit)| {
23869            (
23870                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23871                edit,
23872            )
23873        })
23874        .collect::<Vec<_>>();
23875
23876    let text_anchor_edits = edits
23877        .clone()
23878        .into_iter()
23879        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23880        .collect::<Vec<_>>();
23881
23882    let edit_preview = window
23883        .update(cx, |_, _window, cx| {
23884            buffer
23885                .read(cx)
23886                .as_singleton()
23887                .unwrap()
23888                .read(cx)
23889                .preview_edits(text_anchor_edits.into(), cx)
23890        })
23891        .unwrap()
23892        .await;
23893
23894    cx.update(|_window, cx| {
23895        let highlighted_edits = edit_prediction_edit_text(
23896            snapshot.as_singleton().unwrap().2,
23897            &edits,
23898            &edit_preview,
23899            include_deletions,
23900            cx,
23901        );
23902        assertion_fn(highlighted_edits, cx)
23903    });
23904}
23905
23906#[track_caller]
23907fn assert_breakpoint(
23908    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23909    path: &Arc<Path>,
23910    expected: Vec<(u32, Breakpoint)>,
23911) {
23912    if expected.is_empty() {
23913        assert!(!breakpoints.contains_key(path), "{}", path.display());
23914    } else {
23915        let mut breakpoint = breakpoints
23916            .get(path)
23917            .unwrap()
23918            .iter()
23919            .map(|breakpoint| {
23920                (
23921                    breakpoint.row,
23922                    Breakpoint {
23923                        message: breakpoint.message.clone(),
23924                        state: breakpoint.state,
23925                        condition: breakpoint.condition.clone(),
23926                        hit_condition: breakpoint.hit_condition.clone(),
23927                    },
23928                )
23929            })
23930            .collect::<Vec<_>>();
23931
23932        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23933
23934        assert_eq!(expected, breakpoint);
23935    }
23936}
23937
23938fn add_log_breakpoint_at_cursor(
23939    editor: &mut Editor,
23940    log_message: &str,
23941    window: &mut Window,
23942    cx: &mut Context<Editor>,
23943) {
23944    let (anchor, bp) = editor
23945        .breakpoints_at_cursors(window, cx)
23946        .first()
23947        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23948        .unwrap_or_else(|| {
23949            let snapshot = editor.snapshot(window, cx);
23950            let cursor_position: Point =
23951                editor.selections.newest(&snapshot.display_snapshot).head();
23952
23953            let breakpoint_position = snapshot
23954                .buffer_snapshot()
23955                .anchor_before(Point::new(cursor_position.row, 0));
23956
23957            (breakpoint_position, Breakpoint::new_log(log_message))
23958        });
23959
23960    editor.edit_breakpoint_at_anchor(
23961        anchor,
23962        bp,
23963        BreakpointEditAction::EditLogMessage(log_message.into()),
23964        cx,
23965    );
23966}
23967
23968#[gpui::test]
23969async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23970    init_test(cx, |_| {});
23971
23972    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23973    let fs = FakeFs::new(cx.executor());
23974    fs.insert_tree(
23975        path!("/a"),
23976        json!({
23977            "main.rs": sample_text,
23978        }),
23979    )
23980    .await;
23981    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23982    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23983    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23984
23985    let fs = FakeFs::new(cx.executor());
23986    fs.insert_tree(
23987        path!("/a"),
23988        json!({
23989            "main.rs": sample_text,
23990        }),
23991    )
23992    .await;
23993    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23994    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23995    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23996    let worktree_id = workspace
23997        .update(cx, |workspace, _window, cx| {
23998            workspace.project().update(cx, |project, cx| {
23999                project.worktrees(cx).next().unwrap().read(cx).id()
24000            })
24001        })
24002        .unwrap();
24003
24004    let buffer = project
24005        .update(cx, |project, cx| {
24006            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24007        })
24008        .await
24009        .unwrap();
24010
24011    let (editor, cx) = cx.add_window_view(|window, cx| {
24012        Editor::new(
24013            EditorMode::full(),
24014            MultiBuffer::build_from_buffer(buffer, cx),
24015            Some(project.clone()),
24016            window,
24017            cx,
24018        )
24019    });
24020
24021    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24022    let abs_path = project.read_with(cx, |project, cx| {
24023        project
24024            .absolute_path(&project_path, cx)
24025            .map(Arc::from)
24026            .unwrap()
24027    });
24028
24029    // assert we can add breakpoint on the first line
24030    editor.update_in(cx, |editor, window, cx| {
24031        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24032        editor.move_to_end(&MoveToEnd, window, cx);
24033        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24034    });
24035
24036    let breakpoints = editor.update(cx, |editor, cx| {
24037        editor
24038            .breakpoint_store()
24039            .as_ref()
24040            .unwrap()
24041            .read(cx)
24042            .all_source_breakpoints(cx)
24043    });
24044
24045    assert_eq!(1, breakpoints.len());
24046    assert_breakpoint(
24047        &breakpoints,
24048        &abs_path,
24049        vec![
24050            (0, Breakpoint::new_standard()),
24051            (3, Breakpoint::new_standard()),
24052        ],
24053    );
24054
24055    editor.update_in(cx, |editor, window, cx| {
24056        editor.move_to_beginning(&MoveToBeginning, window, cx);
24057        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24058    });
24059
24060    let breakpoints = editor.update(cx, |editor, cx| {
24061        editor
24062            .breakpoint_store()
24063            .as_ref()
24064            .unwrap()
24065            .read(cx)
24066            .all_source_breakpoints(cx)
24067    });
24068
24069    assert_eq!(1, breakpoints.len());
24070    assert_breakpoint(
24071        &breakpoints,
24072        &abs_path,
24073        vec![(3, Breakpoint::new_standard())],
24074    );
24075
24076    editor.update_in(cx, |editor, window, cx| {
24077        editor.move_to_end(&MoveToEnd, window, cx);
24078        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24079    });
24080
24081    let breakpoints = editor.update(cx, |editor, cx| {
24082        editor
24083            .breakpoint_store()
24084            .as_ref()
24085            .unwrap()
24086            .read(cx)
24087            .all_source_breakpoints(cx)
24088    });
24089
24090    assert_eq!(0, breakpoints.len());
24091    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24092}
24093
24094#[gpui::test]
24095async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24096    init_test(cx, |_| {});
24097
24098    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24099
24100    let fs = FakeFs::new(cx.executor());
24101    fs.insert_tree(
24102        path!("/a"),
24103        json!({
24104            "main.rs": sample_text,
24105        }),
24106    )
24107    .await;
24108    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24109    let (workspace, cx) =
24110        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24111
24112    let worktree_id = workspace.update(cx, |workspace, cx| {
24113        workspace.project().update(cx, |project, cx| {
24114            project.worktrees(cx).next().unwrap().read(cx).id()
24115        })
24116    });
24117
24118    let buffer = project
24119        .update(cx, |project, cx| {
24120            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24121        })
24122        .await
24123        .unwrap();
24124
24125    let (editor, cx) = cx.add_window_view(|window, cx| {
24126        Editor::new(
24127            EditorMode::full(),
24128            MultiBuffer::build_from_buffer(buffer, cx),
24129            Some(project.clone()),
24130            window,
24131            cx,
24132        )
24133    });
24134
24135    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24136    let abs_path = project.read_with(cx, |project, cx| {
24137        project
24138            .absolute_path(&project_path, cx)
24139            .map(Arc::from)
24140            .unwrap()
24141    });
24142
24143    editor.update_in(cx, |editor, window, cx| {
24144        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24145    });
24146
24147    let breakpoints = editor.update(cx, |editor, cx| {
24148        editor
24149            .breakpoint_store()
24150            .as_ref()
24151            .unwrap()
24152            .read(cx)
24153            .all_source_breakpoints(cx)
24154    });
24155
24156    assert_breakpoint(
24157        &breakpoints,
24158        &abs_path,
24159        vec![(0, Breakpoint::new_log("hello world"))],
24160    );
24161
24162    // Removing a log message from a log breakpoint should remove it
24163    editor.update_in(cx, |editor, window, cx| {
24164        add_log_breakpoint_at_cursor(editor, "", window, cx);
24165    });
24166
24167    let breakpoints = editor.update(cx, |editor, cx| {
24168        editor
24169            .breakpoint_store()
24170            .as_ref()
24171            .unwrap()
24172            .read(cx)
24173            .all_source_breakpoints(cx)
24174    });
24175
24176    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24177
24178    editor.update_in(cx, |editor, window, cx| {
24179        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24180        editor.move_to_end(&MoveToEnd, window, cx);
24181        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24182        // Not adding a log message to a standard breakpoint shouldn't remove it
24183        add_log_breakpoint_at_cursor(editor, "", window, cx);
24184    });
24185
24186    let breakpoints = editor.update(cx, |editor, cx| {
24187        editor
24188            .breakpoint_store()
24189            .as_ref()
24190            .unwrap()
24191            .read(cx)
24192            .all_source_breakpoints(cx)
24193    });
24194
24195    assert_breakpoint(
24196        &breakpoints,
24197        &abs_path,
24198        vec![
24199            (0, Breakpoint::new_standard()),
24200            (3, Breakpoint::new_standard()),
24201        ],
24202    );
24203
24204    editor.update_in(cx, |editor, window, cx| {
24205        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24206    });
24207
24208    let breakpoints = editor.update(cx, |editor, cx| {
24209        editor
24210            .breakpoint_store()
24211            .as_ref()
24212            .unwrap()
24213            .read(cx)
24214            .all_source_breakpoints(cx)
24215    });
24216
24217    assert_breakpoint(
24218        &breakpoints,
24219        &abs_path,
24220        vec![
24221            (0, Breakpoint::new_standard()),
24222            (3, Breakpoint::new_log("hello world")),
24223        ],
24224    );
24225
24226    editor.update_in(cx, |editor, window, cx| {
24227        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24228    });
24229
24230    let breakpoints = editor.update(cx, |editor, cx| {
24231        editor
24232            .breakpoint_store()
24233            .as_ref()
24234            .unwrap()
24235            .read(cx)
24236            .all_source_breakpoints(cx)
24237    });
24238
24239    assert_breakpoint(
24240        &breakpoints,
24241        &abs_path,
24242        vec![
24243            (0, Breakpoint::new_standard()),
24244            (3, Breakpoint::new_log("hello Earth!!")),
24245        ],
24246    );
24247}
24248
24249/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24250/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24251/// or when breakpoints were placed out of order. This tests for a regression too
24252#[gpui::test]
24253async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24254    init_test(cx, |_| {});
24255
24256    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24257    let fs = FakeFs::new(cx.executor());
24258    fs.insert_tree(
24259        path!("/a"),
24260        json!({
24261            "main.rs": sample_text,
24262        }),
24263    )
24264    .await;
24265    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24266    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24267    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24268
24269    let fs = FakeFs::new(cx.executor());
24270    fs.insert_tree(
24271        path!("/a"),
24272        json!({
24273            "main.rs": sample_text,
24274        }),
24275    )
24276    .await;
24277    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24278    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24279    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24280    let worktree_id = workspace
24281        .update(cx, |workspace, _window, cx| {
24282            workspace.project().update(cx, |project, cx| {
24283                project.worktrees(cx).next().unwrap().read(cx).id()
24284            })
24285        })
24286        .unwrap();
24287
24288    let buffer = project
24289        .update(cx, |project, cx| {
24290            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24291        })
24292        .await
24293        .unwrap();
24294
24295    let (editor, cx) = cx.add_window_view(|window, cx| {
24296        Editor::new(
24297            EditorMode::full(),
24298            MultiBuffer::build_from_buffer(buffer, cx),
24299            Some(project.clone()),
24300            window,
24301            cx,
24302        )
24303    });
24304
24305    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24306    let abs_path = project.read_with(cx, |project, cx| {
24307        project
24308            .absolute_path(&project_path, cx)
24309            .map(Arc::from)
24310            .unwrap()
24311    });
24312
24313    // assert we can add breakpoint on the first line
24314    editor.update_in(cx, |editor, window, cx| {
24315        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24316        editor.move_to_end(&MoveToEnd, window, cx);
24317        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24318        editor.move_up(&MoveUp, window, cx);
24319        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24320    });
24321
24322    let breakpoints = editor.update(cx, |editor, cx| {
24323        editor
24324            .breakpoint_store()
24325            .as_ref()
24326            .unwrap()
24327            .read(cx)
24328            .all_source_breakpoints(cx)
24329    });
24330
24331    assert_eq!(1, breakpoints.len());
24332    assert_breakpoint(
24333        &breakpoints,
24334        &abs_path,
24335        vec![
24336            (0, Breakpoint::new_standard()),
24337            (2, Breakpoint::new_standard()),
24338            (3, Breakpoint::new_standard()),
24339        ],
24340    );
24341
24342    editor.update_in(cx, |editor, window, cx| {
24343        editor.move_to_beginning(&MoveToBeginning, window, cx);
24344        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24345        editor.move_to_end(&MoveToEnd, window, cx);
24346        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24347        // Disabling a breakpoint that doesn't exist should do nothing
24348        editor.move_up(&MoveUp, window, cx);
24349        editor.move_up(&MoveUp, window, cx);
24350        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24351    });
24352
24353    let breakpoints = editor.update(cx, |editor, cx| {
24354        editor
24355            .breakpoint_store()
24356            .as_ref()
24357            .unwrap()
24358            .read(cx)
24359            .all_source_breakpoints(cx)
24360    });
24361
24362    let disable_breakpoint = {
24363        let mut bp = Breakpoint::new_standard();
24364        bp.state = BreakpointState::Disabled;
24365        bp
24366    };
24367
24368    assert_eq!(1, breakpoints.len());
24369    assert_breakpoint(
24370        &breakpoints,
24371        &abs_path,
24372        vec![
24373            (0, disable_breakpoint.clone()),
24374            (2, Breakpoint::new_standard()),
24375            (3, disable_breakpoint.clone()),
24376        ],
24377    );
24378
24379    editor.update_in(cx, |editor, window, cx| {
24380        editor.move_to_beginning(&MoveToBeginning, window, cx);
24381        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24382        editor.move_to_end(&MoveToEnd, window, cx);
24383        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24384        editor.move_up(&MoveUp, window, cx);
24385        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24386    });
24387
24388    let breakpoints = editor.update(cx, |editor, cx| {
24389        editor
24390            .breakpoint_store()
24391            .as_ref()
24392            .unwrap()
24393            .read(cx)
24394            .all_source_breakpoints(cx)
24395    });
24396
24397    assert_eq!(1, breakpoints.len());
24398    assert_breakpoint(
24399        &breakpoints,
24400        &abs_path,
24401        vec![
24402            (0, Breakpoint::new_standard()),
24403            (2, disable_breakpoint),
24404            (3, Breakpoint::new_standard()),
24405        ],
24406    );
24407}
24408
24409#[gpui::test]
24410async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24411    init_test(cx, |_| {});
24412    let capabilities = lsp::ServerCapabilities {
24413        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24414            prepare_provider: Some(true),
24415            work_done_progress_options: Default::default(),
24416        })),
24417        ..Default::default()
24418    };
24419    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24420
24421    cx.set_state(indoc! {"
24422        struct Fˇoo {}
24423    "});
24424
24425    cx.update_editor(|editor, _, cx| {
24426        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24427        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24428        editor.highlight_background::<DocumentHighlightRead>(
24429            &[highlight_range],
24430            |_, theme| theme.colors().editor_document_highlight_read_background,
24431            cx,
24432        );
24433    });
24434
24435    let mut prepare_rename_handler = cx
24436        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24437            move |_, _, _| async move {
24438                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24439                    start: lsp::Position {
24440                        line: 0,
24441                        character: 7,
24442                    },
24443                    end: lsp::Position {
24444                        line: 0,
24445                        character: 10,
24446                    },
24447                })))
24448            },
24449        );
24450    let prepare_rename_task = cx
24451        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24452        .expect("Prepare rename was not started");
24453    prepare_rename_handler.next().await.unwrap();
24454    prepare_rename_task.await.expect("Prepare rename failed");
24455
24456    let mut rename_handler =
24457        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24458            let edit = lsp::TextEdit {
24459                range: lsp::Range {
24460                    start: lsp::Position {
24461                        line: 0,
24462                        character: 7,
24463                    },
24464                    end: lsp::Position {
24465                        line: 0,
24466                        character: 10,
24467                    },
24468                },
24469                new_text: "FooRenamed".to_string(),
24470            };
24471            Ok(Some(lsp::WorkspaceEdit::new(
24472                // Specify the same edit twice
24473                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24474            )))
24475        });
24476    let rename_task = cx
24477        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24478        .expect("Confirm rename was not started");
24479    rename_handler.next().await.unwrap();
24480    rename_task.await.expect("Confirm rename failed");
24481    cx.run_until_parked();
24482
24483    // Despite two edits, only one is actually applied as those are identical
24484    cx.assert_editor_state(indoc! {"
24485        struct FooRenamedˇ {}
24486    "});
24487}
24488
24489#[gpui::test]
24490async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24491    init_test(cx, |_| {});
24492    // These capabilities indicate that the server does not support prepare rename.
24493    let capabilities = lsp::ServerCapabilities {
24494        rename_provider: Some(lsp::OneOf::Left(true)),
24495        ..Default::default()
24496    };
24497    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24498
24499    cx.set_state(indoc! {"
24500        struct Fˇoo {}
24501    "});
24502
24503    cx.update_editor(|editor, _window, cx| {
24504        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24505        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24506        editor.highlight_background::<DocumentHighlightRead>(
24507            &[highlight_range],
24508            |_, theme| theme.colors().editor_document_highlight_read_background,
24509            cx,
24510        );
24511    });
24512
24513    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24514        .expect("Prepare rename was not started")
24515        .await
24516        .expect("Prepare rename failed");
24517
24518    let mut rename_handler =
24519        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24520            let edit = lsp::TextEdit {
24521                range: lsp::Range {
24522                    start: lsp::Position {
24523                        line: 0,
24524                        character: 7,
24525                    },
24526                    end: lsp::Position {
24527                        line: 0,
24528                        character: 10,
24529                    },
24530                },
24531                new_text: "FooRenamed".to_string(),
24532            };
24533            Ok(Some(lsp::WorkspaceEdit::new(
24534                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24535            )))
24536        });
24537    let rename_task = cx
24538        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24539        .expect("Confirm rename was not started");
24540    rename_handler.next().await.unwrap();
24541    rename_task.await.expect("Confirm rename failed");
24542    cx.run_until_parked();
24543
24544    // Correct range is renamed, as `surrounding_word` is used to find it.
24545    cx.assert_editor_state(indoc! {"
24546        struct FooRenamedˇ {}
24547    "});
24548}
24549
24550#[gpui::test]
24551async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24552    init_test(cx, |_| {});
24553    let mut cx = EditorTestContext::new(cx).await;
24554
24555    let language = Arc::new(
24556        Language::new(
24557            LanguageConfig::default(),
24558            Some(tree_sitter_html::LANGUAGE.into()),
24559        )
24560        .with_brackets_query(
24561            r#"
24562            ("<" @open "/>" @close)
24563            ("</" @open ">" @close)
24564            ("<" @open ">" @close)
24565            ("\"" @open "\"" @close)
24566            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24567        "#,
24568        )
24569        .unwrap(),
24570    );
24571    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24572
24573    cx.set_state(indoc! {"
24574        <span>ˇ</span>
24575    "});
24576    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24577    cx.assert_editor_state(indoc! {"
24578        <span>
24579        ˇ
24580        </span>
24581    "});
24582
24583    cx.set_state(indoc! {"
24584        <span><span></span>ˇ</span>
24585    "});
24586    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24587    cx.assert_editor_state(indoc! {"
24588        <span><span></span>
24589        ˇ</span>
24590    "});
24591
24592    cx.set_state(indoc! {"
24593        <span>ˇ
24594        </span>
24595    "});
24596    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24597    cx.assert_editor_state(indoc! {"
24598        <span>
24599        ˇ
24600        </span>
24601    "});
24602}
24603
24604#[gpui::test(iterations = 10)]
24605async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24606    init_test(cx, |_| {});
24607
24608    let fs = FakeFs::new(cx.executor());
24609    fs.insert_tree(
24610        path!("/dir"),
24611        json!({
24612            "a.ts": "a",
24613        }),
24614    )
24615    .await;
24616
24617    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24618    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24619    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24620
24621    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24622    language_registry.add(Arc::new(Language::new(
24623        LanguageConfig {
24624            name: "TypeScript".into(),
24625            matcher: LanguageMatcher {
24626                path_suffixes: vec!["ts".to_string()],
24627                ..Default::default()
24628            },
24629            ..Default::default()
24630        },
24631        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24632    )));
24633    let mut fake_language_servers = language_registry.register_fake_lsp(
24634        "TypeScript",
24635        FakeLspAdapter {
24636            capabilities: lsp::ServerCapabilities {
24637                code_lens_provider: Some(lsp::CodeLensOptions {
24638                    resolve_provider: Some(true),
24639                }),
24640                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24641                    commands: vec!["_the/command".to_string()],
24642                    ..lsp::ExecuteCommandOptions::default()
24643                }),
24644                ..lsp::ServerCapabilities::default()
24645            },
24646            ..FakeLspAdapter::default()
24647        },
24648    );
24649
24650    let editor = workspace
24651        .update(cx, |workspace, window, cx| {
24652            workspace.open_abs_path(
24653                PathBuf::from(path!("/dir/a.ts")),
24654                OpenOptions::default(),
24655                window,
24656                cx,
24657            )
24658        })
24659        .unwrap()
24660        .await
24661        .unwrap()
24662        .downcast::<Editor>()
24663        .unwrap();
24664    cx.executor().run_until_parked();
24665
24666    let fake_server = fake_language_servers.next().await.unwrap();
24667
24668    let buffer = editor.update(cx, |editor, cx| {
24669        editor
24670            .buffer()
24671            .read(cx)
24672            .as_singleton()
24673            .expect("have opened a single file by path")
24674    });
24675
24676    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24677    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24678    drop(buffer_snapshot);
24679    let actions = cx
24680        .update_window(*workspace, |_, window, cx| {
24681            project.code_actions(&buffer, anchor..anchor, window, cx)
24682        })
24683        .unwrap();
24684
24685    fake_server
24686        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24687            Ok(Some(vec![
24688                lsp::CodeLens {
24689                    range: lsp::Range::default(),
24690                    command: Some(lsp::Command {
24691                        title: "Code lens command".to_owned(),
24692                        command: "_the/command".to_owned(),
24693                        arguments: None,
24694                    }),
24695                    data: None,
24696                },
24697                lsp::CodeLens {
24698                    range: lsp::Range::default(),
24699                    command: Some(lsp::Command {
24700                        title: "Command not in capabilities".to_owned(),
24701                        command: "not in capabilities".to_owned(),
24702                        arguments: None,
24703                    }),
24704                    data: None,
24705                },
24706                lsp::CodeLens {
24707                    range: lsp::Range {
24708                        start: lsp::Position {
24709                            line: 1,
24710                            character: 1,
24711                        },
24712                        end: lsp::Position {
24713                            line: 1,
24714                            character: 1,
24715                        },
24716                    },
24717                    command: Some(lsp::Command {
24718                        title: "Command not in range".to_owned(),
24719                        command: "_the/command".to_owned(),
24720                        arguments: None,
24721                    }),
24722                    data: None,
24723                },
24724            ]))
24725        })
24726        .next()
24727        .await;
24728
24729    let actions = actions.await.unwrap();
24730    assert_eq!(
24731        actions.len(),
24732        1,
24733        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24734    );
24735    let action = actions[0].clone();
24736    let apply = project.update(cx, |project, cx| {
24737        project.apply_code_action(buffer.clone(), action, true, cx)
24738    });
24739
24740    // Resolving the code action does not populate its edits. In absence of
24741    // edits, we must execute the given command.
24742    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24743        |mut lens, _| async move {
24744            let lens_command = lens.command.as_mut().expect("should have a command");
24745            assert_eq!(lens_command.title, "Code lens command");
24746            lens_command.arguments = Some(vec![json!("the-argument")]);
24747            Ok(lens)
24748        },
24749    );
24750
24751    // While executing the command, the language server sends the editor
24752    // a `workspaceEdit` request.
24753    fake_server
24754        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24755            let fake = fake_server.clone();
24756            move |params, _| {
24757                assert_eq!(params.command, "_the/command");
24758                let fake = fake.clone();
24759                async move {
24760                    fake.server
24761                        .request::<lsp::request::ApplyWorkspaceEdit>(
24762                            lsp::ApplyWorkspaceEditParams {
24763                                label: None,
24764                                edit: lsp::WorkspaceEdit {
24765                                    changes: Some(
24766                                        [(
24767                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24768                                            vec![lsp::TextEdit {
24769                                                range: lsp::Range::new(
24770                                                    lsp::Position::new(0, 0),
24771                                                    lsp::Position::new(0, 0),
24772                                                ),
24773                                                new_text: "X".into(),
24774                                            }],
24775                                        )]
24776                                        .into_iter()
24777                                        .collect(),
24778                                    ),
24779                                    ..lsp::WorkspaceEdit::default()
24780                                },
24781                            },
24782                        )
24783                        .await
24784                        .into_response()
24785                        .unwrap();
24786                    Ok(Some(json!(null)))
24787                }
24788            }
24789        })
24790        .next()
24791        .await;
24792
24793    // Applying the code lens command returns a project transaction containing the edits
24794    // sent by the language server in its `workspaceEdit` request.
24795    let transaction = apply.await.unwrap();
24796    assert!(transaction.0.contains_key(&buffer));
24797    buffer.update(cx, |buffer, cx| {
24798        assert_eq!(buffer.text(), "Xa");
24799        buffer.undo(cx);
24800        assert_eq!(buffer.text(), "a");
24801    });
24802
24803    let actions_after_edits = cx
24804        .update_window(*workspace, |_, window, cx| {
24805            project.code_actions(&buffer, anchor..anchor, window, cx)
24806        })
24807        .unwrap()
24808        .await
24809        .unwrap();
24810    assert_eq!(
24811        actions, actions_after_edits,
24812        "For the same selection, same code lens actions should be returned"
24813    );
24814
24815    let _responses =
24816        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24817            panic!("No more code lens requests are expected");
24818        });
24819    editor.update_in(cx, |editor, window, cx| {
24820        editor.select_all(&SelectAll, window, cx);
24821    });
24822    cx.executor().run_until_parked();
24823    let new_actions = cx
24824        .update_window(*workspace, |_, window, cx| {
24825            project.code_actions(&buffer, anchor..anchor, window, cx)
24826        })
24827        .unwrap()
24828        .await
24829        .unwrap();
24830    assert_eq!(
24831        actions, new_actions,
24832        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24833    );
24834}
24835
24836#[gpui::test]
24837async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24838    init_test(cx, |_| {});
24839
24840    let fs = FakeFs::new(cx.executor());
24841    let main_text = r#"fn main() {
24842println!("1");
24843println!("2");
24844println!("3");
24845println!("4");
24846println!("5");
24847}"#;
24848    let lib_text = "mod foo {}";
24849    fs.insert_tree(
24850        path!("/a"),
24851        json!({
24852            "lib.rs": lib_text,
24853            "main.rs": main_text,
24854        }),
24855    )
24856    .await;
24857
24858    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24859    let (workspace, cx) =
24860        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24861    let worktree_id = workspace.update(cx, |workspace, cx| {
24862        workspace.project().update(cx, |project, cx| {
24863            project.worktrees(cx).next().unwrap().read(cx).id()
24864        })
24865    });
24866
24867    let expected_ranges = vec![
24868        Point::new(0, 0)..Point::new(0, 0),
24869        Point::new(1, 0)..Point::new(1, 1),
24870        Point::new(2, 0)..Point::new(2, 2),
24871        Point::new(3, 0)..Point::new(3, 3),
24872    ];
24873
24874    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24875    let editor_1 = workspace
24876        .update_in(cx, |workspace, window, cx| {
24877            workspace.open_path(
24878                (worktree_id, rel_path("main.rs")),
24879                Some(pane_1.downgrade()),
24880                true,
24881                window,
24882                cx,
24883            )
24884        })
24885        .unwrap()
24886        .await
24887        .downcast::<Editor>()
24888        .unwrap();
24889    pane_1.update(cx, |pane, cx| {
24890        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24891        open_editor.update(cx, |editor, cx| {
24892            assert_eq!(
24893                editor.display_text(cx),
24894                main_text,
24895                "Original main.rs text on initial open",
24896            );
24897            assert_eq!(
24898                editor
24899                    .selections
24900                    .all::<Point>(&editor.display_snapshot(cx))
24901                    .into_iter()
24902                    .map(|s| s.range())
24903                    .collect::<Vec<_>>(),
24904                vec![Point::zero()..Point::zero()],
24905                "Default selections on initial open",
24906            );
24907        })
24908    });
24909    editor_1.update_in(cx, |editor, window, cx| {
24910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24911            s.select_ranges(expected_ranges.clone());
24912        });
24913    });
24914
24915    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24916        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24917    });
24918    let editor_2 = workspace
24919        .update_in(cx, |workspace, window, cx| {
24920            workspace.open_path(
24921                (worktree_id, rel_path("main.rs")),
24922                Some(pane_2.downgrade()),
24923                true,
24924                window,
24925                cx,
24926            )
24927        })
24928        .unwrap()
24929        .await
24930        .downcast::<Editor>()
24931        .unwrap();
24932    pane_2.update(cx, |pane, cx| {
24933        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24934        open_editor.update(cx, |editor, cx| {
24935            assert_eq!(
24936                editor.display_text(cx),
24937                main_text,
24938                "Original main.rs text on initial open in another panel",
24939            );
24940            assert_eq!(
24941                editor
24942                    .selections
24943                    .all::<Point>(&editor.display_snapshot(cx))
24944                    .into_iter()
24945                    .map(|s| s.range())
24946                    .collect::<Vec<_>>(),
24947                vec![Point::zero()..Point::zero()],
24948                "Default selections on initial open in another panel",
24949            );
24950        })
24951    });
24952
24953    editor_2.update_in(cx, |editor, window, cx| {
24954        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24955    });
24956
24957    let _other_editor_1 = workspace
24958        .update_in(cx, |workspace, window, cx| {
24959            workspace.open_path(
24960                (worktree_id, rel_path("lib.rs")),
24961                Some(pane_1.downgrade()),
24962                true,
24963                window,
24964                cx,
24965            )
24966        })
24967        .unwrap()
24968        .await
24969        .downcast::<Editor>()
24970        .unwrap();
24971    pane_1
24972        .update_in(cx, |pane, window, cx| {
24973            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24974        })
24975        .await
24976        .unwrap();
24977    drop(editor_1);
24978    pane_1.update(cx, |pane, cx| {
24979        pane.active_item()
24980            .unwrap()
24981            .downcast::<Editor>()
24982            .unwrap()
24983            .update(cx, |editor, cx| {
24984                assert_eq!(
24985                    editor.display_text(cx),
24986                    lib_text,
24987                    "Other file should be open and active",
24988                );
24989            });
24990        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24991    });
24992
24993    let _other_editor_2 = workspace
24994        .update_in(cx, |workspace, window, cx| {
24995            workspace.open_path(
24996                (worktree_id, rel_path("lib.rs")),
24997                Some(pane_2.downgrade()),
24998                true,
24999                window,
25000                cx,
25001            )
25002        })
25003        .unwrap()
25004        .await
25005        .downcast::<Editor>()
25006        .unwrap();
25007    pane_2
25008        .update_in(cx, |pane, window, cx| {
25009            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25010        })
25011        .await
25012        .unwrap();
25013    drop(editor_2);
25014    pane_2.update(cx, |pane, cx| {
25015        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25016        open_editor.update(cx, |editor, cx| {
25017            assert_eq!(
25018                editor.display_text(cx),
25019                lib_text,
25020                "Other file should be open and active in another panel too",
25021            );
25022        });
25023        assert_eq!(
25024            pane.items().count(),
25025            1,
25026            "No other editors should be open in another pane",
25027        );
25028    });
25029
25030    let _editor_1_reopened = workspace
25031        .update_in(cx, |workspace, window, cx| {
25032            workspace.open_path(
25033                (worktree_id, rel_path("main.rs")),
25034                Some(pane_1.downgrade()),
25035                true,
25036                window,
25037                cx,
25038            )
25039        })
25040        .unwrap()
25041        .await
25042        .downcast::<Editor>()
25043        .unwrap();
25044    let _editor_2_reopened = workspace
25045        .update_in(cx, |workspace, window, cx| {
25046            workspace.open_path(
25047                (worktree_id, rel_path("main.rs")),
25048                Some(pane_2.downgrade()),
25049                true,
25050                window,
25051                cx,
25052            )
25053        })
25054        .unwrap()
25055        .await
25056        .downcast::<Editor>()
25057        .unwrap();
25058    pane_1.update(cx, |pane, cx| {
25059        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25060        open_editor.update(cx, |editor, cx| {
25061            assert_eq!(
25062                editor.display_text(cx),
25063                main_text,
25064                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25065            );
25066            assert_eq!(
25067                editor
25068                    .selections
25069                    .all::<Point>(&editor.display_snapshot(cx))
25070                    .into_iter()
25071                    .map(|s| s.range())
25072                    .collect::<Vec<_>>(),
25073                expected_ranges,
25074                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25075            );
25076        })
25077    });
25078    pane_2.update(cx, |pane, cx| {
25079        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25080        open_editor.update(cx, |editor, cx| {
25081            assert_eq!(
25082                editor.display_text(cx),
25083                r#"fn main() {
25084⋯rintln!("1");
25085⋯intln!("2");
25086⋯ntln!("3");
25087println!("4");
25088println!("5");
25089}"#,
25090                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25091            );
25092            assert_eq!(
25093                editor
25094                    .selections
25095                    .all::<Point>(&editor.display_snapshot(cx))
25096                    .into_iter()
25097                    .map(|s| s.range())
25098                    .collect::<Vec<_>>(),
25099                vec![Point::zero()..Point::zero()],
25100                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25101            );
25102        })
25103    });
25104}
25105
25106#[gpui::test]
25107async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25108    init_test(cx, |_| {});
25109
25110    let fs = FakeFs::new(cx.executor());
25111    let main_text = r#"fn main() {
25112println!("1");
25113println!("2");
25114println!("3");
25115println!("4");
25116println!("5");
25117}"#;
25118    let lib_text = "mod foo {}";
25119    fs.insert_tree(
25120        path!("/a"),
25121        json!({
25122            "lib.rs": lib_text,
25123            "main.rs": main_text,
25124        }),
25125    )
25126    .await;
25127
25128    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25129    let (workspace, cx) =
25130        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25131    let worktree_id = workspace.update(cx, |workspace, cx| {
25132        workspace.project().update(cx, |project, cx| {
25133            project.worktrees(cx).next().unwrap().read(cx).id()
25134        })
25135    });
25136
25137    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25138    let editor = workspace
25139        .update_in(cx, |workspace, window, cx| {
25140            workspace.open_path(
25141                (worktree_id, rel_path("main.rs")),
25142                Some(pane.downgrade()),
25143                true,
25144                window,
25145                cx,
25146            )
25147        })
25148        .unwrap()
25149        .await
25150        .downcast::<Editor>()
25151        .unwrap();
25152    pane.update(cx, |pane, cx| {
25153        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25154        open_editor.update(cx, |editor, cx| {
25155            assert_eq!(
25156                editor.display_text(cx),
25157                main_text,
25158                "Original main.rs text on initial open",
25159            );
25160        })
25161    });
25162    editor.update_in(cx, |editor, window, cx| {
25163        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25164    });
25165
25166    cx.update_global(|store: &mut SettingsStore, cx| {
25167        store.update_user_settings(cx, |s| {
25168            s.workspace.restore_on_file_reopen = Some(false);
25169        });
25170    });
25171    editor.update_in(cx, |editor, window, cx| {
25172        editor.fold_ranges(
25173            vec![
25174                Point::new(1, 0)..Point::new(1, 1),
25175                Point::new(2, 0)..Point::new(2, 2),
25176                Point::new(3, 0)..Point::new(3, 3),
25177            ],
25178            false,
25179            window,
25180            cx,
25181        );
25182    });
25183    pane.update_in(cx, |pane, window, cx| {
25184        pane.close_all_items(&CloseAllItems::default(), window, cx)
25185    })
25186    .await
25187    .unwrap();
25188    pane.update(cx, |pane, _| {
25189        assert!(pane.active_item().is_none());
25190    });
25191    cx.update_global(|store: &mut SettingsStore, cx| {
25192        store.update_user_settings(cx, |s| {
25193            s.workspace.restore_on_file_reopen = Some(true);
25194        });
25195    });
25196
25197    let _editor_reopened = workspace
25198        .update_in(cx, |workspace, window, cx| {
25199            workspace.open_path(
25200                (worktree_id, rel_path("main.rs")),
25201                Some(pane.downgrade()),
25202                true,
25203                window,
25204                cx,
25205            )
25206        })
25207        .unwrap()
25208        .await
25209        .downcast::<Editor>()
25210        .unwrap();
25211    pane.update(cx, |pane, cx| {
25212        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25213        open_editor.update(cx, |editor, cx| {
25214            assert_eq!(
25215                editor.display_text(cx),
25216                main_text,
25217                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25218            );
25219        })
25220    });
25221}
25222
25223#[gpui::test]
25224async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25225    struct EmptyModalView {
25226        focus_handle: gpui::FocusHandle,
25227    }
25228    impl EventEmitter<DismissEvent> for EmptyModalView {}
25229    impl Render for EmptyModalView {
25230        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25231            div()
25232        }
25233    }
25234    impl Focusable for EmptyModalView {
25235        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25236            self.focus_handle.clone()
25237        }
25238    }
25239    impl workspace::ModalView for EmptyModalView {}
25240    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25241        EmptyModalView {
25242            focus_handle: cx.focus_handle(),
25243        }
25244    }
25245
25246    init_test(cx, |_| {});
25247
25248    let fs = FakeFs::new(cx.executor());
25249    let project = Project::test(fs, [], cx).await;
25250    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25251    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25252    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25253    let editor = cx.new_window_entity(|window, cx| {
25254        Editor::new(
25255            EditorMode::full(),
25256            buffer,
25257            Some(project.clone()),
25258            window,
25259            cx,
25260        )
25261    });
25262    workspace
25263        .update(cx, |workspace, window, cx| {
25264            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25265        })
25266        .unwrap();
25267    editor.update_in(cx, |editor, window, cx| {
25268        editor.open_context_menu(&OpenContextMenu, window, cx);
25269        assert!(editor.mouse_context_menu.is_some());
25270    });
25271    workspace
25272        .update(cx, |workspace, window, cx| {
25273            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25274        })
25275        .unwrap();
25276    cx.read(|cx| {
25277        assert!(editor.read(cx).mouse_context_menu.is_none());
25278    });
25279}
25280
25281fn set_linked_edit_ranges(
25282    opening: (Point, Point),
25283    closing: (Point, Point),
25284    editor: &mut Editor,
25285    cx: &mut Context<Editor>,
25286) {
25287    let Some((buffer, _)) = editor
25288        .buffer
25289        .read(cx)
25290        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25291    else {
25292        panic!("Failed to get buffer for selection position");
25293    };
25294    let buffer = buffer.read(cx);
25295    let buffer_id = buffer.remote_id();
25296    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25297    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25298    let mut linked_ranges = HashMap::default();
25299    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25300    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25301}
25302
25303#[gpui::test]
25304async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25305    init_test(cx, |_| {});
25306
25307    let fs = FakeFs::new(cx.executor());
25308    fs.insert_file(path!("/file.html"), Default::default())
25309        .await;
25310
25311    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25312
25313    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25314    let html_language = Arc::new(Language::new(
25315        LanguageConfig {
25316            name: "HTML".into(),
25317            matcher: LanguageMatcher {
25318                path_suffixes: vec!["html".to_string()],
25319                ..LanguageMatcher::default()
25320            },
25321            brackets: BracketPairConfig {
25322                pairs: vec![BracketPair {
25323                    start: "<".into(),
25324                    end: ">".into(),
25325                    close: true,
25326                    ..Default::default()
25327                }],
25328                ..Default::default()
25329            },
25330            ..Default::default()
25331        },
25332        Some(tree_sitter_html::LANGUAGE.into()),
25333    ));
25334    language_registry.add(html_language);
25335    let mut fake_servers = language_registry.register_fake_lsp(
25336        "HTML",
25337        FakeLspAdapter {
25338            capabilities: lsp::ServerCapabilities {
25339                completion_provider: Some(lsp::CompletionOptions {
25340                    resolve_provider: Some(true),
25341                    ..Default::default()
25342                }),
25343                ..Default::default()
25344            },
25345            ..Default::default()
25346        },
25347    );
25348
25349    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25350    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25351
25352    let worktree_id = workspace
25353        .update(cx, |workspace, _window, cx| {
25354            workspace.project().update(cx, |project, cx| {
25355                project.worktrees(cx).next().unwrap().read(cx).id()
25356            })
25357        })
25358        .unwrap();
25359    project
25360        .update(cx, |project, cx| {
25361            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25362        })
25363        .await
25364        .unwrap();
25365    let editor = workspace
25366        .update(cx, |workspace, window, cx| {
25367            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25368        })
25369        .unwrap()
25370        .await
25371        .unwrap()
25372        .downcast::<Editor>()
25373        .unwrap();
25374
25375    let fake_server = fake_servers.next().await.unwrap();
25376    editor.update_in(cx, |editor, window, cx| {
25377        editor.set_text("<ad></ad>", window, cx);
25378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25379            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25380        });
25381        set_linked_edit_ranges(
25382            (Point::new(0, 1), Point::new(0, 3)),
25383            (Point::new(0, 6), Point::new(0, 8)),
25384            editor,
25385            cx,
25386        );
25387    });
25388    let mut completion_handle =
25389        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25390            Ok(Some(lsp::CompletionResponse::Array(vec![
25391                lsp::CompletionItem {
25392                    label: "head".to_string(),
25393                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25394                        lsp::InsertReplaceEdit {
25395                            new_text: "head".to_string(),
25396                            insert: lsp::Range::new(
25397                                lsp::Position::new(0, 1),
25398                                lsp::Position::new(0, 3),
25399                            ),
25400                            replace: lsp::Range::new(
25401                                lsp::Position::new(0, 1),
25402                                lsp::Position::new(0, 3),
25403                            ),
25404                        },
25405                    )),
25406                    ..Default::default()
25407                },
25408            ])))
25409        });
25410    editor.update_in(cx, |editor, window, cx| {
25411        editor.show_completions(&ShowCompletions, window, cx);
25412    });
25413    cx.run_until_parked();
25414    completion_handle.next().await.unwrap();
25415    editor.update(cx, |editor, _| {
25416        assert!(
25417            editor.context_menu_visible(),
25418            "Completion menu should be visible"
25419        );
25420    });
25421    editor.update_in(cx, |editor, window, cx| {
25422        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25423    });
25424    cx.executor().run_until_parked();
25425    editor.update(cx, |editor, cx| {
25426        assert_eq!(editor.text(cx), "<head></head>");
25427    });
25428}
25429
25430#[gpui::test]
25431async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25432    init_test(cx, |_| {});
25433
25434    let mut cx = EditorTestContext::new(cx).await;
25435    let language = Arc::new(Language::new(
25436        LanguageConfig {
25437            name: "TSX".into(),
25438            matcher: LanguageMatcher {
25439                path_suffixes: vec!["tsx".to_string()],
25440                ..LanguageMatcher::default()
25441            },
25442            brackets: BracketPairConfig {
25443                pairs: vec![BracketPair {
25444                    start: "<".into(),
25445                    end: ">".into(),
25446                    close: true,
25447                    ..Default::default()
25448                }],
25449                ..Default::default()
25450            },
25451            linked_edit_characters: HashSet::from_iter(['.']),
25452            ..Default::default()
25453        },
25454        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25455    ));
25456    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25457
25458    // Test typing > does not extend linked pair
25459    cx.set_state("<divˇ<div></div>");
25460    cx.update_editor(|editor, _, cx| {
25461        set_linked_edit_ranges(
25462            (Point::new(0, 1), Point::new(0, 4)),
25463            (Point::new(0, 11), Point::new(0, 14)),
25464            editor,
25465            cx,
25466        );
25467    });
25468    cx.update_editor(|editor, window, cx| {
25469        editor.handle_input(">", window, cx);
25470    });
25471    cx.assert_editor_state("<div>ˇ<div></div>");
25472
25473    // Test typing . do extend linked pair
25474    cx.set_state("<Animatedˇ></Animated>");
25475    cx.update_editor(|editor, _, cx| {
25476        set_linked_edit_ranges(
25477            (Point::new(0, 1), Point::new(0, 9)),
25478            (Point::new(0, 12), Point::new(0, 20)),
25479            editor,
25480            cx,
25481        );
25482    });
25483    cx.update_editor(|editor, window, cx| {
25484        editor.handle_input(".", window, cx);
25485    });
25486    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25487    cx.update_editor(|editor, _, cx| {
25488        set_linked_edit_ranges(
25489            (Point::new(0, 1), Point::new(0, 10)),
25490            (Point::new(0, 13), Point::new(0, 21)),
25491            editor,
25492            cx,
25493        );
25494    });
25495    cx.update_editor(|editor, window, cx| {
25496        editor.handle_input("V", window, cx);
25497    });
25498    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25499}
25500
25501#[gpui::test]
25502async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25503    init_test(cx, |_| {});
25504
25505    let fs = FakeFs::new(cx.executor());
25506    fs.insert_tree(
25507        path!("/root"),
25508        json!({
25509            "a": {
25510                "main.rs": "fn main() {}",
25511            },
25512            "foo": {
25513                "bar": {
25514                    "external_file.rs": "pub mod external {}",
25515                }
25516            }
25517        }),
25518    )
25519    .await;
25520
25521    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25522    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25523    language_registry.add(rust_lang());
25524    let _fake_servers = language_registry.register_fake_lsp(
25525        "Rust",
25526        FakeLspAdapter {
25527            ..FakeLspAdapter::default()
25528        },
25529    );
25530    let (workspace, cx) =
25531        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25532    let worktree_id = workspace.update(cx, |workspace, cx| {
25533        workspace.project().update(cx, |project, cx| {
25534            project.worktrees(cx).next().unwrap().read(cx).id()
25535        })
25536    });
25537
25538    let assert_language_servers_count =
25539        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25540            project.update(cx, |project, cx| {
25541                let current = project
25542                    .lsp_store()
25543                    .read(cx)
25544                    .as_local()
25545                    .unwrap()
25546                    .language_servers
25547                    .len();
25548                assert_eq!(expected, current, "{context}");
25549            });
25550        };
25551
25552    assert_language_servers_count(
25553        0,
25554        "No servers should be running before any file is open",
25555        cx,
25556    );
25557    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25558    let main_editor = workspace
25559        .update_in(cx, |workspace, window, cx| {
25560            workspace.open_path(
25561                (worktree_id, rel_path("main.rs")),
25562                Some(pane.downgrade()),
25563                true,
25564                window,
25565                cx,
25566            )
25567        })
25568        .unwrap()
25569        .await
25570        .downcast::<Editor>()
25571        .unwrap();
25572    pane.update(cx, |pane, cx| {
25573        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25574        open_editor.update(cx, |editor, cx| {
25575            assert_eq!(
25576                editor.display_text(cx),
25577                "fn main() {}",
25578                "Original main.rs text on initial open",
25579            );
25580        });
25581        assert_eq!(open_editor, main_editor);
25582    });
25583    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25584
25585    let external_editor = workspace
25586        .update_in(cx, |workspace, window, cx| {
25587            workspace.open_abs_path(
25588                PathBuf::from("/root/foo/bar/external_file.rs"),
25589                OpenOptions::default(),
25590                window,
25591                cx,
25592            )
25593        })
25594        .await
25595        .expect("opening external file")
25596        .downcast::<Editor>()
25597        .expect("downcasted external file's open element to editor");
25598    pane.update(cx, |pane, cx| {
25599        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25600        open_editor.update(cx, |editor, cx| {
25601            assert_eq!(
25602                editor.display_text(cx),
25603                "pub mod external {}",
25604                "External file is open now",
25605            );
25606        });
25607        assert_eq!(open_editor, external_editor);
25608    });
25609    assert_language_servers_count(
25610        1,
25611        "Second, external, *.rs file should join the existing server",
25612        cx,
25613    );
25614
25615    pane.update_in(cx, |pane, window, cx| {
25616        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25617    })
25618    .await
25619    .unwrap();
25620    pane.update_in(cx, |pane, window, cx| {
25621        pane.navigate_backward(&Default::default(), window, cx);
25622    });
25623    cx.run_until_parked();
25624    pane.update(cx, |pane, cx| {
25625        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25626        open_editor.update(cx, |editor, cx| {
25627            assert_eq!(
25628                editor.display_text(cx),
25629                "pub mod external {}",
25630                "External file is open now",
25631            );
25632        });
25633    });
25634    assert_language_servers_count(
25635        1,
25636        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25637        cx,
25638    );
25639
25640    cx.update(|_, cx| {
25641        workspace::reload(cx);
25642    });
25643    assert_language_servers_count(
25644        1,
25645        "After reloading the worktree with local and external files opened, only one project should be started",
25646        cx,
25647    );
25648}
25649
25650#[gpui::test]
25651async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25652    init_test(cx, |_| {});
25653
25654    let mut cx = EditorTestContext::new(cx).await;
25655    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25656    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25657
25658    // test cursor move to start of each line on tab
25659    // for `if`, `elif`, `else`, `while`, `with` and `for`
25660    cx.set_state(indoc! {"
25661        def main():
25662        ˇ    for item in items:
25663        ˇ        while item.active:
25664        ˇ            if item.value > 10:
25665        ˇ                continue
25666        ˇ            elif item.value < 0:
25667        ˇ                break
25668        ˇ            else:
25669        ˇ                with item.context() as ctx:
25670        ˇ                    yield count
25671        ˇ        else:
25672        ˇ            log('while else')
25673        ˇ    else:
25674        ˇ        log('for else')
25675    "});
25676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25677    cx.wait_for_autoindent_applied().await;
25678    cx.assert_editor_state(indoc! {"
25679        def main():
25680            ˇfor item in items:
25681                ˇwhile item.active:
25682                    ˇif item.value > 10:
25683                        ˇcontinue
25684                    ˇelif item.value < 0:
25685                        ˇbreak
25686                    ˇelse:
25687                        ˇwith item.context() as ctx:
25688                            ˇyield count
25689                ˇelse:
25690                    ˇlog('while else')
25691            ˇelse:
25692                ˇlog('for else')
25693    "});
25694    // test relative indent is preserved when tab
25695    // for `if`, `elif`, `else`, `while`, `with` and `for`
25696    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25697    cx.wait_for_autoindent_applied().await;
25698    cx.assert_editor_state(indoc! {"
25699        def main():
25700                ˇfor item in items:
25701                    ˇwhile item.active:
25702                        ˇif item.value > 10:
25703                            ˇcontinue
25704                        ˇelif item.value < 0:
25705                            ˇbreak
25706                        ˇelse:
25707                            ˇwith item.context() as ctx:
25708                                ˇyield count
25709                    ˇelse:
25710                        ˇlog('while else')
25711                ˇelse:
25712                    ˇlog('for else')
25713    "});
25714
25715    // test cursor move to start of each line on tab
25716    // for `try`, `except`, `else`, `finally`, `match` and `def`
25717    cx.set_state(indoc! {"
25718        def main():
25719        ˇ    try:
25720        ˇ        fetch()
25721        ˇ    except ValueError:
25722        ˇ        handle_error()
25723        ˇ    else:
25724        ˇ        match value:
25725        ˇ            case _:
25726        ˇ    finally:
25727        ˇ        def status():
25728        ˇ            return 0
25729    "});
25730    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25731    cx.wait_for_autoindent_applied().await;
25732    cx.assert_editor_state(indoc! {"
25733        def main():
25734            ˇtry:
25735                ˇfetch()
25736            ˇexcept ValueError:
25737                ˇhandle_error()
25738            ˇelse:
25739                ˇmatch value:
25740                    ˇcase _:
25741            ˇfinally:
25742                ˇdef status():
25743                    ˇreturn 0
25744    "});
25745    // test relative indent is preserved when tab
25746    // for `try`, `except`, `else`, `finally`, `match` and `def`
25747    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25748    cx.wait_for_autoindent_applied().await;
25749    cx.assert_editor_state(indoc! {"
25750        def main():
25751                ˇtry:
25752                    ˇfetch()
25753                ˇexcept ValueError:
25754                    ˇhandle_error()
25755                ˇelse:
25756                    ˇmatch value:
25757                        ˇcase _:
25758                ˇfinally:
25759                    ˇdef status():
25760                        ˇreturn 0
25761    "});
25762}
25763
25764#[gpui::test]
25765async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25766    init_test(cx, |_| {});
25767
25768    let mut cx = EditorTestContext::new(cx).await;
25769    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25770    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25771
25772    // test `else` auto outdents when typed inside `if` block
25773    cx.set_state(indoc! {"
25774        def main():
25775            if i == 2:
25776                return
25777                ˇ
25778    "});
25779    cx.update_editor(|editor, window, cx| {
25780        editor.handle_input("else:", window, cx);
25781    });
25782    cx.wait_for_autoindent_applied().await;
25783    cx.assert_editor_state(indoc! {"
25784        def main():
25785            if i == 2:
25786                return
25787            else:ˇ
25788    "});
25789
25790    // test `except` auto outdents when typed inside `try` block
25791    cx.set_state(indoc! {"
25792        def main():
25793            try:
25794                i = 2
25795                ˇ
25796    "});
25797    cx.update_editor(|editor, window, cx| {
25798        editor.handle_input("except:", window, cx);
25799    });
25800    cx.wait_for_autoindent_applied().await;
25801    cx.assert_editor_state(indoc! {"
25802        def main():
25803            try:
25804                i = 2
25805            except:ˇ
25806    "});
25807
25808    // test `else` auto outdents when typed inside `except` block
25809    cx.set_state(indoc! {"
25810        def main():
25811            try:
25812                i = 2
25813            except:
25814                j = 2
25815                ˇ
25816    "});
25817    cx.update_editor(|editor, window, cx| {
25818        editor.handle_input("else:", window, cx);
25819    });
25820    cx.wait_for_autoindent_applied().await;
25821    cx.assert_editor_state(indoc! {"
25822        def main():
25823            try:
25824                i = 2
25825            except:
25826                j = 2
25827            else:ˇ
25828    "});
25829
25830    // test `finally` auto outdents when typed inside `else` block
25831    cx.set_state(indoc! {"
25832        def main():
25833            try:
25834                i = 2
25835            except:
25836                j = 2
25837            else:
25838                k = 2
25839                ˇ
25840    "});
25841    cx.update_editor(|editor, window, cx| {
25842        editor.handle_input("finally:", window, cx);
25843    });
25844    cx.wait_for_autoindent_applied().await;
25845    cx.assert_editor_state(indoc! {"
25846        def main():
25847            try:
25848                i = 2
25849            except:
25850                j = 2
25851            else:
25852                k = 2
25853            finally:ˇ
25854    "});
25855
25856    // test `else` does not outdents when typed inside `except` block right after for block
25857    cx.set_state(indoc! {"
25858        def main():
25859            try:
25860                i = 2
25861            except:
25862                for i in range(n):
25863                    pass
25864                ˇ
25865    "});
25866    cx.update_editor(|editor, window, cx| {
25867        editor.handle_input("else:", window, cx);
25868    });
25869    cx.wait_for_autoindent_applied().await;
25870    cx.assert_editor_state(indoc! {"
25871        def main():
25872            try:
25873                i = 2
25874            except:
25875                for i in range(n):
25876                    pass
25877                else:ˇ
25878    "});
25879
25880    // test `finally` auto outdents when typed inside `else` block right after for block
25881    cx.set_state(indoc! {"
25882        def main():
25883            try:
25884                i = 2
25885            except:
25886                j = 2
25887            else:
25888                for i in range(n):
25889                    pass
25890                ˇ
25891    "});
25892    cx.update_editor(|editor, window, cx| {
25893        editor.handle_input("finally:", window, cx);
25894    });
25895    cx.wait_for_autoindent_applied().await;
25896    cx.assert_editor_state(indoc! {"
25897        def main():
25898            try:
25899                i = 2
25900            except:
25901                j = 2
25902            else:
25903                for i in range(n):
25904                    pass
25905            finally:ˇ
25906    "});
25907
25908    // test `except` outdents to inner "try" block
25909    cx.set_state(indoc! {"
25910        def main():
25911            try:
25912                i = 2
25913                if i == 2:
25914                    try:
25915                        i = 3
25916                        ˇ
25917    "});
25918    cx.update_editor(|editor, window, cx| {
25919        editor.handle_input("except:", window, cx);
25920    });
25921    cx.wait_for_autoindent_applied().await;
25922    cx.assert_editor_state(indoc! {"
25923        def main():
25924            try:
25925                i = 2
25926                if i == 2:
25927                    try:
25928                        i = 3
25929                    except:ˇ
25930    "});
25931
25932    // test `except` outdents to outer "try" block
25933    cx.set_state(indoc! {"
25934        def main():
25935            try:
25936                i = 2
25937                if i == 2:
25938                    try:
25939                        i = 3
25940                ˇ
25941    "});
25942    cx.update_editor(|editor, window, cx| {
25943        editor.handle_input("except:", window, cx);
25944    });
25945    cx.wait_for_autoindent_applied().await;
25946    cx.assert_editor_state(indoc! {"
25947        def main():
25948            try:
25949                i = 2
25950                if i == 2:
25951                    try:
25952                        i = 3
25953            except:ˇ
25954    "});
25955
25956    // test `else` stays at correct indent when typed after `for` block
25957    cx.set_state(indoc! {"
25958        def main():
25959            for i in range(10):
25960                if i == 3:
25961                    break
25962            ˇ
25963    "});
25964    cx.update_editor(|editor, window, cx| {
25965        editor.handle_input("else:", window, cx);
25966    });
25967    cx.wait_for_autoindent_applied().await;
25968    cx.assert_editor_state(indoc! {"
25969        def main():
25970            for i in range(10):
25971                if i == 3:
25972                    break
25973            else:ˇ
25974    "});
25975
25976    // test does not outdent on typing after line with square brackets
25977    cx.set_state(indoc! {"
25978        def f() -> list[str]:
25979            ˇ
25980    "});
25981    cx.update_editor(|editor, window, cx| {
25982        editor.handle_input("a", window, cx);
25983    });
25984    cx.wait_for_autoindent_applied().await;
25985    cx.assert_editor_state(indoc! {"
25986        def f() -> list[str]:
2598725988    "});
25989
25990    // test does not outdent on typing : after case keyword
25991    cx.set_state(indoc! {"
25992        match 1:
25993            caseˇ
25994    "});
25995    cx.update_editor(|editor, window, cx| {
25996        editor.handle_input(":", window, cx);
25997    });
25998    cx.wait_for_autoindent_applied().await;
25999    cx.assert_editor_state(indoc! {"
26000        match 1:
26001            case:ˇ
26002    "});
26003}
26004
26005#[gpui::test]
26006async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26007    init_test(cx, |_| {});
26008    update_test_language_settings(cx, |settings| {
26009        settings.defaults.extend_comment_on_newline = Some(false);
26010    });
26011    let mut cx = EditorTestContext::new(cx).await;
26012    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26013    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26014
26015    // test correct indent after newline on comment
26016    cx.set_state(indoc! {"
26017        # COMMENT:ˇ
26018    "});
26019    cx.update_editor(|editor, window, cx| {
26020        editor.newline(&Newline, window, cx);
26021    });
26022    cx.wait_for_autoindent_applied().await;
26023    cx.assert_editor_state(indoc! {"
26024        # COMMENT:
26025        ˇ
26026    "});
26027
26028    // test correct indent after newline in brackets
26029    cx.set_state(indoc! {"
26030        {ˇ}
26031    "});
26032    cx.update_editor(|editor, window, cx| {
26033        editor.newline(&Newline, window, cx);
26034    });
26035    cx.wait_for_autoindent_applied().await;
26036    cx.assert_editor_state(indoc! {"
26037        {
26038            ˇ
26039        }
26040    "});
26041
26042    cx.set_state(indoc! {"
26043        (ˇ)
26044    "});
26045    cx.update_editor(|editor, window, cx| {
26046        editor.newline(&Newline, window, cx);
26047    });
26048    cx.run_until_parked();
26049    cx.assert_editor_state(indoc! {"
26050        (
26051            ˇ
26052        )
26053    "});
26054
26055    // do not indent after empty lists or dictionaries
26056    cx.set_state(indoc! {"
26057        a = []ˇ
26058    "});
26059    cx.update_editor(|editor, window, cx| {
26060        editor.newline(&Newline, window, cx);
26061    });
26062    cx.run_until_parked();
26063    cx.assert_editor_state(indoc! {"
26064        a = []
26065        ˇ
26066    "});
26067}
26068
26069#[gpui::test]
26070async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26071    init_test(cx, |_| {});
26072
26073    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26074    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26075    language_registry.add(markdown_lang());
26076    language_registry.add(python_lang);
26077
26078    let mut cx = EditorTestContext::new(cx).await;
26079    cx.update_buffer(|buffer, cx| {
26080        buffer.set_language_registry(language_registry);
26081        buffer.set_language(Some(markdown_lang()), cx);
26082    });
26083
26084    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26085    cx.set_state(indoc! {"
26086        # Heading
26087
26088        ```python
26089        def main():
26090            if condition:
26091                pass
26092                ˇ
26093        ```
26094    "});
26095    cx.update_editor(|editor, window, cx| {
26096        editor.handle_input("else:", window, cx);
26097    });
26098    cx.run_until_parked();
26099    cx.assert_editor_state(indoc! {"
26100        # Heading
26101
26102        ```python
26103        def main():
26104            if condition:
26105                pass
26106            else:ˇ
26107        ```
26108    "});
26109}
26110
26111#[gpui::test]
26112async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26113    init_test(cx, |_| {});
26114
26115    let mut cx = EditorTestContext::new(cx).await;
26116    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26117    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26118
26119    // test cursor move to start of each line on tab
26120    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26121    cx.set_state(indoc! {"
26122        function main() {
26123        ˇ    for item in $items; do
26124        ˇ        while [ -n \"$item\" ]; do
26125        ˇ            if [ \"$value\" -gt 10 ]; then
26126        ˇ                continue
26127        ˇ            elif [ \"$value\" -lt 0 ]; then
26128        ˇ                break
26129        ˇ            else
26130        ˇ                echo \"$item\"
26131        ˇ            fi
26132        ˇ        done
26133        ˇ    done
26134        ˇ}
26135    "});
26136    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26137    cx.wait_for_autoindent_applied().await;
26138    cx.assert_editor_state(indoc! {"
26139        function main() {
26140            ˇfor item in $items; do
26141                ˇwhile [ -n \"$item\" ]; do
26142                    ˇif [ \"$value\" -gt 10 ]; then
26143                        ˇcontinue
26144                    ˇelif [ \"$value\" -lt 0 ]; then
26145                        ˇbreak
26146                    ˇelse
26147                        ˇecho \"$item\"
26148                    ˇfi
26149                ˇdone
26150            ˇdone
26151        ˇ}
26152    "});
26153    // test relative indent is preserved when tab
26154    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26155    cx.wait_for_autoindent_applied().await;
26156    cx.assert_editor_state(indoc! {"
26157        function main() {
26158                ˇfor item in $items; do
26159                    ˇwhile [ -n \"$item\" ]; do
26160                        ˇif [ \"$value\" -gt 10 ]; then
26161                            ˇcontinue
26162                        ˇelif [ \"$value\" -lt 0 ]; then
26163                            ˇbreak
26164                        ˇelse
26165                            ˇecho \"$item\"
26166                        ˇfi
26167                    ˇdone
26168                ˇdone
26169            ˇ}
26170    "});
26171
26172    // test cursor move to start of each line on tab
26173    // for `case` statement with patterns
26174    cx.set_state(indoc! {"
26175        function handle() {
26176        ˇ    case \"$1\" in
26177        ˇ        start)
26178        ˇ            echo \"a\"
26179        ˇ            ;;
26180        ˇ        stop)
26181        ˇ            echo \"b\"
26182        ˇ            ;;
26183        ˇ        *)
26184        ˇ            echo \"c\"
26185        ˇ            ;;
26186        ˇ    esac
26187        ˇ}
26188    "});
26189    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26190    cx.wait_for_autoindent_applied().await;
26191    cx.assert_editor_state(indoc! {"
26192        function handle() {
26193            ˇcase \"$1\" in
26194                ˇstart)
26195                    ˇecho \"a\"
26196                    ˇ;;
26197                ˇstop)
26198                    ˇecho \"b\"
26199                    ˇ;;
26200                ˇ*)
26201                    ˇecho \"c\"
26202                    ˇ;;
26203            ˇesac
26204        ˇ}
26205    "});
26206}
26207
26208#[gpui::test]
26209async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26210    init_test(cx, |_| {});
26211
26212    let mut cx = EditorTestContext::new(cx).await;
26213    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26214    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26215
26216    // test indents on comment insert
26217    cx.set_state(indoc! {"
26218        function main() {
26219        ˇ    for item in $items; do
26220        ˇ        while [ -n \"$item\" ]; do
26221        ˇ            if [ \"$value\" -gt 10 ]; then
26222        ˇ                continue
26223        ˇ            elif [ \"$value\" -lt 0 ]; then
26224        ˇ                break
26225        ˇ            else
26226        ˇ                echo \"$item\"
26227        ˇ            fi
26228        ˇ        done
26229        ˇ    done
26230        ˇ}
26231    "});
26232    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26233    cx.wait_for_autoindent_applied().await;
26234    cx.assert_editor_state(indoc! {"
26235        function main() {
26236        #ˇ    for item in $items; do
26237        #ˇ        while [ -n \"$item\" ]; do
26238        #ˇ            if [ \"$value\" -gt 10 ]; then
26239        #ˇ                continue
26240        #ˇ            elif [ \"$value\" -lt 0 ]; then
26241        #ˇ                break
26242        #ˇ            else
26243        #ˇ                echo \"$item\"
26244        #ˇ            fi
26245        #ˇ        done
26246        #ˇ    done
26247        #ˇ}
26248    "});
26249}
26250
26251#[gpui::test]
26252async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26253    init_test(cx, |_| {});
26254
26255    let mut cx = EditorTestContext::new(cx).await;
26256    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26257    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26258
26259    // test `else` auto outdents when typed inside `if` block
26260    cx.set_state(indoc! {"
26261        if [ \"$1\" = \"test\" ]; then
26262            echo \"foo bar\"
26263            ˇ
26264    "});
26265    cx.update_editor(|editor, window, cx| {
26266        editor.handle_input("else", window, cx);
26267    });
26268    cx.wait_for_autoindent_applied().await;
26269    cx.assert_editor_state(indoc! {"
26270        if [ \"$1\" = \"test\" ]; then
26271            echo \"foo bar\"
26272        elseˇ
26273    "});
26274
26275    // test `elif` auto outdents when typed inside `if` block
26276    cx.set_state(indoc! {"
26277        if [ \"$1\" = \"test\" ]; then
26278            echo \"foo bar\"
26279            ˇ
26280    "});
26281    cx.update_editor(|editor, window, cx| {
26282        editor.handle_input("elif", window, cx);
26283    });
26284    cx.wait_for_autoindent_applied().await;
26285    cx.assert_editor_state(indoc! {"
26286        if [ \"$1\" = \"test\" ]; then
26287            echo \"foo bar\"
26288        elifˇ
26289    "});
26290
26291    // test `fi` auto outdents when typed inside `else` block
26292    cx.set_state(indoc! {"
26293        if [ \"$1\" = \"test\" ]; then
26294            echo \"foo bar\"
26295        else
26296            echo \"bar baz\"
26297            ˇ
26298    "});
26299    cx.update_editor(|editor, window, cx| {
26300        editor.handle_input("fi", window, cx);
26301    });
26302    cx.wait_for_autoindent_applied().await;
26303    cx.assert_editor_state(indoc! {"
26304        if [ \"$1\" = \"test\" ]; then
26305            echo \"foo bar\"
26306        else
26307            echo \"bar baz\"
26308        fiˇ
26309    "});
26310
26311    // test `done` auto outdents when typed inside `while` block
26312    cx.set_state(indoc! {"
26313        while read line; do
26314            echo \"$line\"
26315            ˇ
26316    "});
26317    cx.update_editor(|editor, window, cx| {
26318        editor.handle_input("done", window, cx);
26319    });
26320    cx.wait_for_autoindent_applied().await;
26321    cx.assert_editor_state(indoc! {"
26322        while read line; do
26323            echo \"$line\"
26324        doneˇ
26325    "});
26326
26327    // test `done` auto outdents when typed inside `for` block
26328    cx.set_state(indoc! {"
26329        for file in *.txt; do
26330            cat \"$file\"
26331            ˇ
26332    "});
26333    cx.update_editor(|editor, window, cx| {
26334        editor.handle_input("done", window, cx);
26335    });
26336    cx.wait_for_autoindent_applied().await;
26337    cx.assert_editor_state(indoc! {"
26338        for file in *.txt; do
26339            cat \"$file\"
26340        doneˇ
26341    "});
26342
26343    // test `esac` auto outdents when typed inside `case` block
26344    cx.set_state(indoc! {"
26345        case \"$1\" in
26346            start)
26347                echo \"foo bar\"
26348                ;;
26349            stop)
26350                echo \"bar baz\"
26351                ;;
26352            ˇ
26353    "});
26354    cx.update_editor(|editor, window, cx| {
26355        editor.handle_input("esac", window, cx);
26356    });
26357    cx.wait_for_autoindent_applied().await;
26358    cx.assert_editor_state(indoc! {"
26359        case \"$1\" in
26360            start)
26361                echo \"foo bar\"
26362                ;;
26363            stop)
26364                echo \"bar baz\"
26365                ;;
26366        esacˇ
26367    "});
26368
26369    // test `*)` auto outdents when typed inside `case` block
26370    cx.set_state(indoc! {"
26371        case \"$1\" in
26372            start)
26373                echo \"foo bar\"
26374                ;;
26375                ˇ
26376    "});
26377    cx.update_editor(|editor, window, cx| {
26378        editor.handle_input("*)", window, cx);
26379    });
26380    cx.wait_for_autoindent_applied().await;
26381    cx.assert_editor_state(indoc! {"
26382        case \"$1\" in
26383            start)
26384                echo \"foo bar\"
26385                ;;
26386            *)ˇ
26387    "});
26388
26389    // test `fi` outdents to correct level with nested if blocks
26390    cx.set_state(indoc! {"
26391        if [ \"$1\" = \"test\" ]; then
26392            echo \"outer if\"
26393            if [ \"$2\" = \"debug\" ]; then
26394                echo \"inner if\"
26395                ˇ
26396    "});
26397    cx.update_editor(|editor, window, cx| {
26398        editor.handle_input("fi", window, cx);
26399    });
26400    cx.wait_for_autoindent_applied().await;
26401    cx.assert_editor_state(indoc! {"
26402        if [ \"$1\" = \"test\" ]; then
26403            echo \"outer if\"
26404            if [ \"$2\" = \"debug\" ]; then
26405                echo \"inner if\"
26406            fiˇ
26407    "});
26408}
26409
26410#[gpui::test]
26411async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26412    init_test(cx, |_| {});
26413    update_test_language_settings(cx, |settings| {
26414        settings.defaults.extend_comment_on_newline = Some(false);
26415    });
26416    let mut cx = EditorTestContext::new(cx).await;
26417    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26418    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26419
26420    // test correct indent after newline on comment
26421    cx.set_state(indoc! {"
26422        # COMMENT:ˇ
26423    "});
26424    cx.update_editor(|editor, window, cx| {
26425        editor.newline(&Newline, window, cx);
26426    });
26427    cx.wait_for_autoindent_applied().await;
26428    cx.assert_editor_state(indoc! {"
26429        # COMMENT:
26430        ˇ
26431    "});
26432
26433    // test correct indent after newline after `then`
26434    cx.set_state(indoc! {"
26435
26436        if [ \"$1\" = \"test\" ]; thenˇ
26437    "});
26438    cx.update_editor(|editor, window, cx| {
26439        editor.newline(&Newline, window, cx);
26440    });
26441    cx.wait_for_autoindent_applied().await;
26442    cx.assert_editor_state(indoc! {"
26443
26444        if [ \"$1\" = \"test\" ]; then
26445            ˇ
26446    "});
26447
26448    // test correct indent after newline after `else`
26449    cx.set_state(indoc! {"
26450        if [ \"$1\" = \"test\" ]; then
26451        elseˇ
26452    "});
26453    cx.update_editor(|editor, window, cx| {
26454        editor.newline(&Newline, window, cx);
26455    });
26456    cx.wait_for_autoindent_applied().await;
26457    cx.assert_editor_state(indoc! {"
26458        if [ \"$1\" = \"test\" ]; then
26459        else
26460            ˇ
26461    "});
26462
26463    // test correct indent after newline after `elif`
26464    cx.set_state(indoc! {"
26465        if [ \"$1\" = \"test\" ]; then
26466        elifˇ
26467    "});
26468    cx.update_editor(|editor, window, cx| {
26469        editor.newline(&Newline, window, cx);
26470    });
26471    cx.wait_for_autoindent_applied().await;
26472    cx.assert_editor_state(indoc! {"
26473        if [ \"$1\" = \"test\" ]; then
26474        elif
26475            ˇ
26476    "});
26477
26478    // test correct indent after newline after `do`
26479    cx.set_state(indoc! {"
26480        for file in *.txt; doˇ
26481    "});
26482    cx.update_editor(|editor, window, cx| {
26483        editor.newline(&Newline, window, cx);
26484    });
26485    cx.wait_for_autoindent_applied().await;
26486    cx.assert_editor_state(indoc! {"
26487        for file in *.txt; do
26488            ˇ
26489    "});
26490
26491    // test correct indent after newline after case pattern
26492    cx.set_state(indoc! {"
26493        case \"$1\" in
26494            start)ˇ
26495    "});
26496    cx.update_editor(|editor, window, cx| {
26497        editor.newline(&Newline, window, cx);
26498    });
26499    cx.wait_for_autoindent_applied().await;
26500    cx.assert_editor_state(indoc! {"
26501        case \"$1\" in
26502            start)
26503                ˇ
26504    "});
26505
26506    // test correct indent after newline after case pattern
26507    cx.set_state(indoc! {"
26508        case \"$1\" in
26509            start)
26510                ;;
26511            *)ˇ
26512    "});
26513    cx.update_editor(|editor, window, cx| {
26514        editor.newline(&Newline, window, cx);
26515    });
26516    cx.wait_for_autoindent_applied().await;
26517    cx.assert_editor_state(indoc! {"
26518        case \"$1\" in
26519            start)
26520                ;;
26521            *)
26522                ˇ
26523    "});
26524
26525    // test correct indent after newline after function opening brace
26526    cx.set_state(indoc! {"
26527        function test() {ˇ}
26528    "});
26529    cx.update_editor(|editor, window, cx| {
26530        editor.newline(&Newline, window, cx);
26531    });
26532    cx.wait_for_autoindent_applied().await;
26533    cx.assert_editor_state(indoc! {"
26534        function test() {
26535            ˇ
26536        }
26537    "});
26538
26539    // test no extra indent after semicolon on same line
26540    cx.set_state(indoc! {"
26541        echo \"test\"26542    "});
26543    cx.update_editor(|editor, window, cx| {
26544        editor.newline(&Newline, window, cx);
26545    });
26546    cx.wait_for_autoindent_applied().await;
26547    cx.assert_editor_state(indoc! {"
26548        echo \"test\";
26549        ˇ
26550    "});
26551}
26552
26553fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26554    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26555    point..point
26556}
26557
26558#[track_caller]
26559fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26560    let (text, ranges) = marked_text_ranges(marked_text, true);
26561    assert_eq!(editor.text(cx), text);
26562    assert_eq!(
26563        editor.selections.ranges(&editor.display_snapshot(cx)),
26564        ranges
26565            .iter()
26566            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26567            .collect::<Vec<_>>(),
26568        "Assert selections are {}",
26569        marked_text
26570    );
26571}
26572
26573pub fn handle_signature_help_request(
26574    cx: &mut EditorLspTestContext,
26575    mocked_response: lsp::SignatureHelp,
26576) -> impl Future<Output = ()> + use<> {
26577    let mut request =
26578        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26579            let mocked_response = mocked_response.clone();
26580            async move { Ok(Some(mocked_response)) }
26581        });
26582
26583    async move {
26584        request.next().await;
26585    }
26586}
26587
26588#[track_caller]
26589pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26590    cx.update_editor(|editor, _, _| {
26591        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26592            let entries = menu.entries.borrow();
26593            let entries = entries
26594                .iter()
26595                .map(|entry| entry.string.as_str())
26596                .collect::<Vec<_>>();
26597            assert_eq!(entries, expected);
26598        } else {
26599            panic!("Expected completions menu");
26600        }
26601    });
26602}
26603
26604#[gpui::test]
26605async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26606    init_test(cx, |_| {});
26607    let mut cx = EditorLspTestContext::new_rust(
26608        lsp::ServerCapabilities {
26609            completion_provider: Some(lsp::CompletionOptions {
26610                ..Default::default()
26611            }),
26612            ..Default::default()
26613        },
26614        cx,
26615    )
26616    .await;
26617    cx.lsp
26618        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26619            Ok(Some(lsp::CompletionResponse::Array(vec![
26620                lsp::CompletionItem {
26621                    label: "unsafe".into(),
26622                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26623                        range: lsp::Range {
26624                            start: lsp::Position {
26625                                line: 0,
26626                                character: 9,
26627                            },
26628                            end: lsp::Position {
26629                                line: 0,
26630                                character: 11,
26631                            },
26632                        },
26633                        new_text: "unsafe".to_string(),
26634                    })),
26635                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26636                    ..Default::default()
26637                },
26638            ])))
26639        });
26640
26641    cx.update_editor(|editor, _, cx| {
26642        editor.project().unwrap().update(cx, |project, cx| {
26643            project.snippets().update(cx, |snippets, _cx| {
26644                snippets.add_snippet_for_test(
26645                    None,
26646                    PathBuf::from("test_snippets.json"),
26647                    vec![
26648                        Arc::new(project::snippet_provider::Snippet {
26649                            prefix: vec![
26650                                "unlimited word count".to_string(),
26651                                "unlimit word count".to_string(),
26652                                "unlimited unknown".to_string(),
26653                            ],
26654                            body: "this is many words".to_string(),
26655                            description: Some("description".to_string()),
26656                            name: "multi-word snippet test".to_string(),
26657                        }),
26658                        Arc::new(project::snippet_provider::Snippet {
26659                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26660                            body: "fewer words".to_string(),
26661                            description: Some("alt description".to_string()),
26662                            name: "other name".to_string(),
26663                        }),
26664                        Arc::new(project::snippet_provider::Snippet {
26665                            prefix: vec!["ab aa".to_string()],
26666                            body: "abcd".to_string(),
26667                            description: None,
26668                            name: "alphabet".to_string(),
26669                        }),
26670                    ],
26671                );
26672            });
26673        })
26674    });
26675
26676    let get_completions = |cx: &mut EditorLspTestContext| {
26677        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26678            Some(CodeContextMenu::Completions(context_menu)) => {
26679                let entries = context_menu.entries.borrow();
26680                entries
26681                    .iter()
26682                    .map(|entry| entry.string.clone())
26683                    .collect_vec()
26684            }
26685            _ => vec![],
26686        })
26687    };
26688
26689    // snippets:
26690    //  @foo
26691    //  foo bar
26692    //
26693    // when typing:
26694    //
26695    // when typing:
26696    //  - if I type a symbol "open the completions with snippets only"
26697    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26698    //
26699    // stuff we need:
26700    //  - filtering logic change?
26701    //  - remember how far back the completion started.
26702
26703    let test_cases: &[(&str, &[&str])] = &[
26704        (
26705            "un",
26706            &[
26707                "unsafe",
26708                "unlimit word count",
26709                "unlimited unknown",
26710                "unlimited word count",
26711                "unsnip",
26712            ],
26713        ),
26714        (
26715            "u ",
26716            &[
26717                "unlimit word count",
26718                "unlimited unknown",
26719                "unlimited word count",
26720            ],
26721        ),
26722        ("u a", &["ab aa", "unsafe"]), // unsAfe
26723        (
26724            "u u",
26725            &[
26726                "unsafe",
26727                "unlimit word count",
26728                "unlimited unknown", // ranked highest among snippets
26729                "unlimited word count",
26730                "unsnip",
26731            ],
26732        ),
26733        ("uw c", &["unlimit word count", "unlimited word count"]),
26734        (
26735            "u w",
26736            &[
26737                "unlimit word count",
26738                "unlimited word count",
26739                "unlimited unknown",
26740            ],
26741        ),
26742        ("u w ", &["unlimit word count", "unlimited word count"]),
26743        (
26744            "u ",
26745            &[
26746                "unlimit word count",
26747                "unlimited unknown",
26748                "unlimited word count",
26749            ],
26750        ),
26751        ("wor", &[]),
26752        ("uf", &["unsafe"]),
26753        ("af", &["unsafe"]),
26754        ("afu", &[]),
26755        (
26756            "ue",
26757            &["unsafe", "unlimited unknown", "unlimited word count"],
26758        ),
26759        ("@", &["@few"]),
26760        ("@few", &["@few"]),
26761        ("@ ", &[]),
26762        ("a@", &["@few"]),
26763        ("a@f", &["@few", "unsafe"]),
26764        ("a@fw", &["@few"]),
26765        ("a", &["ab aa", "unsafe"]),
26766        ("aa", &["ab aa"]),
26767        ("aaa", &["ab aa"]),
26768        ("ab", &["ab aa"]),
26769        ("ab ", &["ab aa"]),
26770        ("ab a", &["ab aa", "unsafe"]),
26771        ("ab ab", &["ab aa"]),
26772        ("ab ab aa", &["ab aa"]),
26773    ];
26774
26775    for &(input_to_simulate, expected_completions) in test_cases {
26776        cx.set_state("fn a() { ˇ }\n");
26777        for c in input_to_simulate.split("") {
26778            cx.simulate_input(c);
26779            cx.run_until_parked();
26780        }
26781        let expected_completions = expected_completions
26782            .iter()
26783            .map(|s| s.to_string())
26784            .collect_vec();
26785        assert_eq!(
26786            get_completions(&mut cx),
26787            expected_completions,
26788            "< actual / expected >, input = {input_to_simulate:?}",
26789        );
26790    }
26791}
26792
26793/// Handle completion request passing a marked string specifying where the completion
26794/// should be triggered from using '|' character, what range should be replaced, and what completions
26795/// should be returned using '<' and '>' to delimit the range.
26796///
26797/// Also see `handle_completion_request_with_insert_and_replace`.
26798#[track_caller]
26799pub fn handle_completion_request(
26800    marked_string: &str,
26801    completions: Vec<&'static str>,
26802    is_incomplete: bool,
26803    counter: Arc<AtomicUsize>,
26804    cx: &mut EditorLspTestContext,
26805) -> impl Future<Output = ()> {
26806    let complete_from_marker: TextRangeMarker = '|'.into();
26807    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26808    let (_, mut marked_ranges) = marked_text_ranges_by(
26809        marked_string,
26810        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26811    );
26812
26813    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26814        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26815    ));
26816    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26817    let replace_range =
26818        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26819
26820    let mut request =
26821        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26822            let completions = completions.clone();
26823            counter.fetch_add(1, atomic::Ordering::Release);
26824            async move {
26825                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26826                assert_eq!(
26827                    params.text_document_position.position,
26828                    complete_from_position
26829                );
26830                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26831                    is_incomplete,
26832                    item_defaults: None,
26833                    items: completions
26834                        .iter()
26835                        .map(|completion_text| lsp::CompletionItem {
26836                            label: completion_text.to_string(),
26837                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26838                                range: replace_range,
26839                                new_text: completion_text.to_string(),
26840                            })),
26841                            ..Default::default()
26842                        })
26843                        .collect(),
26844                })))
26845            }
26846        });
26847
26848    async move {
26849        request.next().await;
26850    }
26851}
26852
26853/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26854/// given instead, which also contains an `insert` range.
26855///
26856/// This function uses markers to define ranges:
26857/// - `|` marks the cursor position
26858/// - `<>` marks the replace range
26859/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26860pub fn handle_completion_request_with_insert_and_replace(
26861    cx: &mut EditorLspTestContext,
26862    marked_string: &str,
26863    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26864    counter: Arc<AtomicUsize>,
26865) -> impl Future<Output = ()> {
26866    let complete_from_marker: TextRangeMarker = '|'.into();
26867    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26868    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26869
26870    let (_, mut marked_ranges) = marked_text_ranges_by(
26871        marked_string,
26872        vec![
26873            complete_from_marker.clone(),
26874            replace_range_marker.clone(),
26875            insert_range_marker.clone(),
26876        ],
26877    );
26878
26879    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26880        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26881    ));
26882    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26883    let replace_range =
26884        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26885
26886    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26887        Some(ranges) if !ranges.is_empty() => {
26888            let range1 = ranges[0].clone();
26889            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26890        }
26891        _ => lsp::Range {
26892            start: replace_range.start,
26893            end: complete_from_position,
26894        },
26895    };
26896
26897    let mut request =
26898        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26899            let completions = completions.clone();
26900            counter.fetch_add(1, atomic::Ordering::Release);
26901            async move {
26902                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26903                assert_eq!(
26904                    params.text_document_position.position, complete_from_position,
26905                    "marker `|` position doesn't match",
26906                );
26907                Ok(Some(lsp::CompletionResponse::Array(
26908                    completions
26909                        .iter()
26910                        .map(|(label, new_text)| lsp::CompletionItem {
26911                            label: label.to_string(),
26912                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26913                                lsp::InsertReplaceEdit {
26914                                    insert: insert_range,
26915                                    replace: replace_range,
26916                                    new_text: new_text.to_string(),
26917                                },
26918                            )),
26919                            ..Default::default()
26920                        })
26921                        .collect(),
26922                )))
26923            }
26924        });
26925
26926    async move {
26927        request.next().await;
26928    }
26929}
26930
26931fn handle_resolve_completion_request(
26932    cx: &mut EditorLspTestContext,
26933    edits: Option<Vec<(&'static str, &'static str)>>,
26934) -> impl Future<Output = ()> {
26935    let edits = edits.map(|edits| {
26936        edits
26937            .iter()
26938            .map(|(marked_string, new_text)| {
26939                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26940                let replace_range = cx.to_lsp_range(
26941                    MultiBufferOffset(marked_ranges[0].start)
26942                        ..MultiBufferOffset(marked_ranges[0].end),
26943                );
26944                lsp::TextEdit::new(replace_range, new_text.to_string())
26945            })
26946            .collect::<Vec<_>>()
26947    });
26948
26949    let mut request =
26950        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26951            let edits = edits.clone();
26952            async move {
26953                Ok(lsp::CompletionItem {
26954                    additional_text_edits: edits,
26955                    ..Default::default()
26956                })
26957            }
26958        });
26959
26960    async move {
26961        request.next().await;
26962    }
26963}
26964
26965pub(crate) fn update_test_language_settings(
26966    cx: &mut TestAppContext,
26967    f: impl Fn(&mut AllLanguageSettingsContent),
26968) {
26969    cx.update(|cx| {
26970        SettingsStore::update_global(cx, |store, cx| {
26971            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26972        });
26973    });
26974}
26975
26976pub(crate) fn update_test_project_settings(
26977    cx: &mut TestAppContext,
26978    f: impl Fn(&mut ProjectSettingsContent),
26979) {
26980    cx.update(|cx| {
26981        SettingsStore::update_global(cx, |store, cx| {
26982            store.update_user_settings(cx, |settings| f(&mut settings.project));
26983        });
26984    });
26985}
26986
26987pub(crate) fn update_test_editor_settings(
26988    cx: &mut TestAppContext,
26989    f: impl Fn(&mut EditorSettingsContent),
26990) {
26991    cx.update(|cx| {
26992        SettingsStore::update_global(cx, |store, cx| {
26993            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26994        })
26995    })
26996}
26997
26998pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26999    cx.update(|cx| {
27000        assets::Assets.load_test_fonts(cx);
27001        let store = SettingsStore::test(cx);
27002        cx.set_global(store);
27003        theme::init(theme::LoadThemes::JustBase, cx);
27004        release_channel::init(semver::Version::new(0, 0, 0), cx);
27005        crate::init(cx);
27006    });
27007    zlog::init_test();
27008    update_test_language_settings(cx, f);
27009}
27010
27011#[track_caller]
27012fn assert_hunk_revert(
27013    not_reverted_text_with_selections: &str,
27014    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27015    expected_reverted_text_with_selections: &str,
27016    base_text: &str,
27017    cx: &mut EditorLspTestContext,
27018) {
27019    cx.set_state(not_reverted_text_with_selections);
27020    cx.set_head_text(base_text);
27021    cx.executor().run_until_parked();
27022
27023    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27024        let snapshot = editor.snapshot(window, cx);
27025        let reverted_hunk_statuses = snapshot
27026            .buffer_snapshot()
27027            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27028            .map(|hunk| hunk.status().kind)
27029            .collect::<Vec<_>>();
27030
27031        editor.git_restore(&Default::default(), window, cx);
27032        reverted_hunk_statuses
27033    });
27034    cx.executor().run_until_parked();
27035    cx.assert_editor_state(expected_reverted_text_with_selections);
27036    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27037}
27038
27039#[gpui::test(iterations = 10)]
27040async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27041    init_test(cx, |_| {});
27042
27043    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27044    let counter = diagnostic_requests.clone();
27045
27046    let fs = FakeFs::new(cx.executor());
27047    fs.insert_tree(
27048        path!("/a"),
27049        json!({
27050            "first.rs": "fn main() { let a = 5; }",
27051            "second.rs": "// Test file",
27052        }),
27053    )
27054    .await;
27055
27056    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27057    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27058    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27059
27060    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27061    language_registry.add(rust_lang());
27062    let mut fake_servers = language_registry.register_fake_lsp(
27063        "Rust",
27064        FakeLspAdapter {
27065            capabilities: lsp::ServerCapabilities {
27066                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27067                    lsp::DiagnosticOptions {
27068                        identifier: None,
27069                        inter_file_dependencies: true,
27070                        workspace_diagnostics: true,
27071                        work_done_progress_options: Default::default(),
27072                    },
27073                )),
27074                ..Default::default()
27075            },
27076            ..Default::default()
27077        },
27078    );
27079
27080    let editor = workspace
27081        .update(cx, |workspace, window, cx| {
27082            workspace.open_abs_path(
27083                PathBuf::from(path!("/a/first.rs")),
27084                OpenOptions::default(),
27085                window,
27086                cx,
27087            )
27088        })
27089        .unwrap()
27090        .await
27091        .unwrap()
27092        .downcast::<Editor>()
27093        .unwrap();
27094    let fake_server = fake_servers.next().await.unwrap();
27095    let server_id = fake_server.server.server_id();
27096    let mut first_request = fake_server
27097        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27098            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27099            let result_id = Some(new_result_id.to_string());
27100            assert_eq!(
27101                params.text_document.uri,
27102                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27103            );
27104            async move {
27105                Ok(lsp::DocumentDiagnosticReportResult::Report(
27106                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27107                        related_documents: None,
27108                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27109                            items: Vec::new(),
27110                            result_id,
27111                        },
27112                    }),
27113                ))
27114            }
27115        });
27116
27117    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27118        project.update(cx, |project, cx| {
27119            let buffer_id = editor
27120                .read(cx)
27121                .buffer()
27122                .read(cx)
27123                .as_singleton()
27124                .expect("created a singleton buffer")
27125                .read(cx)
27126                .remote_id();
27127            let buffer_result_id = project
27128                .lsp_store()
27129                .read(cx)
27130                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27131            assert_eq!(expected, buffer_result_id);
27132        });
27133    };
27134
27135    ensure_result_id(None, cx);
27136    cx.executor().advance_clock(Duration::from_millis(60));
27137    cx.executor().run_until_parked();
27138    assert_eq!(
27139        diagnostic_requests.load(atomic::Ordering::Acquire),
27140        1,
27141        "Opening file should trigger diagnostic request"
27142    );
27143    first_request
27144        .next()
27145        .await
27146        .expect("should have sent the first diagnostics pull request");
27147    ensure_result_id(Some(SharedString::new("1")), cx);
27148
27149    // Editing should trigger diagnostics
27150    editor.update_in(cx, |editor, window, cx| {
27151        editor.handle_input("2", window, cx)
27152    });
27153    cx.executor().advance_clock(Duration::from_millis(60));
27154    cx.executor().run_until_parked();
27155    assert_eq!(
27156        diagnostic_requests.load(atomic::Ordering::Acquire),
27157        2,
27158        "Editing should trigger diagnostic request"
27159    );
27160    ensure_result_id(Some(SharedString::new("2")), cx);
27161
27162    // Moving cursor should not trigger diagnostic request
27163    editor.update_in(cx, |editor, window, cx| {
27164        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27165            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27166        });
27167    });
27168    cx.executor().advance_clock(Duration::from_millis(60));
27169    cx.executor().run_until_parked();
27170    assert_eq!(
27171        diagnostic_requests.load(atomic::Ordering::Acquire),
27172        2,
27173        "Cursor movement should not trigger diagnostic request"
27174    );
27175    ensure_result_id(Some(SharedString::new("2")), cx);
27176    // Multiple rapid edits should be debounced
27177    for _ in 0..5 {
27178        editor.update_in(cx, |editor, window, cx| {
27179            editor.handle_input("x", window, cx)
27180        });
27181    }
27182    cx.executor().advance_clock(Duration::from_millis(60));
27183    cx.executor().run_until_parked();
27184
27185    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27186    assert!(
27187        final_requests <= 4,
27188        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27189    );
27190    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27191}
27192
27193#[gpui::test]
27194async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27195    // Regression test for issue #11671
27196    // Previously, adding a cursor after moving multiple cursors would reset
27197    // the cursor count instead of adding to the existing cursors.
27198    init_test(cx, |_| {});
27199    let mut cx = EditorTestContext::new(cx).await;
27200
27201    // Create a simple buffer with cursor at start
27202    cx.set_state(indoc! {"
27203        ˇaaaa
27204        bbbb
27205        cccc
27206        dddd
27207        eeee
27208        ffff
27209        gggg
27210        hhhh"});
27211
27212    // Add 2 cursors below (so we have 3 total)
27213    cx.update_editor(|editor, window, cx| {
27214        editor.add_selection_below(&Default::default(), window, cx);
27215        editor.add_selection_below(&Default::default(), window, cx);
27216    });
27217
27218    // Verify we have 3 cursors
27219    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27220    assert_eq!(
27221        initial_count, 3,
27222        "Should have 3 cursors after adding 2 below"
27223    );
27224
27225    // Move down one line
27226    cx.update_editor(|editor, window, cx| {
27227        editor.move_down(&MoveDown, window, cx);
27228    });
27229
27230    // Add another cursor below
27231    cx.update_editor(|editor, window, cx| {
27232        editor.add_selection_below(&Default::default(), window, cx);
27233    });
27234
27235    // Should now have 4 cursors (3 original + 1 new)
27236    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27237    assert_eq!(
27238        final_count, 4,
27239        "Should have 4 cursors after moving and adding another"
27240    );
27241}
27242
27243#[gpui::test]
27244async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27245    init_test(cx, |_| {});
27246
27247    let mut cx = EditorTestContext::new(cx).await;
27248
27249    cx.set_state(indoc!(
27250        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27251           Second line here"#
27252    ));
27253
27254    cx.update_editor(|editor, window, cx| {
27255        // Enable soft wrapping with a narrow width to force soft wrapping and
27256        // confirm that more than 2 rows are being displayed.
27257        editor.set_wrap_width(Some(100.0.into()), cx);
27258        assert!(editor.display_text(cx).lines().count() > 2);
27259
27260        editor.add_selection_below(
27261            &AddSelectionBelow {
27262                skip_soft_wrap: true,
27263            },
27264            window,
27265            cx,
27266        );
27267
27268        assert_eq!(
27269            display_ranges(editor, cx),
27270            &[
27271                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27272                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27273            ]
27274        );
27275
27276        editor.add_selection_above(
27277            &AddSelectionAbove {
27278                skip_soft_wrap: true,
27279            },
27280            window,
27281            cx,
27282        );
27283
27284        assert_eq!(
27285            display_ranges(editor, cx),
27286            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27287        );
27288
27289        editor.add_selection_below(
27290            &AddSelectionBelow {
27291                skip_soft_wrap: false,
27292            },
27293            window,
27294            cx,
27295        );
27296
27297        assert_eq!(
27298            display_ranges(editor, cx),
27299            &[
27300                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27301                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27302            ]
27303        );
27304
27305        editor.add_selection_above(
27306            &AddSelectionAbove {
27307                skip_soft_wrap: false,
27308            },
27309            window,
27310            cx,
27311        );
27312
27313        assert_eq!(
27314            display_ranges(editor, cx),
27315            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27316        );
27317    });
27318
27319    // Set up text where selections are in the middle of a soft-wrapped line.
27320    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27321    // selection should be at the same buffer column, not the same pixel
27322    // position.
27323    cx.set_state(indoc!(
27324        r#"1. Very long line to show «howˇ» a wrapped line would look
27325           2. Very long line to show how a wrapped line would look"#
27326    ));
27327
27328    cx.update_editor(|editor, window, cx| {
27329        // Enable soft wrapping with a narrow width to force soft wrapping and
27330        // confirm that more than 2 rows are being displayed.
27331        editor.set_wrap_width(Some(100.0.into()), cx);
27332        assert!(editor.display_text(cx).lines().count() > 2);
27333
27334        editor.add_selection_below(
27335            &AddSelectionBelow {
27336                skip_soft_wrap: true,
27337            },
27338            window,
27339            cx,
27340        );
27341
27342        // Assert that there's now 2 selections, both selecting the same column
27343        // range in the buffer row.
27344        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27345        let selections = editor.selections.all::<Point>(&display_map);
27346        assert_eq!(selections.len(), 2);
27347        assert_eq!(selections[0].start.column, selections[1].start.column);
27348        assert_eq!(selections[0].end.column, selections[1].end.column);
27349    });
27350}
27351
27352#[gpui::test]
27353async fn test_insert_snippet(cx: &mut TestAppContext) {
27354    init_test(cx, |_| {});
27355    let mut cx = EditorTestContext::new(cx).await;
27356
27357    cx.update_editor(|editor, _, cx| {
27358        editor.project().unwrap().update(cx, |project, cx| {
27359            project.snippets().update(cx, |snippets, _cx| {
27360                let snippet = project::snippet_provider::Snippet {
27361                    prefix: vec![], // no prefix needed!
27362                    body: "an Unspecified".to_string(),
27363                    description: Some("shhhh it's a secret".to_string()),
27364                    name: "super secret snippet".to_string(),
27365                };
27366                snippets.add_snippet_for_test(
27367                    None,
27368                    PathBuf::from("test_snippets.json"),
27369                    vec![Arc::new(snippet)],
27370                );
27371
27372                let snippet = project::snippet_provider::Snippet {
27373                    prefix: vec![], // no prefix needed!
27374                    body: " Location".to_string(),
27375                    description: Some("the word 'location'".to_string()),
27376                    name: "location word".to_string(),
27377                };
27378                snippets.add_snippet_for_test(
27379                    Some("Markdown".to_string()),
27380                    PathBuf::from("test_snippets.json"),
27381                    vec![Arc::new(snippet)],
27382                );
27383            });
27384        })
27385    });
27386
27387    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27388
27389    cx.update_editor(|editor, window, cx| {
27390        editor.insert_snippet_at_selections(
27391            &InsertSnippet {
27392                language: None,
27393                name: Some("super secret snippet".to_string()),
27394                snippet: None,
27395            },
27396            window,
27397            cx,
27398        );
27399
27400        // Language is specified in the action,
27401        // so the buffer language does not need to match
27402        editor.insert_snippet_at_selections(
27403            &InsertSnippet {
27404                language: Some("Markdown".to_string()),
27405                name: Some("location word".to_string()),
27406                snippet: None,
27407            },
27408            window,
27409            cx,
27410        );
27411
27412        editor.insert_snippet_at_selections(
27413            &InsertSnippet {
27414                language: None,
27415                name: None,
27416                snippet: Some("$0 after".to_string()),
27417            },
27418            window,
27419            cx,
27420        );
27421    });
27422
27423    cx.assert_editor_state(
27424        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27425    );
27426}
27427
27428#[gpui::test(iterations = 10)]
27429async fn test_document_colors(cx: &mut TestAppContext) {
27430    let expected_color = Rgba {
27431        r: 0.33,
27432        g: 0.33,
27433        b: 0.33,
27434        a: 0.33,
27435    };
27436
27437    init_test(cx, |_| {});
27438
27439    let fs = FakeFs::new(cx.executor());
27440    fs.insert_tree(
27441        path!("/a"),
27442        json!({
27443            "first.rs": "fn main() { let a = 5; }",
27444        }),
27445    )
27446    .await;
27447
27448    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27449    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27450    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27451
27452    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27453    language_registry.add(rust_lang());
27454    let mut fake_servers = language_registry.register_fake_lsp(
27455        "Rust",
27456        FakeLspAdapter {
27457            capabilities: lsp::ServerCapabilities {
27458                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27459                ..lsp::ServerCapabilities::default()
27460            },
27461            name: "rust-analyzer",
27462            ..FakeLspAdapter::default()
27463        },
27464    );
27465    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27466        "Rust",
27467        FakeLspAdapter {
27468            capabilities: lsp::ServerCapabilities {
27469                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27470                ..lsp::ServerCapabilities::default()
27471            },
27472            name: "not-rust-analyzer",
27473            ..FakeLspAdapter::default()
27474        },
27475    );
27476
27477    let editor = workspace
27478        .update(cx, |workspace, window, cx| {
27479            workspace.open_abs_path(
27480                PathBuf::from(path!("/a/first.rs")),
27481                OpenOptions::default(),
27482                window,
27483                cx,
27484            )
27485        })
27486        .unwrap()
27487        .await
27488        .unwrap()
27489        .downcast::<Editor>()
27490        .unwrap();
27491    let fake_language_server = fake_servers.next().await.unwrap();
27492    let fake_language_server_without_capabilities =
27493        fake_servers_without_capabilities.next().await.unwrap();
27494    let requests_made = Arc::new(AtomicUsize::new(0));
27495    let closure_requests_made = Arc::clone(&requests_made);
27496    let mut color_request_handle = fake_language_server
27497        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27498            let requests_made = Arc::clone(&closure_requests_made);
27499            async move {
27500                assert_eq!(
27501                    params.text_document.uri,
27502                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27503                );
27504                requests_made.fetch_add(1, atomic::Ordering::Release);
27505                Ok(vec![
27506                    lsp::ColorInformation {
27507                        range: lsp::Range {
27508                            start: lsp::Position {
27509                                line: 0,
27510                                character: 0,
27511                            },
27512                            end: lsp::Position {
27513                                line: 0,
27514                                character: 1,
27515                            },
27516                        },
27517                        color: lsp::Color {
27518                            red: 0.33,
27519                            green: 0.33,
27520                            blue: 0.33,
27521                            alpha: 0.33,
27522                        },
27523                    },
27524                    lsp::ColorInformation {
27525                        range: lsp::Range {
27526                            start: lsp::Position {
27527                                line: 0,
27528                                character: 0,
27529                            },
27530                            end: lsp::Position {
27531                                line: 0,
27532                                character: 1,
27533                            },
27534                        },
27535                        color: lsp::Color {
27536                            red: 0.33,
27537                            green: 0.33,
27538                            blue: 0.33,
27539                            alpha: 0.33,
27540                        },
27541                    },
27542                ])
27543            }
27544        });
27545
27546    let _handle = fake_language_server_without_capabilities
27547        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27548            panic!("Should not be called");
27549        });
27550    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27551    color_request_handle.next().await.unwrap();
27552    cx.run_until_parked();
27553    assert_eq!(
27554        1,
27555        requests_made.load(atomic::Ordering::Acquire),
27556        "Should query for colors once per editor open"
27557    );
27558    editor.update_in(cx, |editor, _, cx| {
27559        assert_eq!(
27560            vec![expected_color],
27561            extract_color_inlays(editor, cx),
27562            "Should have an initial inlay"
27563        );
27564    });
27565
27566    // opening another file in a split should not influence the LSP query counter
27567    workspace
27568        .update(cx, |workspace, window, cx| {
27569            assert_eq!(
27570                workspace.panes().len(),
27571                1,
27572                "Should have one pane with one editor"
27573            );
27574            workspace.move_item_to_pane_in_direction(
27575                &MoveItemToPaneInDirection {
27576                    direction: SplitDirection::Right,
27577                    focus: false,
27578                    clone: true,
27579                },
27580                window,
27581                cx,
27582            );
27583        })
27584        .unwrap();
27585    cx.run_until_parked();
27586    workspace
27587        .update(cx, |workspace, _, cx| {
27588            let panes = workspace.panes();
27589            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27590            for pane in panes {
27591                let editor = pane
27592                    .read(cx)
27593                    .active_item()
27594                    .and_then(|item| item.downcast::<Editor>())
27595                    .expect("Should have opened an editor in each split");
27596                let editor_file = editor
27597                    .read(cx)
27598                    .buffer()
27599                    .read(cx)
27600                    .as_singleton()
27601                    .expect("test deals with singleton buffers")
27602                    .read(cx)
27603                    .file()
27604                    .expect("test buffese should have a file")
27605                    .path();
27606                assert_eq!(
27607                    editor_file.as_ref(),
27608                    rel_path("first.rs"),
27609                    "Both editors should be opened for the same file"
27610                )
27611            }
27612        })
27613        .unwrap();
27614
27615    cx.executor().advance_clock(Duration::from_millis(500));
27616    let save = editor.update_in(cx, |editor, window, cx| {
27617        editor.move_to_end(&MoveToEnd, window, cx);
27618        editor.handle_input("dirty", window, cx);
27619        editor.save(
27620            SaveOptions {
27621                format: true,
27622                autosave: true,
27623            },
27624            project.clone(),
27625            window,
27626            cx,
27627        )
27628    });
27629    save.await.unwrap();
27630
27631    color_request_handle.next().await.unwrap();
27632    cx.run_until_parked();
27633    assert_eq!(
27634        2,
27635        requests_made.load(atomic::Ordering::Acquire),
27636        "Should query for colors once per save (deduplicated) and once per formatting after save"
27637    );
27638
27639    drop(editor);
27640    let close = workspace
27641        .update(cx, |workspace, window, cx| {
27642            workspace.active_pane().update(cx, |pane, cx| {
27643                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27644            })
27645        })
27646        .unwrap();
27647    close.await.unwrap();
27648    let close = workspace
27649        .update(cx, |workspace, window, cx| {
27650            workspace.active_pane().update(cx, |pane, cx| {
27651                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27652            })
27653        })
27654        .unwrap();
27655    close.await.unwrap();
27656    assert_eq!(
27657        2,
27658        requests_made.load(atomic::Ordering::Acquire),
27659        "After saving and closing all editors, no extra requests should be made"
27660    );
27661    workspace
27662        .update(cx, |workspace, _, cx| {
27663            assert!(
27664                workspace.active_item(cx).is_none(),
27665                "Should close all editors"
27666            )
27667        })
27668        .unwrap();
27669
27670    workspace
27671        .update(cx, |workspace, window, cx| {
27672            workspace.active_pane().update(cx, |pane, cx| {
27673                pane.navigate_backward(&workspace::GoBack, window, cx);
27674            })
27675        })
27676        .unwrap();
27677    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27678    cx.run_until_parked();
27679    let editor = workspace
27680        .update(cx, |workspace, _, cx| {
27681            workspace
27682                .active_item(cx)
27683                .expect("Should have reopened the editor again after navigating back")
27684                .downcast::<Editor>()
27685                .expect("Should be an editor")
27686        })
27687        .unwrap();
27688
27689    assert_eq!(
27690        2,
27691        requests_made.load(atomic::Ordering::Acquire),
27692        "Cache should be reused on buffer close and reopen"
27693    );
27694    editor.update(cx, |editor, cx| {
27695        assert_eq!(
27696            vec![expected_color],
27697            extract_color_inlays(editor, cx),
27698            "Should have an initial inlay"
27699        );
27700    });
27701
27702    drop(color_request_handle);
27703    let closure_requests_made = Arc::clone(&requests_made);
27704    let mut empty_color_request_handle = fake_language_server
27705        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27706            let requests_made = Arc::clone(&closure_requests_made);
27707            async move {
27708                assert_eq!(
27709                    params.text_document.uri,
27710                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27711                );
27712                requests_made.fetch_add(1, atomic::Ordering::Release);
27713                Ok(Vec::new())
27714            }
27715        });
27716    let save = editor.update_in(cx, |editor, window, cx| {
27717        editor.move_to_end(&MoveToEnd, window, cx);
27718        editor.handle_input("dirty_again", window, cx);
27719        editor.save(
27720            SaveOptions {
27721                format: false,
27722                autosave: true,
27723            },
27724            project.clone(),
27725            window,
27726            cx,
27727        )
27728    });
27729    save.await.unwrap();
27730
27731    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27732    empty_color_request_handle.next().await.unwrap();
27733    cx.run_until_parked();
27734    assert_eq!(
27735        3,
27736        requests_made.load(atomic::Ordering::Acquire),
27737        "Should query for colors once per save only, as formatting was not requested"
27738    );
27739    editor.update(cx, |editor, cx| {
27740        assert_eq!(
27741            Vec::<Rgba>::new(),
27742            extract_color_inlays(editor, cx),
27743            "Should clear all colors when the server returns an empty response"
27744        );
27745    });
27746}
27747
27748#[gpui::test]
27749async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27750    init_test(cx, |_| {});
27751    let (editor, cx) = cx.add_window_view(Editor::single_line);
27752    editor.update_in(cx, |editor, window, cx| {
27753        editor.set_text("oops\n\nwow\n", window, cx)
27754    });
27755    cx.run_until_parked();
27756    editor.update(cx, |editor, cx| {
27757        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27758    });
27759    editor.update(cx, |editor, cx| {
27760        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27761    });
27762    cx.run_until_parked();
27763    editor.update(cx, |editor, cx| {
27764        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27765    });
27766}
27767
27768#[gpui::test]
27769async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27770    init_test(cx, |_| {});
27771
27772    cx.update(|cx| {
27773        register_project_item::<Editor>(cx);
27774    });
27775
27776    let fs = FakeFs::new(cx.executor());
27777    fs.insert_tree("/root1", json!({})).await;
27778    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27779        .await;
27780
27781    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27782    let (workspace, cx) =
27783        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27784
27785    let worktree_id = project.update(cx, |project, cx| {
27786        project.worktrees(cx).next().unwrap().read(cx).id()
27787    });
27788
27789    let handle = workspace
27790        .update_in(cx, |workspace, window, cx| {
27791            let project_path = (worktree_id, rel_path("one.pdf"));
27792            workspace.open_path(project_path, None, true, window, cx)
27793        })
27794        .await
27795        .unwrap();
27796    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
27797    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
27798    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
27799    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
27800}
27801
27802#[gpui::test]
27803async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27804    init_test(cx, |_| {});
27805
27806    let language = Arc::new(Language::new(
27807        LanguageConfig::default(),
27808        Some(tree_sitter_rust::LANGUAGE.into()),
27809    ));
27810
27811    // Test hierarchical sibling navigation
27812    let text = r#"
27813        fn outer() {
27814            if condition {
27815                let a = 1;
27816            }
27817            let b = 2;
27818        }
27819
27820        fn another() {
27821            let c = 3;
27822        }
27823    "#;
27824
27825    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27826    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27827    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27828
27829    // Wait for parsing to complete
27830    editor
27831        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27832        .await;
27833
27834    editor.update_in(cx, |editor, window, cx| {
27835        // Start by selecting "let a = 1;" inside the if block
27836        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27837            s.select_display_ranges([
27838                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27839            ]);
27840        });
27841
27842        let initial_selection = editor
27843            .selections
27844            .display_ranges(&editor.display_snapshot(cx));
27845        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27846
27847        // Test select next sibling - should move up levels to find the next sibling
27848        // Since "let a = 1;" has no siblings in the if block, it should move up
27849        // to find "let b = 2;" which is a sibling of the if block
27850        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27851        let next_selection = editor
27852            .selections
27853            .display_ranges(&editor.display_snapshot(cx));
27854
27855        // Should have a selection and it should be different from the initial
27856        assert_eq!(
27857            next_selection.len(),
27858            1,
27859            "Should have one selection after next"
27860        );
27861        assert_ne!(
27862            next_selection[0], initial_selection[0],
27863            "Next sibling selection should be different"
27864        );
27865
27866        // Test hierarchical navigation by going to the end of the current function
27867        // and trying to navigate to the next function
27868        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27869            s.select_display_ranges([
27870                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27871            ]);
27872        });
27873
27874        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27875        let function_next_selection = editor
27876            .selections
27877            .display_ranges(&editor.display_snapshot(cx));
27878
27879        // Should move to the next function
27880        assert_eq!(
27881            function_next_selection.len(),
27882            1,
27883            "Should have one selection after function next"
27884        );
27885
27886        // Test select previous sibling navigation
27887        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27888        let prev_selection = editor
27889            .selections
27890            .display_ranges(&editor.display_snapshot(cx));
27891
27892        // Should have a selection and it should be different
27893        assert_eq!(
27894            prev_selection.len(),
27895            1,
27896            "Should have one selection after prev"
27897        );
27898        assert_ne!(
27899            prev_selection[0], function_next_selection[0],
27900            "Previous sibling selection should be different from next"
27901        );
27902    });
27903}
27904
27905#[gpui::test]
27906async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27907    init_test(cx, |_| {});
27908
27909    let mut cx = EditorTestContext::new(cx).await;
27910    cx.set_state(
27911        "let ˇvariable = 42;
27912let another = variable + 1;
27913let result = variable * 2;",
27914    );
27915
27916    // Set up document highlights manually (simulating LSP response)
27917    cx.update_editor(|editor, _window, cx| {
27918        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27919
27920        // Create highlights for "variable" occurrences
27921        let highlight_ranges = [
27922            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27923            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27924            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27925        ];
27926
27927        let anchor_ranges: Vec<_> = highlight_ranges
27928            .iter()
27929            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27930            .collect();
27931
27932        editor.highlight_background::<DocumentHighlightRead>(
27933            &anchor_ranges,
27934            |_, theme| theme.colors().editor_document_highlight_read_background,
27935            cx,
27936        );
27937    });
27938
27939    // Go to next highlight - should move to second "variable"
27940    cx.update_editor(|editor, window, cx| {
27941        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27942    });
27943    cx.assert_editor_state(
27944        "let variable = 42;
27945let another = ˇvariable + 1;
27946let result = variable * 2;",
27947    );
27948
27949    // Go to next highlight - should move to third "variable"
27950    cx.update_editor(|editor, window, cx| {
27951        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27952    });
27953    cx.assert_editor_state(
27954        "let variable = 42;
27955let another = variable + 1;
27956let result = ˇvariable * 2;",
27957    );
27958
27959    // Go to next highlight - should stay at third "variable" (no wrap-around)
27960    cx.update_editor(|editor, window, cx| {
27961        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27962    });
27963    cx.assert_editor_state(
27964        "let variable = 42;
27965let another = variable + 1;
27966let result = ˇvariable * 2;",
27967    );
27968
27969    // Now test going backwards from third position
27970    cx.update_editor(|editor, window, cx| {
27971        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27972    });
27973    cx.assert_editor_state(
27974        "let variable = 42;
27975let another = ˇvariable + 1;
27976let result = variable * 2;",
27977    );
27978
27979    // Go to previous highlight - should move to first "variable"
27980    cx.update_editor(|editor, window, cx| {
27981        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27982    });
27983    cx.assert_editor_state(
27984        "let ˇvariable = 42;
27985let another = variable + 1;
27986let result = variable * 2;",
27987    );
27988
27989    // Go to previous highlight - should stay on first "variable"
27990    cx.update_editor(|editor, window, cx| {
27991        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27992    });
27993    cx.assert_editor_state(
27994        "let ˇvariable = 42;
27995let another = variable + 1;
27996let result = variable * 2;",
27997    );
27998}
27999
28000#[gpui::test]
28001async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28002    cx: &mut gpui::TestAppContext,
28003) {
28004    init_test(cx, |_| {});
28005
28006    let url = "https://zed.dev";
28007
28008    let markdown_language = Arc::new(Language::new(
28009        LanguageConfig {
28010            name: "Markdown".into(),
28011            ..LanguageConfig::default()
28012        },
28013        None,
28014    ));
28015
28016    let mut cx = EditorTestContext::new(cx).await;
28017    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28018    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28019
28020    cx.update_editor(|editor, window, cx| {
28021        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28022        editor.paste(&Paste, window, cx);
28023    });
28024
28025    cx.assert_editor_state(&format!(
28026        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28027    ));
28028}
28029
28030#[gpui::test]
28031async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28032    init_test(cx, |_| {});
28033
28034    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28035    let mut cx = EditorTestContext::new(cx).await;
28036
28037    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28038
28039    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28040    cx.set_state(&indoc! {"
28041        - [ ] Item 1
28042            - [ ] Item 1.a
28043        - [ˇ] Item 2
28044            - [ˇ] Item 2.a
28045            - [ˇ] Item 2.b
28046        "
28047    });
28048    cx.update_editor(|editor, window, cx| {
28049        editor.handle_input("x", window, cx);
28050    });
28051    cx.run_until_parked();
28052    cx.assert_editor_state(indoc! {"
28053        - [ ] Item 1
28054            - [ ] Item 1.a
28055        - [xˇ] Item 2
28056            - [xˇ] Item 2.a
28057            - [xˇ] Item 2.b
28058        "
28059    });
28060
28061    // Case 2: Test adding new line after nested list continues the list with unchecked task
28062    cx.set_state(&indoc! {"
28063        - [ ] Item 1
28064            - [ ] Item 1.a
28065        - [x] Item 2
28066            - [x] Item 2.a
28067            - [x] Item 2.bˇ"
28068    });
28069    cx.update_editor(|editor, window, cx| {
28070        editor.newline(&Newline, window, cx);
28071    });
28072    cx.assert_editor_state(indoc! {"
28073        - [ ] Item 1
28074            - [ ] Item 1.a
28075        - [x] Item 2
28076            - [x] Item 2.a
28077            - [x] Item 2.b
28078            - [ ] ˇ"
28079    });
28080
28081    // Case 3: Test adding content to continued list item
28082    cx.update_editor(|editor, window, cx| {
28083        editor.handle_input("Item 2.c", window, cx);
28084    });
28085    cx.run_until_parked();
28086    cx.assert_editor_state(indoc! {"
28087        - [ ] Item 1
28088            - [ ] Item 1.a
28089        - [x] Item 2
28090            - [x] Item 2.a
28091            - [x] Item 2.b
28092            - [ ] Item 2.cˇ"
28093    });
28094
28095    // Case 4: Test adding new line after nested ordered list continues with next number
28096    cx.set_state(indoc! {"
28097        1. Item 1
28098            1. Item 1.a
28099        2. Item 2
28100            1. Item 2.a
28101            2. Item 2.bˇ"
28102    });
28103    cx.update_editor(|editor, window, cx| {
28104        editor.newline(&Newline, window, cx);
28105    });
28106    cx.assert_editor_state(indoc! {"
28107        1. Item 1
28108            1. Item 1.a
28109        2. Item 2
28110            1. Item 2.a
28111            2. Item 2.b
28112            3. ˇ"
28113    });
28114
28115    // Case 5: Adding content to continued ordered list item
28116    cx.update_editor(|editor, window, cx| {
28117        editor.handle_input("Item 2.c", window, cx);
28118    });
28119    cx.run_until_parked();
28120    cx.assert_editor_state(indoc! {"
28121        1. Item 1
28122            1. Item 1.a
28123        2. Item 2
28124            1. Item 2.a
28125            2. Item 2.b
28126            3. Item 2.cˇ"
28127    });
28128
28129    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28130    cx.set_state(indoc! {"
28131        - Item 1
28132            - Item 1.a
28133            - Item 1.a
28134        ˇ"});
28135    cx.update_editor(|editor, window, cx| {
28136        editor.handle_input("-", window, cx);
28137    });
28138    cx.run_until_parked();
28139    cx.assert_editor_state(indoc! {"
28140        - Item 1
28141            - Item 1.a
28142            - Item 1.a
28143"});
28144
28145    // Case 7: Test blockquote newline preserves something
28146    cx.set_state(indoc! {"
28147        > Item 1ˇ"
28148    });
28149    cx.update_editor(|editor, window, cx| {
28150        editor.newline(&Newline, window, cx);
28151    });
28152    cx.assert_editor_state(indoc! {"
28153        > Item 1
28154        ˇ"
28155    });
28156}
28157
28158#[gpui::test]
28159async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28160    cx: &mut gpui::TestAppContext,
28161) {
28162    init_test(cx, |_| {});
28163
28164    let url = "https://zed.dev";
28165
28166    let markdown_language = Arc::new(Language::new(
28167        LanguageConfig {
28168            name: "Markdown".into(),
28169            ..LanguageConfig::default()
28170        },
28171        None,
28172    ));
28173
28174    let mut cx = EditorTestContext::new(cx).await;
28175    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28176    cx.set_state(&format!(
28177        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28178    ));
28179
28180    cx.update_editor(|editor, window, cx| {
28181        editor.copy(&Copy, window, cx);
28182    });
28183
28184    cx.set_state(&format!(
28185        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28186    ));
28187
28188    cx.update_editor(|editor, window, cx| {
28189        editor.paste(&Paste, window, cx);
28190    });
28191
28192    cx.assert_editor_state(&format!(
28193        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28194    ));
28195}
28196
28197#[gpui::test]
28198async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28199    cx: &mut gpui::TestAppContext,
28200) {
28201    init_test(cx, |_| {});
28202
28203    let url = "https://zed.dev";
28204
28205    let markdown_language = Arc::new(Language::new(
28206        LanguageConfig {
28207            name: "Markdown".into(),
28208            ..LanguageConfig::default()
28209        },
28210        None,
28211    ));
28212
28213    let mut cx = EditorTestContext::new(cx).await;
28214    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28215    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28216
28217    cx.update_editor(|editor, window, cx| {
28218        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28219        editor.paste(&Paste, window, cx);
28220    });
28221
28222    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28223}
28224
28225#[gpui::test]
28226async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28227    cx: &mut gpui::TestAppContext,
28228) {
28229    init_test(cx, |_| {});
28230
28231    let text = "Awesome";
28232
28233    let markdown_language = Arc::new(Language::new(
28234        LanguageConfig {
28235            name: "Markdown".into(),
28236            ..LanguageConfig::default()
28237        },
28238        None,
28239    ));
28240
28241    let mut cx = EditorTestContext::new(cx).await;
28242    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28243    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28244
28245    cx.update_editor(|editor, window, cx| {
28246        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28247        editor.paste(&Paste, window, cx);
28248    });
28249
28250    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28251}
28252
28253#[gpui::test]
28254async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28255    cx: &mut gpui::TestAppContext,
28256) {
28257    init_test(cx, |_| {});
28258
28259    let url = "https://zed.dev";
28260
28261    let markdown_language = Arc::new(Language::new(
28262        LanguageConfig {
28263            name: "Rust".into(),
28264            ..LanguageConfig::default()
28265        },
28266        None,
28267    ));
28268
28269    let mut cx = EditorTestContext::new(cx).await;
28270    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28271    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28272
28273    cx.update_editor(|editor, window, cx| {
28274        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28275        editor.paste(&Paste, window, cx);
28276    });
28277
28278    cx.assert_editor_state(&format!(
28279        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28280    ));
28281}
28282
28283#[gpui::test]
28284async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28285    cx: &mut TestAppContext,
28286) {
28287    init_test(cx, |_| {});
28288
28289    let url = "https://zed.dev";
28290
28291    let markdown_language = Arc::new(Language::new(
28292        LanguageConfig {
28293            name: "Markdown".into(),
28294            ..LanguageConfig::default()
28295        },
28296        None,
28297    ));
28298
28299    let (editor, cx) = cx.add_window_view(|window, cx| {
28300        let multi_buffer = MultiBuffer::build_multi(
28301            [
28302                ("this will embed -> link", vec![Point::row_range(0..1)]),
28303                ("this will replace -> link", vec![Point::row_range(0..1)]),
28304            ],
28305            cx,
28306        );
28307        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28309            s.select_ranges(vec![
28310                Point::new(0, 19)..Point::new(0, 23),
28311                Point::new(1, 21)..Point::new(1, 25),
28312            ])
28313        });
28314        let first_buffer_id = multi_buffer
28315            .read(cx)
28316            .excerpt_buffer_ids()
28317            .into_iter()
28318            .next()
28319            .unwrap();
28320        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28321        first_buffer.update(cx, |buffer, cx| {
28322            buffer.set_language(Some(markdown_language.clone()), cx);
28323        });
28324
28325        editor
28326    });
28327    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28328
28329    cx.update_editor(|editor, window, cx| {
28330        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28331        editor.paste(&Paste, window, cx);
28332    });
28333
28334    cx.assert_editor_state(&format!(
28335        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28336    ));
28337}
28338
28339#[gpui::test]
28340async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28341    init_test(cx, |_| {});
28342
28343    let fs = FakeFs::new(cx.executor());
28344    fs.insert_tree(
28345        path!("/project"),
28346        json!({
28347            "first.rs": "# First Document\nSome content here.",
28348            "second.rs": "Plain text content for second file.",
28349        }),
28350    )
28351    .await;
28352
28353    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28355    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28356
28357    let language = rust_lang();
28358    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28359    language_registry.add(language.clone());
28360    let mut fake_servers = language_registry.register_fake_lsp(
28361        "Rust",
28362        FakeLspAdapter {
28363            ..FakeLspAdapter::default()
28364        },
28365    );
28366
28367    let buffer1 = project
28368        .update(cx, |project, cx| {
28369            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28370        })
28371        .await
28372        .unwrap();
28373    let buffer2 = project
28374        .update(cx, |project, cx| {
28375            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28376        })
28377        .await
28378        .unwrap();
28379
28380    let multi_buffer = cx.new(|cx| {
28381        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28382        multi_buffer.set_excerpts_for_path(
28383            PathKey::for_buffer(&buffer1, cx),
28384            buffer1.clone(),
28385            [Point::zero()..buffer1.read(cx).max_point()],
28386            3,
28387            cx,
28388        );
28389        multi_buffer.set_excerpts_for_path(
28390            PathKey::for_buffer(&buffer2, cx),
28391            buffer2.clone(),
28392            [Point::zero()..buffer1.read(cx).max_point()],
28393            3,
28394            cx,
28395        );
28396        multi_buffer
28397    });
28398
28399    let (editor, cx) = cx.add_window_view(|window, cx| {
28400        Editor::new(
28401            EditorMode::full(),
28402            multi_buffer,
28403            Some(project.clone()),
28404            window,
28405            cx,
28406        )
28407    });
28408
28409    let fake_language_server = fake_servers.next().await.unwrap();
28410
28411    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28412
28413    let save = editor.update_in(cx, |editor, window, cx| {
28414        assert!(editor.is_dirty(cx));
28415
28416        editor.save(
28417            SaveOptions {
28418                format: true,
28419                autosave: true,
28420            },
28421            project,
28422            window,
28423            cx,
28424        )
28425    });
28426    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28427    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28428    let mut done_edit_rx = Some(done_edit_rx);
28429    let mut start_edit_tx = Some(start_edit_tx);
28430
28431    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28432        start_edit_tx.take().unwrap().send(()).unwrap();
28433        let done_edit_rx = done_edit_rx.take().unwrap();
28434        async move {
28435            done_edit_rx.await.unwrap();
28436            Ok(None)
28437        }
28438    });
28439
28440    start_edit_rx.await.unwrap();
28441    buffer2
28442        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28443        .unwrap();
28444
28445    done_edit_tx.send(()).unwrap();
28446
28447    save.await.unwrap();
28448    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28449}
28450
28451#[track_caller]
28452fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28453    editor
28454        .all_inlays(cx)
28455        .into_iter()
28456        .filter_map(|inlay| inlay.get_color())
28457        .map(Rgba::from)
28458        .collect()
28459}
28460
28461#[gpui::test]
28462fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28463    init_test(cx, |_| {});
28464
28465    let editor = cx.add_window(|window, cx| {
28466        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28467        build_editor(buffer, window, cx)
28468    });
28469
28470    editor
28471        .update(cx, |editor, window, cx| {
28472            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28473                s.select_display_ranges([
28474                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28475                ])
28476            });
28477
28478            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28479
28480            assert_eq!(
28481                editor.display_text(cx),
28482                "line1\nline2\nline2",
28483                "Duplicating last line upward should create duplicate above, not on same line"
28484            );
28485
28486            assert_eq!(
28487                editor
28488                    .selections
28489                    .display_ranges(&editor.display_snapshot(cx)),
28490                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28491                "Selection should move to the duplicated line"
28492            );
28493        })
28494        .unwrap();
28495}
28496
28497#[gpui::test]
28498async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28499    init_test(cx, |_| {});
28500
28501    let mut cx = EditorTestContext::new(cx).await;
28502
28503    cx.set_state("line1\nline2ˇ");
28504
28505    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28506
28507    let clipboard_text = cx
28508        .read_from_clipboard()
28509        .and_then(|item| item.text().as_deref().map(str::to_string));
28510
28511    assert_eq!(
28512        clipboard_text,
28513        Some("line2\n".to_string()),
28514        "Copying a line without trailing newline should include a newline"
28515    );
28516
28517    cx.set_state("line1\nˇ");
28518
28519    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28520
28521    cx.assert_editor_state("line1\nline2\nˇ");
28522}
28523
28524#[gpui::test]
28525async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28526    init_test(cx, |_| {});
28527
28528    let mut cx = EditorTestContext::new(cx).await;
28529
28530    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28531
28532    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28533
28534    let clipboard_text = cx
28535        .read_from_clipboard()
28536        .and_then(|item| item.text().as_deref().map(str::to_string));
28537
28538    assert_eq!(
28539        clipboard_text,
28540        Some("line1\nline2\nline3\n".to_string()),
28541        "Copying multiple lines should include a single newline between lines"
28542    );
28543
28544    cx.set_state("lineA\nˇ");
28545
28546    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28547
28548    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28549}
28550
28551#[gpui::test]
28552async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28553    init_test(cx, |_| {});
28554
28555    let mut cx = EditorTestContext::new(cx).await;
28556
28557    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28558
28559    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28560
28561    let clipboard_text = cx
28562        .read_from_clipboard()
28563        .and_then(|item| item.text().as_deref().map(str::to_string));
28564
28565    assert_eq!(
28566        clipboard_text,
28567        Some("line1\nline2\nline3\n".to_string()),
28568        "Copying multiple lines should include a single newline between lines"
28569    );
28570
28571    cx.set_state("lineA\nˇ");
28572
28573    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28574
28575    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28576}
28577
28578#[gpui::test]
28579async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28580    init_test(cx, |_| {});
28581
28582    let mut cx = EditorTestContext::new(cx).await;
28583
28584    cx.set_state("line1\nline2ˇ");
28585    cx.update_editor(|e, window, cx| {
28586        e.set_mode(EditorMode::SingleLine);
28587        assert!(e.key_context(window, cx).contains("end_of_input"));
28588    });
28589    cx.set_state("ˇline1\nline2");
28590    cx.update_editor(|e, window, cx| {
28591        assert!(!e.key_context(window, cx).contains("end_of_input"));
28592    });
28593    cx.set_state("line1ˇ\nline2");
28594    cx.update_editor(|e, window, cx| {
28595        assert!(!e.key_context(window, cx).contains("end_of_input"));
28596    });
28597}
28598
28599#[gpui::test]
28600async fn test_sticky_scroll(cx: &mut TestAppContext) {
28601    init_test(cx, |_| {});
28602    let mut cx = EditorTestContext::new(cx).await;
28603
28604    let buffer = indoc! {"
28605            ˇfn foo() {
28606                let abc = 123;
28607            }
28608            struct Bar;
28609            impl Bar {
28610                fn new() -> Self {
28611                    Self
28612                }
28613            }
28614            fn baz() {
28615            }
28616        "};
28617    cx.set_state(&buffer);
28618
28619    cx.update_editor(|e, _, cx| {
28620        e.buffer()
28621            .read(cx)
28622            .as_singleton()
28623            .unwrap()
28624            .update(cx, |buffer, cx| {
28625                buffer.set_language(Some(rust_lang()), cx);
28626            })
28627    });
28628
28629    let mut sticky_headers = |offset: ScrollOffset| {
28630        cx.update_editor(|e, window, cx| {
28631            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28632            let style = e.style(cx).clone();
28633            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28634                .into_iter()
28635                .map(
28636                    |StickyHeader {
28637                         start_point,
28638                         offset,
28639                         ..
28640                     }| { (start_point, offset) },
28641                )
28642                .collect::<Vec<_>>()
28643        })
28644    };
28645
28646    let fn_foo = Point { row: 0, column: 0 };
28647    let impl_bar = Point { row: 4, column: 0 };
28648    let fn_new = Point { row: 5, column: 4 };
28649
28650    assert_eq!(sticky_headers(0.0), vec![]);
28651    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28652    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28653    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28654    assert_eq!(sticky_headers(2.0), vec![]);
28655    assert_eq!(sticky_headers(2.5), vec![]);
28656    assert_eq!(sticky_headers(3.0), vec![]);
28657    assert_eq!(sticky_headers(3.5), vec![]);
28658    assert_eq!(sticky_headers(4.0), vec![]);
28659    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28660    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28661    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28662    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28663    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28664    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28665    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28666    assert_eq!(sticky_headers(8.0), vec![]);
28667    assert_eq!(sticky_headers(8.5), vec![]);
28668    assert_eq!(sticky_headers(9.0), vec![]);
28669    assert_eq!(sticky_headers(9.5), vec![]);
28670    assert_eq!(sticky_headers(10.0), vec![]);
28671}
28672
28673#[gpui::test]
28674fn test_relative_line_numbers(cx: &mut TestAppContext) {
28675    init_test(cx, |_| {});
28676
28677    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
28678    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
28679    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
28680
28681    let multibuffer = cx.new(|cx| {
28682        let mut multibuffer = MultiBuffer::new(ReadWrite);
28683        multibuffer.push_excerpts(
28684            buffer_1.clone(),
28685            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28686            cx,
28687        );
28688        multibuffer.push_excerpts(
28689            buffer_2.clone(),
28690            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28691            cx,
28692        );
28693        multibuffer.push_excerpts(
28694            buffer_3.clone(),
28695            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28696            cx,
28697        );
28698        multibuffer
28699    });
28700
28701    // wrapped contents of multibuffer:
28702    //    aaa
28703    //    aaa
28704    //    aaa
28705    //    a
28706    //    bbb
28707    //
28708    //    ccc
28709    //    ccc
28710    //    ccc
28711    //    c
28712    //    ddd
28713    //
28714    //    eee
28715    //    fff
28716    //    fff
28717    //    fff
28718    //    f
28719
28720    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
28721    editor.update_in(cx, |editor, window, cx| {
28722        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
28723
28724        // includes trailing newlines.
28725        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
28726        let expected_wrapped_line_numbers = [
28727            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
28728        ];
28729
28730        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28731            s.select_ranges([
28732                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
28733            ]);
28734        });
28735
28736        let snapshot = editor.snapshot(window, cx);
28737
28738        // these are all 0-indexed
28739        let base_display_row = DisplayRow(11);
28740        let base_row = 3;
28741        let wrapped_base_row = 7;
28742
28743        // test not counting wrapped lines
28744        let expected_relative_numbers = expected_line_numbers
28745            .into_iter()
28746            .enumerate()
28747            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
28748            .collect_vec();
28749        let actual_relative_numbers = snapshot
28750            .calculate_relative_line_numbers(
28751                &(DisplayRow(0)..DisplayRow(24)),
28752                base_display_row,
28753                false,
28754            )
28755            .into_iter()
28756            .sorted()
28757            .collect_vec();
28758        assert_eq!(expected_relative_numbers, actual_relative_numbers);
28759        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
28760        for (display_row, relative_number) in expected_relative_numbers {
28761            assert_eq!(
28762                relative_number,
28763                snapshot
28764                    .relative_line_delta(display_row, base_display_row, false)
28765                    .unsigned_abs() as u32,
28766            );
28767        }
28768
28769        // test counting wrapped lines
28770        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
28771            .into_iter()
28772            .enumerate()
28773            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
28774            .filter(|(row, _)| *row != base_display_row)
28775            .collect_vec();
28776        let actual_relative_numbers = snapshot
28777            .calculate_relative_line_numbers(
28778                &(DisplayRow(0)..DisplayRow(24)),
28779                base_display_row,
28780                true,
28781            )
28782            .into_iter()
28783            .sorted()
28784            .collect_vec();
28785        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
28786        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
28787        for (display_row, relative_number) in expected_wrapped_relative_numbers {
28788            assert_eq!(
28789                relative_number,
28790                snapshot
28791                    .relative_line_delta(display_row, base_display_row, true)
28792                    .unsigned_abs() as u32,
28793            );
28794        }
28795    });
28796}
28797
28798#[gpui::test]
28799async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28800    init_test(cx, |_| {});
28801    cx.update(|cx| {
28802        SettingsStore::update_global(cx, |store, cx| {
28803            store.update_user_settings(cx, |settings| {
28804                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28805                    enabled: Some(true),
28806                })
28807            });
28808        });
28809    });
28810    let mut cx = EditorTestContext::new(cx).await;
28811
28812    let line_height = cx.update_editor(|editor, window, cx| {
28813        editor
28814            .style(cx)
28815            .text
28816            .line_height_in_pixels(window.rem_size())
28817    });
28818
28819    let buffer = indoc! {"
28820            ˇfn foo() {
28821                let abc = 123;
28822            }
28823            struct Bar;
28824            impl Bar {
28825                fn new() -> Self {
28826                    Self
28827                }
28828            }
28829            fn baz() {
28830            }
28831        "};
28832    cx.set_state(&buffer);
28833
28834    cx.update_editor(|e, _, cx| {
28835        e.buffer()
28836            .read(cx)
28837            .as_singleton()
28838            .unwrap()
28839            .update(cx, |buffer, cx| {
28840                buffer.set_language(Some(rust_lang()), cx);
28841            })
28842    });
28843
28844    let fn_foo = || empty_range(0, 0);
28845    let impl_bar = || empty_range(4, 0);
28846    let fn_new = || empty_range(5, 4);
28847
28848    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28849        cx.update_editor(|e, window, cx| {
28850            e.scroll(
28851                gpui::Point {
28852                    x: 0.,
28853                    y: scroll_offset,
28854                },
28855                None,
28856                window,
28857                cx,
28858            );
28859        });
28860        cx.simulate_click(
28861            gpui::Point {
28862                x: px(0.),
28863                y: click_offset as f32 * line_height,
28864            },
28865            Modifiers::none(),
28866        );
28867        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28868    };
28869
28870    assert_eq!(
28871        scroll_and_click(
28872            4.5, // impl Bar is halfway off the screen
28873            0.0  // click top of screen
28874        ),
28875        // scrolled to impl Bar
28876        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28877    );
28878
28879    assert_eq!(
28880        scroll_and_click(
28881            4.5,  // impl Bar is halfway off the screen
28882            0.25  // click middle of impl Bar
28883        ),
28884        // scrolled to impl Bar
28885        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28886    );
28887
28888    assert_eq!(
28889        scroll_and_click(
28890            4.5, // impl Bar is halfway off the screen
28891            1.5  // click below impl Bar (e.g. fn new())
28892        ),
28893        // scrolled to fn new() - this is below the impl Bar header which has persisted
28894        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28895    );
28896
28897    assert_eq!(
28898        scroll_and_click(
28899            5.5,  // fn new is halfway underneath impl Bar
28900            0.75  // click on the overlap of impl Bar and fn new()
28901        ),
28902        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28903    );
28904
28905    assert_eq!(
28906        scroll_and_click(
28907            5.5,  // fn new is halfway underneath impl Bar
28908            1.25  // click on the visible part of fn new()
28909        ),
28910        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28911    );
28912
28913    assert_eq!(
28914        scroll_and_click(
28915            1.5, // fn foo is halfway off the screen
28916            0.0  // click top of screen
28917        ),
28918        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28919    );
28920
28921    assert_eq!(
28922        scroll_and_click(
28923            1.5,  // fn foo is halfway off the screen
28924            0.75  // click visible part of let abc...
28925        )
28926        .0,
28927        // no change in scroll
28928        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28929        (gpui::Point { x: 0., y: 1.5 })
28930    );
28931}
28932
28933#[gpui::test]
28934async fn test_next_prev_reference(cx: &mut TestAppContext) {
28935    const CYCLE_POSITIONS: &[&'static str] = &[
28936        indoc! {"
28937            fn foo() {
28938                let ˇabc = 123;
28939                let x = abc + 1;
28940                let y = abc + 2;
28941                let z = abc + 2;
28942            }
28943        "},
28944        indoc! {"
28945            fn foo() {
28946                let abc = 123;
28947                let x = ˇabc + 1;
28948                let y = abc + 2;
28949                let z = abc + 2;
28950            }
28951        "},
28952        indoc! {"
28953            fn foo() {
28954                let abc = 123;
28955                let x = abc + 1;
28956                let y = ˇabc + 2;
28957                let z = abc + 2;
28958            }
28959        "},
28960        indoc! {"
28961            fn foo() {
28962                let abc = 123;
28963                let x = abc + 1;
28964                let y = abc + 2;
28965                let z = ˇabc + 2;
28966            }
28967        "},
28968    ];
28969
28970    init_test(cx, |_| {});
28971
28972    let mut cx = EditorLspTestContext::new_rust(
28973        lsp::ServerCapabilities {
28974            references_provider: Some(lsp::OneOf::Left(true)),
28975            ..Default::default()
28976        },
28977        cx,
28978    )
28979    .await;
28980
28981    // importantly, the cursor is in the middle
28982    cx.set_state(indoc! {"
28983        fn foo() {
28984            let aˇbc = 123;
28985            let x = abc + 1;
28986            let y = abc + 2;
28987            let z = abc + 2;
28988        }
28989    "});
28990
28991    let reference_ranges = [
28992        lsp::Position::new(1, 8),
28993        lsp::Position::new(2, 12),
28994        lsp::Position::new(3, 12),
28995        lsp::Position::new(4, 12),
28996    ]
28997    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28998
28999    cx.lsp
29000        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29001            Ok(Some(
29002                reference_ranges
29003                    .map(|range| lsp::Location {
29004                        uri: params.text_document_position.text_document.uri.clone(),
29005                        range,
29006                    })
29007                    .to_vec(),
29008            ))
29009        });
29010
29011    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29012        cx.update_editor(|editor, window, cx| {
29013            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29014        })
29015        .unwrap()
29016        .await
29017        .unwrap()
29018    };
29019
29020    _move(Direction::Next, 1, &mut cx).await;
29021    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29022
29023    _move(Direction::Next, 1, &mut cx).await;
29024    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29025
29026    _move(Direction::Next, 1, &mut cx).await;
29027    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29028
29029    // loops back to the start
29030    _move(Direction::Next, 1, &mut cx).await;
29031    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29032
29033    // loops back to the end
29034    _move(Direction::Prev, 1, &mut cx).await;
29035    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29036
29037    _move(Direction::Prev, 1, &mut cx).await;
29038    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29039
29040    _move(Direction::Prev, 1, &mut cx).await;
29041    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29042
29043    _move(Direction::Prev, 1, &mut cx).await;
29044    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29045
29046    _move(Direction::Next, 3, &mut cx).await;
29047    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29048
29049    _move(Direction::Prev, 2, &mut cx).await;
29050    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29051}
29052
29053#[gpui::test]
29054async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29055    init_test(cx, |_| {});
29056
29057    let (editor, cx) = cx.add_window_view(|window, cx| {
29058        let multi_buffer = MultiBuffer::build_multi(
29059            [
29060                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29061                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29062            ],
29063            cx,
29064        );
29065        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29066    });
29067
29068    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29069    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29070
29071    cx.assert_excerpts_with_selections(indoc! {"
29072        [EXCERPT]
29073        ˇ1
29074        2
29075        3
29076        [EXCERPT]
29077        1
29078        2
29079        3
29080        "});
29081
29082    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29083    cx.update_editor(|editor, window, cx| {
29084        editor.change_selections(None.into(), window, cx, |s| {
29085            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29086        });
29087    });
29088    cx.assert_excerpts_with_selections(indoc! {"
29089        [EXCERPT]
29090        1
2909129092        3
29093        [EXCERPT]
29094        1
29095        2
29096        3
29097        "});
29098
29099    cx.update_editor(|editor, window, cx| {
29100        editor
29101            .select_all_matches(&SelectAllMatches, window, cx)
29102            .unwrap();
29103    });
29104    cx.assert_excerpts_with_selections(indoc! {"
29105        [EXCERPT]
29106        1
2910729108        3
29109        [EXCERPT]
29110        1
2911129112        3
29113        "});
29114
29115    cx.update_editor(|editor, window, cx| {
29116        editor.handle_input("X", window, cx);
29117    });
29118    cx.assert_excerpts_with_selections(indoc! {"
29119        [EXCERPT]
29120        1
2912129122        3
29123        [EXCERPT]
29124        1
2912529126        3
29127        "});
29128
29129    // Scenario 2: Select "2", then fold second buffer before insertion
29130    cx.update_multibuffer(|mb, cx| {
29131        for buffer_id in buffer_ids.iter() {
29132            let buffer = mb.buffer(*buffer_id).unwrap();
29133            buffer.update(cx, |buffer, cx| {
29134                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29135            });
29136        }
29137    });
29138
29139    // Select "2" and select all matches
29140    cx.update_editor(|editor, window, cx| {
29141        editor.change_selections(None.into(), window, cx, |s| {
29142            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29143        });
29144        editor
29145            .select_all_matches(&SelectAllMatches, window, cx)
29146            .unwrap();
29147    });
29148
29149    // Fold second buffer - should remove selections from folded buffer
29150    cx.update_editor(|editor, _, cx| {
29151        editor.fold_buffer(buffer_ids[1], cx);
29152    });
29153    cx.assert_excerpts_with_selections(indoc! {"
29154        [EXCERPT]
29155        1
2915629157        3
29158        [EXCERPT]
29159        [FOLDED]
29160        "});
29161
29162    // Insert text - should only affect first buffer
29163    cx.update_editor(|editor, window, cx| {
29164        editor.handle_input("Y", window, cx);
29165    });
29166    cx.update_editor(|editor, _, cx| {
29167        editor.unfold_buffer(buffer_ids[1], cx);
29168    });
29169    cx.assert_excerpts_with_selections(indoc! {"
29170        [EXCERPT]
29171        1
2917229173        3
29174        [EXCERPT]
29175        1
29176        2
29177        3
29178        "});
29179
29180    // Scenario 3: Select "2", then fold first buffer before insertion
29181    cx.update_multibuffer(|mb, cx| {
29182        for buffer_id in buffer_ids.iter() {
29183            let buffer = mb.buffer(*buffer_id).unwrap();
29184            buffer.update(cx, |buffer, cx| {
29185                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29186            });
29187        }
29188    });
29189
29190    // Select "2" and select all matches
29191    cx.update_editor(|editor, window, cx| {
29192        editor.change_selections(None.into(), window, cx, |s| {
29193            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29194        });
29195        editor
29196            .select_all_matches(&SelectAllMatches, window, cx)
29197            .unwrap();
29198    });
29199
29200    // Fold first buffer - should remove selections from folded buffer
29201    cx.update_editor(|editor, _, cx| {
29202        editor.fold_buffer(buffer_ids[0], cx);
29203    });
29204    cx.assert_excerpts_with_selections(indoc! {"
29205        [EXCERPT]
29206        [FOLDED]
29207        [EXCERPT]
29208        1
2920929210        3
29211        "});
29212
29213    // Insert text - should only affect second buffer
29214    cx.update_editor(|editor, window, cx| {
29215        editor.handle_input("Z", window, cx);
29216    });
29217    cx.update_editor(|editor, _, cx| {
29218        editor.unfold_buffer(buffer_ids[0], cx);
29219    });
29220    cx.assert_excerpts_with_selections(indoc! {"
29221        [EXCERPT]
29222        1
29223        2
29224        3
29225        [EXCERPT]
29226        1
2922729228        3
29229        "});
29230
29231    // Test correct folded header is selected upon fold
29232    cx.update_editor(|editor, _, cx| {
29233        editor.fold_buffer(buffer_ids[0], cx);
29234        editor.fold_buffer(buffer_ids[1], cx);
29235    });
29236    cx.assert_excerpts_with_selections(indoc! {"
29237        [EXCERPT]
29238        [FOLDED]
29239        [EXCERPT]
29240        ˇ[FOLDED]
29241        "});
29242
29243    // Test selection inside folded buffer unfolds it on type
29244    cx.update_editor(|editor, window, cx| {
29245        editor.handle_input("W", window, cx);
29246    });
29247    cx.update_editor(|editor, _, cx| {
29248        editor.unfold_buffer(buffer_ids[0], cx);
29249    });
29250    cx.assert_excerpts_with_selections(indoc! {"
29251        [EXCERPT]
29252        1
29253        2
29254        3
29255        [EXCERPT]
29256        Wˇ1
29257        Z
29258        3
29259        "});
29260}
29261
29262#[gpui::test]
29263async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29264    init_test(cx, |_| {});
29265
29266    let (editor, cx) = cx.add_window_view(|window, cx| {
29267        let multi_buffer = MultiBuffer::build_multi(
29268            [
29269                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29270                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29271            ],
29272            cx,
29273        );
29274        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29275    });
29276
29277    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29278
29279    cx.assert_excerpts_with_selections(indoc! {"
29280        [EXCERPT]
29281        ˇ1
29282        2
29283        3
29284        [EXCERPT]
29285        1
29286        2
29287        3
29288        4
29289        5
29290        6
29291        7
29292        8
29293        9
29294        "});
29295
29296    cx.update_editor(|editor, window, cx| {
29297        editor.change_selections(None.into(), window, cx, |s| {
29298            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29299        });
29300    });
29301
29302    cx.assert_excerpts_with_selections(indoc! {"
29303        [EXCERPT]
29304        1
29305        2
29306        3
29307        [EXCERPT]
29308        1
29309        2
29310        3
29311        4
29312        5
29313        6
29314        ˇ7
29315        8
29316        9
29317        "});
29318
29319    cx.update_editor(|editor, _window, cx| {
29320        editor.set_vertical_scroll_margin(0, cx);
29321    });
29322
29323    cx.update_editor(|editor, window, cx| {
29324        assert_eq!(editor.vertical_scroll_margin(), 0);
29325        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29326        assert_eq!(
29327            editor.snapshot(window, cx).scroll_position(),
29328            gpui::Point::new(0., 12.0)
29329        );
29330    });
29331
29332    cx.update_editor(|editor, _window, cx| {
29333        editor.set_vertical_scroll_margin(3, cx);
29334    });
29335
29336    cx.update_editor(|editor, window, cx| {
29337        assert_eq!(editor.vertical_scroll_margin(), 3);
29338        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29339        assert_eq!(
29340            editor.snapshot(window, cx).scroll_position(),
29341            gpui::Point::new(0., 9.0)
29342        );
29343    });
29344}
29345
29346#[gpui::test]
29347async fn test_find_references_single_case(cx: &mut TestAppContext) {
29348    init_test(cx, |_| {});
29349    let mut cx = EditorLspTestContext::new_rust(
29350        lsp::ServerCapabilities {
29351            references_provider: Some(lsp::OneOf::Left(true)),
29352            ..lsp::ServerCapabilities::default()
29353        },
29354        cx,
29355    )
29356    .await;
29357
29358    let before = indoc!(
29359        r#"
29360        fn main() {
29361            let aˇbc = 123;
29362            let xyz = abc;
29363        }
29364        "#
29365    );
29366    let after = indoc!(
29367        r#"
29368        fn main() {
29369            let abc = 123;
29370            let xyz = ˇabc;
29371        }
29372        "#
29373    );
29374
29375    cx.lsp
29376        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29377            Ok(Some(vec![
29378                lsp::Location {
29379                    uri: params.text_document_position.text_document.uri.clone(),
29380                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29381                },
29382                lsp::Location {
29383                    uri: params.text_document_position.text_document.uri,
29384                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29385                },
29386            ]))
29387        });
29388
29389    cx.set_state(before);
29390
29391    let action = FindAllReferences {
29392        always_open_multibuffer: false,
29393    };
29394
29395    let navigated = cx
29396        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29397        .expect("should have spawned a task")
29398        .await
29399        .unwrap();
29400
29401    assert_eq!(navigated, Navigated::No);
29402
29403    cx.run_until_parked();
29404
29405    cx.assert_editor_state(after);
29406}
29407
29408#[gpui::test]
29409async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29410    init_test(cx, |settings| {
29411        settings.defaults.tab_size = Some(2.try_into().unwrap());
29412    });
29413
29414    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29415    let mut cx = EditorTestContext::new(cx).await;
29416    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29417
29418    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29419    cx.set_state(indoc! {"
29420        - [ ] taskˇ
29421    "});
29422    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29423    cx.wait_for_autoindent_applied().await;
29424    cx.assert_editor_state(indoc! {"
29425        - [ ] task
29426        - [ ] ˇ
29427    "});
29428
29429    // Case 2: Works with checked task items too
29430    cx.set_state(indoc! {"
29431        - [x] completed taskˇ
29432    "});
29433    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29434    cx.wait_for_autoindent_applied().await;
29435    cx.assert_editor_state(indoc! {"
29436        - [x] completed task
29437        - [ ] ˇ
29438    "});
29439
29440    // Case 2.1: Works with uppercase checked marker too
29441    cx.set_state(indoc! {"
29442        - [X] completed taskˇ
29443    "});
29444    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29445    cx.wait_for_autoindent_applied().await;
29446    cx.assert_editor_state(indoc! {"
29447        - [X] completed task
29448        - [ ] ˇ
29449    "});
29450
29451    // Case 3: Cursor position doesn't matter - content after marker is what counts
29452    cx.set_state(indoc! {"
29453        - [ ] taˇsk
29454    "});
29455    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29456    cx.wait_for_autoindent_applied().await;
29457    cx.assert_editor_state(indoc! {"
29458        - [ ] ta
29459        - [ ] ˇsk
29460    "});
29461
29462    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29463    cx.set_state(indoc! {"
29464        - [ ]  ˇ
29465    "});
29466    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29467    cx.wait_for_autoindent_applied().await;
29468    cx.assert_editor_state(
29469        indoc! {"
29470        - [ ]$$
29471        ˇ
29472    "}
29473        .replace("$", " ")
29474        .as_str(),
29475    );
29476
29477    // Case 5: Adding newline with content adds marker preserving indentation
29478    cx.set_state(indoc! {"
29479        - [ ] task
29480          - [ ] indentedˇ
29481    "});
29482    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29483    cx.wait_for_autoindent_applied().await;
29484    cx.assert_editor_state(indoc! {"
29485        - [ ] task
29486          - [ ] indented
29487          - [ ] ˇ
29488    "});
29489
29490    // Case 6: Adding newline with cursor right after prefix, unindents
29491    cx.set_state(indoc! {"
29492        - [ ] task
29493          - [ ] sub task
29494            - [ ] ˇ
29495    "});
29496    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29497    cx.wait_for_autoindent_applied().await;
29498    cx.assert_editor_state(indoc! {"
29499        - [ ] task
29500          - [ ] sub task
29501          - [ ] ˇ
29502    "});
29503    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29504    cx.wait_for_autoindent_applied().await;
29505
29506    // Case 7: Adding newline with cursor right after prefix, removes marker
29507    cx.assert_editor_state(indoc! {"
29508        - [ ] task
29509          - [ ] sub task
29510        - [ ] ˇ
29511    "});
29512    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29513    cx.wait_for_autoindent_applied().await;
29514    cx.assert_editor_state(indoc! {"
29515        - [ ] task
29516          - [ ] sub task
29517        ˇ
29518    "});
29519
29520    // Case 8: Cursor before or inside prefix does not add marker
29521    cx.set_state(indoc! {"
29522        ˇ- [ ] task
29523    "});
29524    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29525    cx.wait_for_autoindent_applied().await;
29526    cx.assert_editor_state(indoc! {"
29527
29528        ˇ- [ ] task
29529    "});
29530
29531    cx.set_state(indoc! {"
29532        - [ˇ ] task
29533    "});
29534    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29535    cx.wait_for_autoindent_applied().await;
29536    cx.assert_editor_state(indoc! {"
29537        - [
29538        ˇ
29539        ] task
29540    "});
29541}
29542
29543#[gpui::test]
29544async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29545    init_test(cx, |settings| {
29546        settings.defaults.tab_size = Some(2.try_into().unwrap());
29547    });
29548
29549    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29550    let mut cx = EditorTestContext::new(cx).await;
29551    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29552
29553    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29554    cx.set_state(indoc! {"
29555        - itemˇ
29556    "});
29557    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29558    cx.wait_for_autoindent_applied().await;
29559    cx.assert_editor_state(indoc! {"
29560        - item
29561        - ˇ
29562    "});
29563
29564    // Case 2: Works with different markers
29565    cx.set_state(indoc! {"
29566        * starred itemˇ
29567    "});
29568    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29569    cx.wait_for_autoindent_applied().await;
29570    cx.assert_editor_state(indoc! {"
29571        * starred item
29572        * ˇ
29573    "});
29574
29575    cx.set_state(indoc! {"
29576        + plus itemˇ
29577    "});
29578    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29579    cx.wait_for_autoindent_applied().await;
29580    cx.assert_editor_state(indoc! {"
29581        + plus item
29582        + ˇ
29583    "});
29584
29585    // Case 3: Cursor position doesn't matter - content after marker is what counts
29586    cx.set_state(indoc! {"
29587        - itˇem
29588    "});
29589    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29590    cx.wait_for_autoindent_applied().await;
29591    cx.assert_editor_state(indoc! {"
29592        - it
29593        - ˇem
29594    "});
29595
29596    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29597    cx.set_state(indoc! {"
29598        -  ˇ
29599    "});
29600    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29601    cx.wait_for_autoindent_applied().await;
29602    cx.assert_editor_state(
29603        indoc! {"
29604        - $
29605        ˇ
29606    "}
29607        .replace("$", " ")
29608        .as_str(),
29609    );
29610
29611    // Case 5: Adding newline with content adds marker preserving indentation
29612    cx.set_state(indoc! {"
29613        - item
29614          - indentedˇ
29615    "});
29616    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29617    cx.wait_for_autoindent_applied().await;
29618    cx.assert_editor_state(indoc! {"
29619        - item
29620          - indented
29621          - ˇ
29622    "});
29623
29624    // Case 6: Adding newline with cursor right after marker, unindents
29625    cx.set_state(indoc! {"
29626        - item
29627          - sub item
29628            - ˇ
29629    "});
29630    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29631    cx.wait_for_autoindent_applied().await;
29632    cx.assert_editor_state(indoc! {"
29633        - item
29634          - sub item
29635          - ˇ
29636    "});
29637    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29638    cx.wait_for_autoindent_applied().await;
29639
29640    // Case 7: Adding newline with cursor right after marker, removes marker
29641    cx.assert_editor_state(indoc! {"
29642        - item
29643          - sub item
29644        - ˇ
29645    "});
29646    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29647    cx.wait_for_autoindent_applied().await;
29648    cx.assert_editor_state(indoc! {"
29649        - item
29650          - sub item
29651        ˇ
29652    "});
29653
29654    // Case 8: Cursor before or inside prefix does not add marker
29655    cx.set_state(indoc! {"
29656        ˇ- item
29657    "});
29658    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29659    cx.wait_for_autoindent_applied().await;
29660    cx.assert_editor_state(indoc! {"
29661
29662        ˇ- item
29663    "});
29664
29665    cx.set_state(indoc! {"
29666        -ˇ item
29667    "});
29668    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29669    cx.wait_for_autoindent_applied().await;
29670    cx.assert_editor_state(indoc! {"
29671        -
29672        ˇitem
29673    "});
29674}
29675
29676#[gpui::test]
29677async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
29678    init_test(cx, |settings| {
29679        settings.defaults.tab_size = Some(2.try_into().unwrap());
29680    });
29681
29682    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29683    let mut cx = EditorTestContext::new(cx).await;
29684    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29685
29686    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29687    cx.set_state(indoc! {"
29688        1. first itemˇ
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        1. first item
29694        2. ˇ
29695    "});
29696
29697    // Case 2: Works with larger numbers
29698    cx.set_state(indoc! {"
29699        10. tenth itemˇ
29700    "});
29701    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29702    cx.wait_for_autoindent_applied().await;
29703    cx.assert_editor_state(indoc! {"
29704        10. tenth item
29705        11. ˇ
29706    "});
29707
29708    // Case 3: Cursor position doesn't matter - content after marker is what counts
29709    cx.set_state(indoc! {"
29710        1. itˇem
29711    "});
29712    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29713    cx.wait_for_autoindent_applied().await;
29714    cx.assert_editor_state(indoc! {"
29715        1. it
29716        2. ˇem
29717    "});
29718
29719    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29720    cx.set_state(indoc! {"
29721        1.  ˇ
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(
29726        indoc! {"
29727        1. $
29728        ˇ
29729    "}
29730        .replace("$", " ")
29731        .as_str(),
29732    );
29733
29734    // Case 5: Adding newline with content adds marker preserving indentation
29735    cx.set_state(indoc! {"
29736        1. item
29737          2. indentedˇ
29738    "});
29739    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29740    cx.wait_for_autoindent_applied().await;
29741    cx.assert_editor_state(indoc! {"
29742        1. item
29743          2. indented
29744          3. ˇ
29745    "});
29746
29747    // Case 6: Adding newline with cursor right after marker, unindents
29748    cx.set_state(indoc! {"
29749        1. item
29750          2. sub item
29751            3. ˇ
29752    "});
29753    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29754    cx.wait_for_autoindent_applied().await;
29755    cx.assert_editor_state(indoc! {"
29756        1. item
29757          2. sub item
29758          1. ˇ
29759    "});
29760    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29761    cx.wait_for_autoindent_applied().await;
29762
29763    // Case 7: Adding newline with cursor right after marker, removes marker
29764    cx.assert_editor_state(indoc! {"
29765        1. item
29766          2. sub item
29767        1. ˇ
29768    "});
29769    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29770    cx.wait_for_autoindent_applied().await;
29771    cx.assert_editor_state(indoc! {"
29772        1. item
29773          2. sub item
29774        ˇ
29775    "});
29776
29777    // Case 8: Cursor before or inside prefix does not add marker
29778    cx.set_state(indoc! {"
29779        ˇ1. item
29780    "});
29781    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29782    cx.wait_for_autoindent_applied().await;
29783    cx.assert_editor_state(indoc! {"
29784
29785        ˇ1. item
29786    "});
29787
29788    cx.set_state(indoc! {"
29789        1ˇ. item
29790    "});
29791    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29792    cx.wait_for_autoindent_applied().await;
29793    cx.assert_editor_state(indoc! {"
29794        1
29795        ˇ. item
29796    "});
29797}
29798
29799#[gpui::test]
29800async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
29801    init_test(cx, |settings| {
29802        settings.defaults.tab_size = Some(2.try_into().unwrap());
29803    });
29804
29805    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29806    let mut cx = EditorTestContext::new(cx).await;
29807    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29808
29809    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29810    cx.set_state(indoc! {"
29811        1. first item
29812          1. sub first item
29813          2. sub second item
29814          3. ˇ
29815    "});
29816    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29817    cx.wait_for_autoindent_applied().await;
29818    cx.assert_editor_state(indoc! {"
29819        1. first item
29820          1. sub first item
29821          2. sub second item
29822        1. ˇ
29823    "});
29824}
29825
29826#[gpui::test]
29827async fn test_tab_list_indent(cx: &mut TestAppContext) {
29828    init_test(cx, |settings| {
29829        settings.defaults.tab_size = Some(2.try_into().unwrap());
29830    });
29831
29832    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29833    let mut cx = EditorTestContext::new(cx).await;
29834    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29835
29836    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
29837    cx.set_state(indoc! {"
29838        - ˇitem
29839    "});
29840    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29841    cx.wait_for_autoindent_applied().await;
29842    let expected = indoc! {"
29843        $$- ˇitem
29844    "};
29845    cx.assert_editor_state(expected.replace("$", " ").as_str());
29846
29847    // Case 2: Task list - cursor after prefix
29848    cx.set_state(indoc! {"
29849        - [ ] ˇtask
29850    "});
29851    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29852    cx.wait_for_autoindent_applied().await;
29853    let expected = indoc! {"
29854        $$- [ ] ˇtask
29855    "};
29856    cx.assert_editor_state(expected.replace("$", " ").as_str());
29857
29858    // Case 3: Ordered list - cursor after prefix
29859    cx.set_state(indoc! {"
29860        1. ˇfirst
29861    "});
29862    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29863    cx.wait_for_autoindent_applied().await;
29864    let expected = indoc! {"
29865        $$1. ˇfirst
29866    "};
29867    cx.assert_editor_state(expected.replace("$", " ").as_str());
29868
29869    // Case 4: With existing indentation - adds more indent
29870    let initial = indoc! {"
29871        $$- ˇitem
29872    "};
29873    cx.set_state(initial.replace("$", " ").as_str());
29874    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29875    cx.wait_for_autoindent_applied().await;
29876    let expected = indoc! {"
29877        $$$$- ˇitem
29878    "};
29879    cx.assert_editor_state(expected.replace("$", " ").as_str());
29880
29881    // Case 5: Empty list item
29882    cx.set_state(indoc! {"
29883        - ˇ
29884    "});
29885    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29886    cx.wait_for_autoindent_applied().await;
29887    let expected = indoc! {"
29888        $$- ˇ
29889    "};
29890    cx.assert_editor_state(expected.replace("$", " ").as_str());
29891
29892    // Case 6: Cursor at end of line with content
29893    cx.set_state(indoc! {"
29894        - itemˇ
29895    "});
29896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29897    cx.wait_for_autoindent_applied().await;
29898    let expected = indoc! {"
29899        $$- itemˇ
29900    "};
29901    cx.assert_editor_state(expected.replace("$", " ").as_str());
29902
29903    // Case 7: Cursor at start of list item, indents it
29904    cx.set_state(indoc! {"
29905        - item
29906        ˇ  - sub item
29907    "});
29908    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29909    cx.wait_for_autoindent_applied().await;
29910    let expected = indoc! {"
29911        - item
29912          ˇ  - sub item
29913    "};
29914    cx.assert_editor_state(expected);
29915
29916    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
29917    cx.update_editor(|_, _, cx| {
29918        SettingsStore::update_global(cx, |store, cx| {
29919            store.update_user_settings(cx, |settings| {
29920                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
29921            });
29922        });
29923    });
29924    cx.set_state(indoc! {"
29925        - item
29926        ˇ  - sub item
29927    "});
29928    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
29929    cx.wait_for_autoindent_applied().await;
29930    let expected = indoc! {"
29931        - item
29932          ˇ- sub item
29933    "};
29934    cx.assert_editor_state(expected);
29935}
29936
29937#[gpui::test]
29938async fn test_local_worktree_trust(cx: &mut TestAppContext) {
29939    init_test(cx, |_| {});
29940    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
29941
29942    cx.update(|cx| {
29943        SettingsStore::update_global(cx, |store, cx| {
29944            store.update_user_settings(cx, |settings| {
29945                settings.project.all_languages.defaults.inlay_hints =
29946                    Some(InlayHintSettingsContent {
29947                        enabled: Some(true),
29948                        ..InlayHintSettingsContent::default()
29949                    });
29950            });
29951        });
29952    });
29953
29954    let fs = FakeFs::new(cx.executor());
29955    fs.insert_tree(
29956        path!("/project"),
29957        json!({
29958            ".zed": {
29959                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
29960            },
29961            "main.rs": "fn main() {}"
29962        }),
29963    )
29964    .await;
29965
29966    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
29967    let server_name = "override-rust-analyzer";
29968    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
29969
29970    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29971    language_registry.add(rust_lang());
29972
29973    let capabilities = lsp::ServerCapabilities {
29974        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29975        ..lsp::ServerCapabilities::default()
29976    };
29977    let mut fake_language_servers = language_registry.register_fake_lsp(
29978        "Rust",
29979        FakeLspAdapter {
29980            name: server_name,
29981            capabilities,
29982            initializer: Some(Box::new({
29983                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29984                move |fake_server| {
29985                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29986                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29987                        move |_params, _| {
29988                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
29989                            async move {
29990                                Ok(Some(vec![lsp::InlayHint {
29991                                    position: lsp::Position::new(0, 0),
29992                                    label: lsp::InlayHintLabel::String("hint".to_string()),
29993                                    kind: None,
29994                                    text_edits: None,
29995                                    tooltip: None,
29996                                    padding_left: None,
29997                                    padding_right: None,
29998                                    data: None,
29999                                }]))
30000                            }
30001                        },
30002                    );
30003                }
30004            })),
30005            ..FakeLspAdapter::default()
30006        },
30007    );
30008
30009    cx.run_until_parked();
30010
30011    let worktree_id = project.read_with(cx, |project, cx| {
30012        project
30013            .worktrees(cx)
30014            .next()
30015            .map(|wt| wt.read(cx).id())
30016            .expect("should have a worktree")
30017    });
30018    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30019
30020    let trusted_worktrees =
30021        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30022
30023    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30024        store.can_trust(&worktree_store, worktree_id, cx)
30025    });
30026    assert!(!can_trust, "worktree should be restricted initially");
30027
30028    let buffer_before_approval = project
30029        .update(cx, |project, cx| {
30030            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30031        })
30032        .await
30033        .unwrap();
30034
30035    let (editor, cx) = cx.add_window_view(|window, cx| {
30036        Editor::new(
30037            EditorMode::full(),
30038            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30039            Some(project.clone()),
30040            window,
30041            cx,
30042        )
30043    });
30044    cx.run_until_parked();
30045    let fake_language_server = fake_language_servers.next();
30046
30047    cx.read(|cx| {
30048        let file = buffer_before_approval.read(cx).file();
30049        assert_eq!(
30050            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30051                .language_servers,
30052            ["...".to_string()],
30053            "local .zed/settings.json must not apply before trust approval"
30054        )
30055    });
30056
30057    editor.update_in(cx, |editor, window, cx| {
30058        editor.handle_input("1", window, cx);
30059    });
30060    cx.run_until_parked();
30061    cx.executor()
30062        .advance_clock(std::time::Duration::from_secs(1));
30063    assert_eq!(
30064        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30065        0,
30066        "inlay hints must not be queried before trust approval"
30067    );
30068
30069    trusted_worktrees.update(cx, |store, cx| {
30070        store.trust(
30071            &worktree_store,
30072            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30073            cx,
30074        );
30075    });
30076    cx.run_until_parked();
30077
30078    cx.read(|cx| {
30079        let file = buffer_before_approval.read(cx).file();
30080        assert_eq!(
30081            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30082                .language_servers,
30083            ["override-rust-analyzer".to_string()],
30084            "local .zed/settings.json should apply after trust approval"
30085        )
30086    });
30087    let _fake_language_server = fake_language_server.await.unwrap();
30088    editor.update_in(cx, |editor, window, cx| {
30089        editor.handle_input("1", window, cx);
30090    });
30091    cx.run_until_parked();
30092    cx.executor()
30093        .advance_clock(std::time::Duration::from_secs(1));
30094    assert!(
30095        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30096        "inlay hints should be queried after trust approval"
30097    );
30098
30099    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30100        store.can_trust(&worktree_store, worktree_id, cx)
30101    });
30102    assert!(can_trust_after, "worktree should be trusted after trust()");
30103}
30104
30105#[gpui::test]
30106fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30107    // This test reproduces a bug where drawing an editor at a position above the viewport
30108    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30109    // causes an infinite loop in blocks_in_range.
30110    //
30111    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30112    // the content mask intersection produces visible_bounds with origin at the viewport top.
30113    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30114    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30115    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30116    init_test(cx, |_| {});
30117
30118    let window = cx.add_window(|_, _| gpui::Empty);
30119    let mut cx = VisualTestContext::from_window(*window, cx);
30120
30121    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30122    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30123
30124    // Simulate a small viewport (500x500 pixels at origin 0,0)
30125    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30126
30127    // Draw the editor at a very negative Y position, simulating an editor that's been
30128    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30129    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30130    // This should NOT hang - it should just render nothing.
30131    cx.draw(
30132        gpui::point(px(0.), px(-10000.)),
30133        gpui::size(px(500.), px(3000.)),
30134        |_, _| editor.clone(),
30135    );
30136
30137    // If we get here without hanging, the test passes
30138}