editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    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            let buffer_changes = buffer_changes.clone();
13224            move |_, _| {
13225                let buffer_changes = buffer_changes.clone();
13226                // Insert blank lines between each line of the buffer.
13227                async move {
13228                    // When formatting is requested, trailing whitespace has already been stripped,
13229                    // and the trailing newline has already been added.
13230                    assert_eq!(
13231                        &buffer_changes.lock()[1..],
13232                        &[
13233                            (
13234                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13235                                "".into()
13236                            ),
13237                            (
13238                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13239                                "".into()
13240                            ),
13241                            (
13242                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13243                                "\n".into()
13244                            ),
13245                        ]
13246                    );
13247
13248                    Ok(Some(vec![
13249                        lsp::TextEdit {
13250                            range: lsp::Range::new(
13251                                lsp::Position::new(1, 0),
13252                                lsp::Position::new(1, 0),
13253                            ),
13254                            new_text: "\n".into(),
13255                        },
13256                        lsp::TextEdit {
13257                            range: lsp::Range::new(
13258                                lsp::Position::new(2, 0),
13259                                lsp::Position::new(2, 0),
13260                            ),
13261                            new_text: "\n".into(),
13262                        },
13263                    ]))
13264                }
13265            }
13266        });
13267
13268    // Set up a buffer white some trailing whitespace and no trailing newline.
13269    cx.set_state(
13270        &[
13271            "one ",   //
13272            "twoˇ",   //
13273            "three ", //
13274            "four",   //
13275        ]
13276        .join("\n"),
13277    );
13278    cx.run_until_parked();
13279
13280    // Submit a format request.
13281    let format = cx
13282        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13283        .unwrap();
13284
13285    cx.run_until_parked();
13286    // After formatting the buffer, the trailing whitespace is stripped,
13287    // a newline is appended, and the edits provided by the language server
13288    // have been applied.
13289    format.await.unwrap();
13290
13291    cx.assert_editor_state(
13292        &[
13293            "one",   //
13294            "",      //
13295            "twoˇ",  //
13296            "",      //
13297            "three", //
13298            "four",  //
13299            "",      //
13300        ]
13301        .join("\n"),
13302    );
13303
13304    // Undoing the formatting undoes the trailing whitespace removal, the
13305    // trailing newline, and the LSP edits.
13306    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13307    cx.assert_editor_state(
13308        &[
13309            "one ",   //
13310            "twoˇ",   //
13311            "three ", //
13312            "four",   //
13313        ]
13314        .join("\n"),
13315    );
13316}
13317
13318#[gpui::test]
13319async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13320    cx: &mut TestAppContext,
13321) {
13322    init_test(cx, |_| {});
13323
13324    cx.update(|cx| {
13325        cx.update_global::<SettingsStore, _>(|settings, cx| {
13326            settings.update_user_settings(cx, |settings| {
13327                settings.editor.auto_signature_help = Some(true);
13328            });
13329        });
13330    });
13331
13332    let mut cx = EditorLspTestContext::new_rust(
13333        lsp::ServerCapabilities {
13334            signature_help_provider: Some(lsp::SignatureHelpOptions {
13335                ..Default::default()
13336            }),
13337            ..Default::default()
13338        },
13339        cx,
13340    )
13341    .await;
13342
13343    let language = Language::new(
13344        LanguageConfig {
13345            name: "Rust".into(),
13346            brackets: BracketPairConfig {
13347                pairs: vec![
13348                    BracketPair {
13349                        start: "{".to_string(),
13350                        end: "}".to_string(),
13351                        close: true,
13352                        surround: true,
13353                        newline: true,
13354                    },
13355                    BracketPair {
13356                        start: "(".to_string(),
13357                        end: ")".to_string(),
13358                        close: true,
13359                        surround: true,
13360                        newline: true,
13361                    },
13362                    BracketPair {
13363                        start: "/*".to_string(),
13364                        end: " */".to_string(),
13365                        close: true,
13366                        surround: true,
13367                        newline: true,
13368                    },
13369                    BracketPair {
13370                        start: "[".to_string(),
13371                        end: "]".to_string(),
13372                        close: false,
13373                        surround: false,
13374                        newline: true,
13375                    },
13376                    BracketPair {
13377                        start: "\"".to_string(),
13378                        end: "\"".to_string(),
13379                        close: true,
13380                        surround: true,
13381                        newline: false,
13382                    },
13383                    BracketPair {
13384                        start: "<".to_string(),
13385                        end: ">".to_string(),
13386                        close: false,
13387                        surround: true,
13388                        newline: true,
13389                    },
13390                ],
13391                ..Default::default()
13392            },
13393            autoclose_before: "})]".to_string(),
13394            ..Default::default()
13395        },
13396        Some(tree_sitter_rust::LANGUAGE.into()),
13397    );
13398    let language = Arc::new(language);
13399
13400    cx.language_registry().add(language.clone());
13401    cx.update_buffer(|buffer, cx| {
13402        buffer.set_language(Some(language), cx);
13403    });
13404
13405    cx.set_state(
13406        &r#"
13407            fn main() {
13408                sampleˇ
13409            }
13410        "#
13411        .unindent(),
13412    );
13413
13414    cx.update_editor(|editor, window, cx| {
13415        editor.handle_input("(", window, cx);
13416    });
13417    cx.assert_editor_state(
13418        &"
13419            fn main() {
13420                sample(ˇ)
13421            }
13422        "
13423        .unindent(),
13424    );
13425
13426    let mocked_response = lsp::SignatureHelp {
13427        signatures: vec![lsp::SignatureInformation {
13428            label: "fn sample(param1: u8, param2: u8)".to_string(),
13429            documentation: None,
13430            parameters: Some(vec![
13431                lsp::ParameterInformation {
13432                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13433                    documentation: None,
13434                },
13435                lsp::ParameterInformation {
13436                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13437                    documentation: None,
13438                },
13439            ]),
13440            active_parameter: None,
13441        }],
13442        active_signature: Some(0),
13443        active_parameter: Some(0),
13444    };
13445    handle_signature_help_request(&mut cx, mocked_response).await;
13446
13447    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13448        .await;
13449
13450    cx.editor(|editor, _, _| {
13451        let signature_help_state = editor.signature_help_state.popover().cloned();
13452        let signature = signature_help_state.unwrap();
13453        assert_eq!(
13454            signature.signatures[signature.current_signature].label,
13455            "fn sample(param1: u8, param2: u8)"
13456        );
13457    });
13458}
13459
13460#[gpui::test]
13461async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13462    init_test(cx, |_| {});
13463
13464    cx.update(|cx| {
13465        cx.update_global::<SettingsStore, _>(|settings, cx| {
13466            settings.update_user_settings(cx, |settings| {
13467                settings.editor.auto_signature_help = Some(false);
13468                settings.editor.show_signature_help_after_edits = Some(false);
13469            });
13470        });
13471    });
13472
13473    let mut cx = EditorLspTestContext::new_rust(
13474        lsp::ServerCapabilities {
13475            signature_help_provider: Some(lsp::SignatureHelpOptions {
13476                ..Default::default()
13477            }),
13478            ..Default::default()
13479        },
13480        cx,
13481    )
13482    .await;
13483
13484    let language = Language::new(
13485        LanguageConfig {
13486            name: "Rust".into(),
13487            brackets: BracketPairConfig {
13488                pairs: vec![
13489                    BracketPair {
13490                        start: "{".to_string(),
13491                        end: "}".to_string(),
13492                        close: true,
13493                        surround: true,
13494                        newline: true,
13495                    },
13496                    BracketPair {
13497                        start: "(".to_string(),
13498                        end: ")".to_string(),
13499                        close: true,
13500                        surround: true,
13501                        newline: true,
13502                    },
13503                    BracketPair {
13504                        start: "/*".to_string(),
13505                        end: " */".to_string(),
13506                        close: true,
13507                        surround: true,
13508                        newline: true,
13509                    },
13510                    BracketPair {
13511                        start: "[".to_string(),
13512                        end: "]".to_string(),
13513                        close: false,
13514                        surround: false,
13515                        newline: true,
13516                    },
13517                    BracketPair {
13518                        start: "\"".to_string(),
13519                        end: "\"".to_string(),
13520                        close: true,
13521                        surround: true,
13522                        newline: false,
13523                    },
13524                    BracketPair {
13525                        start: "<".to_string(),
13526                        end: ">".to_string(),
13527                        close: false,
13528                        surround: true,
13529                        newline: true,
13530                    },
13531                ],
13532                ..Default::default()
13533            },
13534            autoclose_before: "})]".to_string(),
13535            ..Default::default()
13536        },
13537        Some(tree_sitter_rust::LANGUAGE.into()),
13538    );
13539    let language = Arc::new(language);
13540
13541    cx.language_registry().add(language.clone());
13542    cx.update_buffer(|buffer, cx| {
13543        buffer.set_language(Some(language), cx);
13544    });
13545
13546    // Ensure that signature_help is not called when no signature help is enabled.
13547    cx.set_state(
13548        &r#"
13549            fn main() {
13550                sampleˇ
13551            }
13552        "#
13553        .unindent(),
13554    );
13555    cx.update_editor(|editor, window, cx| {
13556        editor.handle_input("(", window, cx);
13557    });
13558    cx.assert_editor_state(
13559        &"
13560            fn main() {
13561                sample(ˇ)
13562            }
13563        "
13564        .unindent(),
13565    );
13566    cx.editor(|editor, _, _| {
13567        assert!(editor.signature_help_state.task().is_none());
13568    });
13569
13570    let mocked_response = lsp::SignatureHelp {
13571        signatures: vec![lsp::SignatureInformation {
13572            label: "fn sample(param1: u8, param2: u8)".to_string(),
13573            documentation: None,
13574            parameters: Some(vec![
13575                lsp::ParameterInformation {
13576                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13577                    documentation: None,
13578                },
13579                lsp::ParameterInformation {
13580                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13581                    documentation: None,
13582                },
13583            ]),
13584            active_parameter: None,
13585        }],
13586        active_signature: Some(0),
13587        active_parameter: Some(0),
13588    };
13589
13590    // Ensure that signature_help is called when enabled afte edits
13591    cx.update(|_, cx| {
13592        cx.update_global::<SettingsStore, _>(|settings, cx| {
13593            settings.update_user_settings(cx, |settings| {
13594                settings.editor.auto_signature_help = Some(false);
13595                settings.editor.show_signature_help_after_edits = Some(true);
13596            });
13597        });
13598    });
13599    cx.set_state(
13600        &r#"
13601            fn main() {
13602                sampleˇ
13603            }
13604        "#
13605        .unindent(),
13606    );
13607    cx.update_editor(|editor, window, cx| {
13608        editor.handle_input("(", window, cx);
13609    });
13610    cx.assert_editor_state(
13611        &"
13612            fn main() {
13613                sample(ˇ)
13614            }
13615        "
13616        .unindent(),
13617    );
13618    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13619    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13620        .await;
13621    cx.update_editor(|editor, _, _| {
13622        let signature_help_state = editor.signature_help_state.popover().cloned();
13623        assert!(signature_help_state.is_some());
13624        let signature = signature_help_state.unwrap();
13625        assert_eq!(
13626            signature.signatures[signature.current_signature].label,
13627            "fn sample(param1: u8, param2: u8)"
13628        );
13629        editor.signature_help_state = SignatureHelpState::default();
13630    });
13631
13632    // Ensure that signature_help is called when auto signature help override is enabled
13633    cx.update(|_, cx| {
13634        cx.update_global::<SettingsStore, _>(|settings, cx| {
13635            settings.update_user_settings(cx, |settings| {
13636                settings.editor.auto_signature_help = Some(true);
13637                settings.editor.show_signature_help_after_edits = Some(false);
13638            });
13639        });
13640    });
13641    cx.set_state(
13642        &r#"
13643            fn main() {
13644                sampleˇ
13645            }
13646        "#
13647        .unindent(),
13648    );
13649    cx.update_editor(|editor, window, cx| {
13650        editor.handle_input("(", window, cx);
13651    });
13652    cx.assert_editor_state(
13653        &"
13654            fn main() {
13655                sample(ˇ)
13656            }
13657        "
13658        .unindent(),
13659    );
13660    handle_signature_help_request(&mut cx, mocked_response).await;
13661    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13662        .await;
13663    cx.editor(|editor, _, _| {
13664        let signature_help_state = editor.signature_help_state.popover().cloned();
13665        assert!(signature_help_state.is_some());
13666        let signature = signature_help_state.unwrap();
13667        assert_eq!(
13668            signature.signatures[signature.current_signature].label,
13669            "fn sample(param1: u8, param2: u8)"
13670        );
13671    });
13672}
13673
13674#[gpui::test]
13675async fn test_signature_help(cx: &mut TestAppContext) {
13676    init_test(cx, |_| {});
13677    cx.update(|cx| {
13678        cx.update_global::<SettingsStore, _>(|settings, cx| {
13679            settings.update_user_settings(cx, |settings| {
13680                settings.editor.auto_signature_help = Some(true);
13681            });
13682        });
13683    });
13684
13685    let mut cx = EditorLspTestContext::new_rust(
13686        lsp::ServerCapabilities {
13687            signature_help_provider: Some(lsp::SignatureHelpOptions {
13688                ..Default::default()
13689            }),
13690            ..Default::default()
13691        },
13692        cx,
13693    )
13694    .await;
13695
13696    // A test that directly calls `show_signature_help`
13697    cx.update_editor(|editor, window, cx| {
13698        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13699    });
13700
13701    let mocked_response = lsp::SignatureHelp {
13702        signatures: vec![lsp::SignatureInformation {
13703            label: "fn sample(param1: u8, param2: u8)".to_string(),
13704            documentation: None,
13705            parameters: Some(vec![
13706                lsp::ParameterInformation {
13707                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13708                    documentation: None,
13709                },
13710                lsp::ParameterInformation {
13711                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13712                    documentation: None,
13713                },
13714            ]),
13715            active_parameter: None,
13716        }],
13717        active_signature: Some(0),
13718        active_parameter: Some(0),
13719    };
13720    handle_signature_help_request(&mut cx, mocked_response).await;
13721
13722    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13723        .await;
13724
13725    cx.editor(|editor, _, _| {
13726        let signature_help_state = editor.signature_help_state.popover().cloned();
13727        assert!(signature_help_state.is_some());
13728        let signature = signature_help_state.unwrap();
13729        assert_eq!(
13730            signature.signatures[signature.current_signature].label,
13731            "fn sample(param1: u8, param2: u8)"
13732        );
13733    });
13734
13735    // When exiting outside from inside the brackets, `signature_help` is closed.
13736    cx.set_state(indoc! {"
13737        fn main() {
13738            sample(ˇ);
13739        }
13740
13741        fn sample(param1: u8, param2: u8) {}
13742    "});
13743
13744    cx.update_editor(|editor, window, cx| {
13745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13746            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13747        });
13748    });
13749
13750    let mocked_response = lsp::SignatureHelp {
13751        signatures: Vec::new(),
13752        active_signature: None,
13753        active_parameter: None,
13754    };
13755    handle_signature_help_request(&mut cx, mocked_response).await;
13756
13757    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13758        .await;
13759
13760    cx.editor(|editor, _, _| {
13761        assert!(!editor.signature_help_state.is_shown());
13762    });
13763
13764    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13765    cx.set_state(indoc! {"
13766        fn main() {
13767            sample(ˇ);
13768        }
13769
13770        fn sample(param1: u8, param2: u8) {}
13771    "});
13772
13773    let mocked_response = lsp::SignatureHelp {
13774        signatures: vec![lsp::SignatureInformation {
13775            label: "fn sample(param1: u8, param2: u8)".to_string(),
13776            documentation: None,
13777            parameters: Some(vec![
13778                lsp::ParameterInformation {
13779                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13780                    documentation: None,
13781                },
13782                lsp::ParameterInformation {
13783                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13784                    documentation: None,
13785                },
13786            ]),
13787            active_parameter: None,
13788        }],
13789        active_signature: Some(0),
13790        active_parameter: Some(0),
13791    };
13792    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13793    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13794        .await;
13795    cx.editor(|editor, _, _| {
13796        assert!(editor.signature_help_state.is_shown());
13797    });
13798
13799    // Restore the popover with more parameter input
13800    cx.set_state(indoc! {"
13801        fn main() {
13802            sample(param1, param2ˇ);
13803        }
13804
13805        fn sample(param1: u8, param2: u8) {}
13806    "});
13807
13808    let mocked_response = lsp::SignatureHelp {
13809        signatures: vec![lsp::SignatureInformation {
13810            label: "fn sample(param1: u8, param2: u8)".to_string(),
13811            documentation: None,
13812            parameters: Some(vec![
13813                lsp::ParameterInformation {
13814                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13815                    documentation: None,
13816                },
13817                lsp::ParameterInformation {
13818                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13819                    documentation: None,
13820                },
13821            ]),
13822            active_parameter: None,
13823        }],
13824        active_signature: Some(0),
13825        active_parameter: Some(1),
13826    };
13827    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13828    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13829        .await;
13830
13831    // When selecting a range, the popover is gone.
13832    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13833    cx.update_editor(|editor, window, cx| {
13834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13835            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13836        })
13837    });
13838    cx.assert_editor_state(indoc! {"
13839        fn main() {
13840            sample(param1, «ˇparam2»);
13841        }
13842
13843        fn sample(param1: u8, param2: u8) {}
13844    "});
13845    cx.editor(|editor, _, _| {
13846        assert!(!editor.signature_help_state.is_shown());
13847    });
13848
13849    // When unselecting again, the popover is back if within the brackets.
13850    cx.update_editor(|editor, window, cx| {
13851        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13852            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13853        })
13854    });
13855    cx.assert_editor_state(indoc! {"
13856        fn main() {
13857            sample(param1, ˇparam2);
13858        }
13859
13860        fn sample(param1: u8, param2: u8) {}
13861    "});
13862    handle_signature_help_request(&mut cx, mocked_response).await;
13863    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13864        .await;
13865    cx.editor(|editor, _, _| {
13866        assert!(editor.signature_help_state.is_shown());
13867    });
13868
13869    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13870    cx.update_editor(|editor, window, cx| {
13871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13872            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13873            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13874        })
13875    });
13876    cx.assert_editor_state(indoc! {"
13877        fn main() {
13878            sample(param1, ˇparam2);
13879        }
13880
13881        fn sample(param1: u8, param2: u8) {}
13882    "});
13883
13884    let mocked_response = lsp::SignatureHelp {
13885        signatures: vec![lsp::SignatureInformation {
13886            label: "fn sample(param1: u8, param2: u8)".to_string(),
13887            documentation: None,
13888            parameters: Some(vec![
13889                lsp::ParameterInformation {
13890                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13891                    documentation: None,
13892                },
13893                lsp::ParameterInformation {
13894                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13895                    documentation: None,
13896                },
13897            ]),
13898            active_parameter: None,
13899        }],
13900        active_signature: Some(0),
13901        active_parameter: Some(1),
13902    };
13903    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13904    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13905        .await;
13906    cx.update_editor(|editor, _, cx| {
13907        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13908    });
13909    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13910        .await;
13911    cx.update_editor(|editor, window, cx| {
13912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13913            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13914        })
13915    });
13916    cx.assert_editor_state(indoc! {"
13917        fn main() {
13918            sample(param1, «ˇparam2»);
13919        }
13920
13921        fn sample(param1: u8, param2: u8) {}
13922    "});
13923    cx.update_editor(|editor, window, cx| {
13924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13925            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13926        })
13927    });
13928    cx.assert_editor_state(indoc! {"
13929        fn main() {
13930            sample(param1, ˇparam2);
13931        }
13932
13933        fn sample(param1: u8, param2: u8) {}
13934    "});
13935    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13936        .await;
13937}
13938
13939#[gpui::test]
13940async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13941    init_test(cx, |_| {});
13942
13943    let mut cx = EditorLspTestContext::new_rust(
13944        lsp::ServerCapabilities {
13945            signature_help_provider: Some(lsp::SignatureHelpOptions {
13946                ..Default::default()
13947            }),
13948            ..Default::default()
13949        },
13950        cx,
13951    )
13952    .await;
13953
13954    cx.set_state(indoc! {"
13955        fn main() {
13956            overloadedˇ
13957        }
13958    "});
13959
13960    cx.update_editor(|editor, window, cx| {
13961        editor.handle_input("(", window, cx);
13962        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13963    });
13964
13965    // Mock response with 3 signatures
13966    let mocked_response = lsp::SignatureHelp {
13967        signatures: vec![
13968            lsp::SignatureInformation {
13969                label: "fn overloaded(x: i32)".to_string(),
13970                documentation: None,
13971                parameters: Some(vec![lsp::ParameterInformation {
13972                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13973                    documentation: None,
13974                }]),
13975                active_parameter: None,
13976            },
13977            lsp::SignatureInformation {
13978                label: "fn overloaded(x: i32, y: i32)".to_string(),
13979                documentation: None,
13980                parameters: Some(vec![
13981                    lsp::ParameterInformation {
13982                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13983                        documentation: None,
13984                    },
13985                    lsp::ParameterInformation {
13986                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13987                        documentation: None,
13988                    },
13989                ]),
13990                active_parameter: None,
13991            },
13992            lsp::SignatureInformation {
13993                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13994                documentation: None,
13995                parameters: Some(vec![
13996                    lsp::ParameterInformation {
13997                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13998                        documentation: None,
13999                    },
14000                    lsp::ParameterInformation {
14001                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14002                        documentation: None,
14003                    },
14004                    lsp::ParameterInformation {
14005                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14006                        documentation: None,
14007                    },
14008                ]),
14009                active_parameter: None,
14010            },
14011        ],
14012        active_signature: Some(1),
14013        active_parameter: Some(0),
14014    };
14015    handle_signature_help_request(&mut cx, mocked_response).await;
14016
14017    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14018        .await;
14019
14020    // Verify we have multiple signatures and the right one is selected
14021    cx.editor(|editor, _, _| {
14022        let popover = editor.signature_help_state.popover().cloned().unwrap();
14023        assert_eq!(popover.signatures.len(), 3);
14024        // active_signature was 1, so that should be the current
14025        assert_eq!(popover.current_signature, 1);
14026        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14027        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14028        assert_eq!(
14029            popover.signatures[2].label,
14030            "fn overloaded(x: i32, y: i32, z: i32)"
14031        );
14032    });
14033
14034    // Test navigation functionality
14035    cx.update_editor(|editor, window, cx| {
14036        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14037    });
14038
14039    cx.editor(|editor, _, _| {
14040        let popover = editor.signature_help_state.popover().cloned().unwrap();
14041        assert_eq!(popover.current_signature, 2);
14042    });
14043
14044    // Test wrap around
14045    cx.update_editor(|editor, window, cx| {
14046        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14047    });
14048
14049    cx.editor(|editor, _, _| {
14050        let popover = editor.signature_help_state.popover().cloned().unwrap();
14051        assert_eq!(popover.current_signature, 0);
14052    });
14053
14054    // Test previous navigation
14055    cx.update_editor(|editor, window, cx| {
14056        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14057    });
14058
14059    cx.editor(|editor, _, _| {
14060        let popover = editor.signature_help_state.popover().cloned().unwrap();
14061        assert_eq!(popover.current_signature, 2);
14062    });
14063}
14064
14065#[gpui::test]
14066async fn test_completion_mode(cx: &mut TestAppContext) {
14067    init_test(cx, |_| {});
14068    let mut cx = EditorLspTestContext::new_rust(
14069        lsp::ServerCapabilities {
14070            completion_provider: Some(lsp::CompletionOptions {
14071                resolve_provider: Some(true),
14072                ..Default::default()
14073            }),
14074            ..Default::default()
14075        },
14076        cx,
14077    )
14078    .await;
14079
14080    struct Run {
14081        run_description: &'static str,
14082        initial_state: String,
14083        buffer_marked_text: String,
14084        completion_label: &'static str,
14085        completion_text: &'static str,
14086        expected_with_insert_mode: String,
14087        expected_with_replace_mode: String,
14088        expected_with_replace_subsequence_mode: String,
14089        expected_with_replace_suffix_mode: String,
14090    }
14091
14092    let runs = [
14093        Run {
14094            run_description: "Start of word matches completion text",
14095            initial_state: "before ediˇ after".into(),
14096            buffer_marked_text: "before <edi|> after".into(),
14097            completion_label: "editor",
14098            completion_text: "editor",
14099            expected_with_insert_mode: "before editorˇ after".into(),
14100            expected_with_replace_mode: "before editorˇ after".into(),
14101            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14102            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14103        },
14104        Run {
14105            run_description: "Accept same text at the middle of the word",
14106            initial_state: "before ediˇtor after".into(),
14107            buffer_marked_text: "before <edi|tor> after".into(),
14108            completion_label: "editor",
14109            completion_text: "editor",
14110            expected_with_insert_mode: "before editorˇtor after".into(),
14111            expected_with_replace_mode: "before editorˇ after".into(),
14112            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14113            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14114        },
14115        Run {
14116            run_description: "End of word matches completion text -- cursor at end",
14117            initial_state: "before torˇ after".into(),
14118            buffer_marked_text: "before <tor|> after".into(),
14119            completion_label: "editor",
14120            completion_text: "editor",
14121            expected_with_insert_mode: "before editorˇ after".into(),
14122            expected_with_replace_mode: "before editorˇ after".into(),
14123            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14124            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14125        },
14126        Run {
14127            run_description: "End of word matches completion text -- cursor at start",
14128            initial_state: "before ˇtor after".into(),
14129            buffer_marked_text: "before <|tor> after".into(),
14130            completion_label: "editor",
14131            completion_text: "editor",
14132            expected_with_insert_mode: "before editorˇtor after".into(),
14133            expected_with_replace_mode: "before editorˇ after".into(),
14134            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14135            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14136        },
14137        Run {
14138            run_description: "Prepend text containing whitespace",
14139            initial_state: "pˇfield: bool".into(),
14140            buffer_marked_text: "<p|field>: bool".into(),
14141            completion_label: "pub ",
14142            completion_text: "pub ",
14143            expected_with_insert_mode: "pub ˇfield: bool".into(),
14144            expected_with_replace_mode: "pub ˇ: bool".into(),
14145            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14146            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14147        },
14148        Run {
14149            run_description: "Add element to start of list",
14150            initial_state: "[element_ˇelement_2]".into(),
14151            buffer_marked_text: "[<element_|element_2>]".into(),
14152            completion_label: "element_1",
14153            completion_text: "element_1",
14154            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14155            expected_with_replace_mode: "[element_1ˇ]".into(),
14156            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14157            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14158        },
14159        Run {
14160            run_description: "Add element to start of list -- first and second elements are equal",
14161            initial_state: "[elˇelement]".into(),
14162            buffer_marked_text: "[<el|element>]".into(),
14163            completion_label: "element",
14164            completion_text: "element",
14165            expected_with_insert_mode: "[elementˇelement]".into(),
14166            expected_with_replace_mode: "[elementˇ]".into(),
14167            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14168            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14169        },
14170        Run {
14171            run_description: "Ends with matching suffix",
14172            initial_state: "SubˇError".into(),
14173            buffer_marked_text: "<Sub|Error>".into(),
14174            completion_label: "SubscriptionError",
14175            completion_text: "SubscriptionError",
14176            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14177            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14178            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14179            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14180        },
14181        Run {
14182            run_description: "Suffix is a subsequence -- contiguous",
14183            initial_state: "SubˇErr".into(),
14184            buffer_marked_text: "<Sub|Err>".into(),
14185            completion_label: "SubscriptionError",
14186            completion_text: "SubscriptionError",
14187            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14188            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14189            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14190            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14191        },
14192        Run {
14193            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14194            initial_state: "Suˇscrirr".into(),
14195            buffer_marked_text: "<Su|scrirr>".into(),
14196            completion_label: "SubscriptionError",
14197            completion_text: "SubscriptionError",
14198            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14199            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14200            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14201            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14202        },
14203        Run {
14204            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14205            initial_state: "foo(indˇix)".into(),
14206            buffer_marked_text: "foo(<ind|ix>)".into(),
14207            completion_label: "node_index",
14208            completion_text: "node_index",
14209            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14210            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14211            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14212            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14213        },
14214        Run {
14215            run_description: "Replace range ends before cursor - should extend to cursor",
14216            initial_state: "before editˇo after".into(),
14217            buffer_marked_text: "before <{ed}>it|o after".into(),
14218            completion_label: "editor",
14219            completion_text: "editor",
14220            expected_with_insert_mode: "before editorˇo after".into(),
14221            expected_with_replace_mode: "before editorˇo after".into(),
14222            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14223            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14224        },
14225        Run {
14226            run_description: "Uses label for suffix matching",
14227            initial_state: "before ediˇtor after".into(),
14228            buffer_marked_text: "before <edi|tor> after".into(),
14229            completion_label: "editor",
14230            completion_text: "editor()",
14231            expected_with_insert_mode: "before editor()ˇtor after".into(),
14232            expected_with_replace_mode: "before editor()ˇ after".into(),
14233            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14234            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14235        },
14236        Run {
14237            run_description: "Case insensitive subsequence and suffix matching",
14238            initial_state: "before EDiˇtoR after".into(),
14239            buffer_marked_text: "before <EDi|toR> after".into(),
14240            completion_label: "editor",
14241            completion_text: "editor",
14242            expected_with_insert_mode: "before editorˇtoR after".into(),
14243            expected_with_replace_mode: "before editorˇ after".into(),
14244            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14245            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14246        },
14247    ];
14248
14249    for run in runs {
14250        let run_variations = [
14251            (LspInsertMode::Insert, run.expected_with_insert_mode),
14252            (LspInsertMode::Replace, run.expected_with_replace_mode),
14253            (
14254                LspInsertMode::ReplaceSubsequence,
14255                run.expected_with_replace_subsequence_mode,
14256            ),
14257            (
14258                LspInsertMode::ReplaceSuffix,
14259                run.expected_with_replace_suffix_mode,
14260            ),
14261        ];
14262
14263        for (lsp_insert_mode, expected_text) in run_variations {
14264            eprintln!(
14265                "run = {:?}, mode = {lsp_insert_mode:.?}",
14266                run.run_description,
14267            );
14268
14269            update_test_language_settings(&mut cx, |settings| {
14270                settings.defaults.completions = Some(CompletionSettingsContent {
14271                    lsp_insert_mode: Some(lsp_insert_mode),
14272                    words: Some(WordsCompletionMode::Disabled),
14273                    words_min_length: Some(0),
14274                    ..Default::default()
14275                });
14276            });
14277
14278            cx.set_state(&run.initial_state);
14279            cx.update_editor(|editor, window, cx| {
14280                editor.show_completions(&ShowCompletions, window, cx);
14281            });
14282
14283            let counter = Arc::new(AtomicUsize::new(0));
14284            handle_completion_request_with_insert_and_replace(
14285                &mut cx,
14286                &run.buffer_marked_text,
14287                vec![(run.completion_label, run.completion_text)],
14288                counter.clone(),
14289            )
14290            .await;
14291            cx.condition(|editor, _| editor.context_menu_visible())
14292                .await;
14293            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14294
14295            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14296                editor
14297                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14298                    .unwrap()
14299            });
14300            cx.assert_editor_state(&expected_text);
14301            handle_resolve_completion_request(&mut cx, None).await;
14302            apply_additional_edits.await.unwrap();
14303        }
14304    }
14305}
14306
14307#[gpui::test]
14308async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14309    init_test(cx, |_| {});
14310    let mut cx = EditorLspTestContext::new_rust(
14311        lsp::ServerCapabilities {
14312            completion_provider: Some(lsp::CompletionOptions {
14313                resolve_provider: Some(true),
14314                ..Default::default()
14315            }),
14316            ..Default::default()
14317        },
14318        cx,
14319    )
14320    .await;
14321
14322    let initial_state = "SubˇError";
14323    let buffer_marked_text = "<Sub|Error>";
14324    let completion_text = "SubscriptionError";
14325    let expected_with_insert_mode = "SubscriptionErrorˇError";
14326    let expected_with_replace_mode = "SubscriptionErrorˇ";
14327
14328    update_test_language_settings(&mut cx, |settings| {
14329        settings.defaults.completions = Some(CompletionSettingsContent {
14330            words: Some(WordsCompletionMode::Disabled),
14331            words_min_length: Some(0),
14332            // set the opposite here to ensure that the action is overriding the default behavior
14333            lsp_insert_mode: Some(LspInsertMode::Insert),
14334            ..Default::default()
14335        });
14336    });
14337
14338    cx.set_state(initial_state);
14339    cx.update_editor(|editor, window, cx| {
14340        editor.show_completions(&ShowCompletions, window, cx);
14341    });
14342
14343    let counter = Arc::new(AtomicUsize::new(0));
14344    handle_completion_request_with_insert_and_replace(
14345        &mut cx,
14346        buffer_marked_text,
14347        vec![(completion_text, completion_text)],
14348        counter.clone(),
14349    )
14350    .await;
14351    cx.condition(|editor, _| editor.context_menu_visible())
14352        .await;
14353    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14354
14355    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14356        editor
14357            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14358            .unwrap()
14359    });
14360    cx.assert_editor_state(expected_with_replace_mode);
14361    handle_resolve_completion_request(&mut cx, None).await;
14362    apply_additional_edits.await.unwrap();
14363
14364    update_test_language_settings(&mut cx, |settings| {
14365        settings.defaults.completions = Some(CompletionSettingsContent {
14366            words: Some(WordsCompletionMode::Disabled),
14367            words_min_length: Some(0),
14368            // set the opposite here to ensure that the action is overriding the default behavior
14369            lsp_insert_mode: Some(LspInsertMode::Replace),
14370            ..Default::default()
14371        });
14372    });
14373
14374    cx.set_state(initial_state);
14375    cx.update_editor(|editor, window, cx| {
14376        editor.show_completions(&ShowCompletions, window, cx);
14377    });
14378    handle_completion_request_with_insert_and_replace(
14379        &mut cx,
14380        buffer_marked_text,
14381        vec![(completion_text, completion_text)],
14382        counter.clone(),
14383    )
14384    .await;
14385    cx.condition(|editor, _| editor.context_menu_visible())
14386        .await;
14387    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14388
14389    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14390        editor
14391            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14392            .unwrap()
14393    });
14394    cx.assert_editor_state(expected_with_insert_mode);
14395    handle_resolve_completion_request(&mut cx, None).await;
14396    apply_additional_edits.await.unwrap();
14397}
14398
14399#[gpui::test]
14400async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14401    init_test(cx, |_| {});
14402    let mut cx = EditorLspTestContext::new_rust(
14403        lsp::ServerCapabilities {
14404            completion_provider: Some(lsp::CompletionOptions {
14405                resolve_provider: Some(true),
14406                ..Default::default()
14407            }),
14408            ..Default::default()
14409        },
14410        cx,
14411    )
14412    .await;
14413
14414    // scenario: surrounding text matches completion text
14415    let completion_text = "to_offset";
14416    let initial_state = indoc! {"
14417        1. buf.to_offˇsuffix
14418        2. buf.to_offˇsuf
14419        3. buf.to_offˇfix
14420        4. buf.to_offˇ
14421        5. into_offˇensive
14422        6. ˇsuffix
14423        7. let ˇ //
14424        8. aaˇzz
14425        9. buf.to_off«zzzzzˇ»suffix
14426        10. buf.«ˇzzzzz»suffix
14427        11. to_off«ˇzzzzz»
14428
14429        buf.to_offˇsuffix  // newest cursor
14430    "};
14431    let completion_marked_buffer = indoc! {"
14432        1. buf.to_offsuffix
14433        2. buf.to_offsuf
14434        3. buf.to_offfix
14435        4. buf.to_off
14436        5. into_offensive
14437        6. suffix
14438        7. let  //
14439        8. aazz
14440        9. buf.to_offzzzzzsuffix
14441        10. buf.zzzzzsuffix
14442        11. to_offzzzzz
14443
14444        buf.<to_off|suffix>  // newest cursor
14445    "};
14446    let expected = indoc! {"
14447        1. buf.to_offsetˇ
14448        2. buf.to_offsetˇsuf
14449        3. buf.to_offsetˇfix
14450        4. buf.to_offsetˇ
14451        5. into_offsetˇensive
14452        6. to_offsetˇsuffix
14453        7. let to_offsetˇ //
14454        8. aato_offsetˇzz
14455        9. buf.to_offsetˇ
14456        10. buf.to_offsetˇsuffix
14457        11. to_offsetˇ
14458
14459        buf.to_offsetˇ  // newest cursor
14460    "};
14461    cx.set_state(initial_state);
14462    cx.update_editor(|editor, window, cx| {
14463        editor.show_completions(&ShowCompletions, window, cx);
14464    });
14465    handle_completion_request_with_insert_and_replace(
14466        &mut cx,
14467        completion_marked_buffer,
14468        vec![(completion_text, completion_text)],
14469        Arc::new(AtomicUsize::new(0)),
14470    )
14471    .await;
14472    cx.condition(|editor, _| editor.context_menu_visible())
14473        .await;
14474    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14475        editor
14476            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14477            .unwrap()
14478    });
14479    cx.assert_editor_state(expected);
14480    handle_resolve_completion_request(&mut cx, None).await;
14481    apply_additional_edits.await.unwrap();
14482
14483    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14484    let completion_text = "foo_and_bar";
14485    let initial_state = indoc! {"
14486        1. ooanbˇ
14487        2. zooanbˇ
14488        3. ooanbˇz
14489        4. zooanbˇz
14490        5. ooanˇ
14491        6. oanbˇ
14492
14493        ooanbˇ
14494    "};
14495    let completion_marked_buffer = indoc! {"
14496        1. ooanb
14497        2. zooanb
14498        3. ooanbz
14499        4. zooanbz
14500        5. ooan
14501        6. oanb
14502
14503        <ooanb|>
14504    "};
14505    let expected = indoc! {"
14506        1. foo_and_barˇ
14507        2. zfoo_and_barˇ
14508        3. foo_and_barˇz
14509        4. zfoo_and_barˇz
14510        5. ooanfoo_and_barˇ
14511        6. oanbfoo_and_barˇ
14512
14513        foo_and_barˇ
14514    "};
14515    cx.set_state(initial_state);
14516    cx.update_editor(|editor, window, cx| {
14517        editor.show_completions(&ShowCompletions, window, cx);
14518    });
14519    handle_completion_request_with_insert_and_replace(
14520        &mut cx,
14521        completion_marked_buffer,
14522        vec![(completion_text, completion_text)],
14523        Arc::new(AtomicUsize::new(0)),
14524    )
14525    .await;
14526    cx.condition(|editor, _| editor.context_menu_visible())
14527        .await;
14528    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14529        editor
14530            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14531            .unwrap()
14532    });
14533    cx.assert_editor_state(expected);
14534    handle_resolve_completion_request(&mut cx, None).await;
14535    apply_additional_edits.await.unwrap();
14536
14537    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14538    // (expects the same as if it was inserted at the end)
14539    let completion_text = "foo_and_bar";
14540    let initial_state = indoc! {"
14541        1. ooˇanb
14542        2. zooˇanb
14543        3. ooˇanbz
14544        4. zooˇanbz
14545
14546        ooˇanb
14547    "};
14548    let completion_marked_buffer = indoc! {"
14549        1. ooanb
14550        2. zooanb
14551        3. ooanbz
14552        4. zooanbz
14553
14554        <oo|anb>
14555    "};
14556    let expected = indoc! {"
14557        1. foo_and_barˇ
14558        2. zfoo_and_barˇ
14559        3. foo_and_barˇz
14560        4. zfoo_and_barˇz
14561
14562        foo_and_barˇ
14563    "};
14564    cx.set_state(initial_state);
14565    cx.update_editor(|editor, window, cx| {
14566        editor.show_completions(&ShowCompletions, window, cx);
14567    });
14568    handle_completion_request_with_insert_and_replace(
14569        &mut cx,
14570        completion_marked_buffer,
14571        vec![(completion_text, completion_text)],
14572        Arc::new(AtomicUsize::new(0)),
14573    )
14574    .await;
14575    cx.condition(|editor, _| editor.context_menu_visible())
14576        .await;
14577    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14578        editor
14579            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14580            .unwrap()
14581    });
14582    cx.assert_editor_state(expected);
14583    handle_resolve_completion_request(&mut cx, None).await;
14584    apply_additional_edits.await.unwrap();
14585}
14586
14587// This used to crash
14588#[gpui::test]
14589async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14590    init_test(cx, |_| {});
14591
14592    let buffer_text = indoc! {"
14593        fn main() {
14594            10.satu;
14595
14596            //
14597            // separate cursors so they open in different excerpts (manually reproducible)
14598            //
14599
14600            10.satu20;
14601        }
14602    "};
14603    let multibuffer_text_with_selections = indoc! {"
14604        fn main() {
14605            10.satuˇ;
14606
14607            //
14608
14609            //
14610
14611            10.satuˇ20;
14612        }
14613    "};
14614    let expected_multibuffer = indoc! {"
14615        fn main() {
14616            10.saturating_sub()ˇ;
14617
14618            //
14619
14620            //
14621
14622            10.saturating_sub()ˇ;
14623        }
14624    "};
14625
14626    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14627    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14628
14629    let fs = FakeFs::new(cx.executor());
14630    fs.insert_tree(
14631        path!("/a"),
14632        json!({
14633            "main.rs": buffer_text,
14634        }),
14635    )
14636    .await;
14637
14638    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14639    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14640    language_registry.add(rust_lang());
14641    let mut fake_servers = language_registry.register_fake_lsp(
14642        "Rust",
14643        FakeLspAdapter {
14644            capabilities: lsp::ServerCapabilities {
14645                completion_provider: Some(lsp::CompletionOptions {
14646                    resolve_provider: None,
14647                    ..lsp::CompletionOptions::default()
14648                }),
14649                ..lsp::ServerCapabilities::default()
14650            },
14651            ..FakeLspAdapter::default()
14652        },
14653    );
14654    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14655    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14656    let buffer = project
14657        .update(cx, |project, cx| {
14658            project.open_local_buffer(path!("/a/main.rs"), cx)
14659        })
14660        .await
14661        .unwrap();
14662
14663    let multi_buffer = cx.new(|cx| {
14664        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14665        multi_buffer.push_excerpts(
14666            buffer.clone(),
14667            [ExcerptRange::new(0..first_excerpt_end)],
14668            cx,
14669        );
14670        multi_buffer.push_excerpts(
14671            buffer.clone(),
14672            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14673            cx,
14674        );
14675        multi_buffer
14676    });
14677
14678    let editor = workspace
14679        .update(cx, |_, window, cx| {
14680            cx.new(|cx| {
14681                Editor::new(
14682                    EditorMode::Full {
14683                        scale_ui_elements_with_buffer_font_size: false,
14684                        show_active_line_background: false,
14685                        sizing_behavior: SizingBehavior::Default,
14686                    },
14687                    multi_buffer.clone(),
14688                    Some(project.clone()),
14689                    window,
14690                    cx,
14691                )
14692            })
14693        })
14694        .unwrap();
14695
14696    let pane = workspace
14697        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14698        .unwrap();
14699    pane.update_in(cx, |pane, window, cx| {
14700        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14701    });
14702
14703    let fake_server = fake_servers.next().await.unwrap();
14704
14705    editor.update_in(cx, |editor, window, cx| {
14706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14707            s.select_ranges([
14708                Point::new(1, 11)..Point::new(1, 11),
14709                Point::new(7, 11)..Point::new(7, 11),
14710            ])
14711        });
14712
14713        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14714    });
14715
14716    editor.update_in(cx, |editor, window, cx| {
14717        editor.show_completions(&ShowCompletions, window, cx);
14718    });
14719
14720    fake_server
14721        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14722            let completion_item = lsp::CompletionItem {
14723                label: "saturating_sub()".into(),
14724                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14725                    lsp::InsertReplaceEdit {
14726                        new_text: "saturating_sub()".to_owned(),
14727                        insert: lsp::Range::new(
14728                            lsp::Position::new(7, 7),
14729                            lsp::Position::new(7, 11),
14730                        ),
14731                        replace: lsp::Range::new(
14732                            lsp::Position::new(7, 7),
14733                            lsp::Position::new(7, 13),
14734                        ),
14735                    },
14736                )),
14737                ..lsp::CompletionItem::default()
14738            };
14739
14740            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14741        })
14742        .next()
14743        .await
14744        .unwrap();
14745
14746    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14747        .await;
14748
14749    editor
14750        .update_in(cx, |editor, window, cx| {
14751            editor
14752                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14753                .unwrap()
14754        })
14755        .await
14756        .unwrap();
14757
14758    editor.update(cx, |editor, cx| {
14759        assert_text_with_selections(editor, expected_multibuffer, cx);
14760    })
14761}
14762
14763#[gpui::test]
14764async fn test_completion(cx: &mut TestAppContext) {
14765    init_test(cx, |_| {});
14766
14767    let mut cx = EditorLspTestContext::new_rust(
14768        lsp::ServerCapabilities {
14769            completion_provider: Some(lsp::CompletionOptions {
14770                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14771                resolve_provider: Some(true),
14772                ..Default::default()
14773            }),
14774            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14775            ..Default::default()
14776        },
14777        cx,
14778    )
14779    .await;
14780    let counter = Arc::new(AtomicUsize::new(0));
14781
14782    cx.set_state(indoc! {"
14783        oneˇ
14784        two
14785        three
14786    "});
14787    cx.simulate_keystroke(".");
14788    handle_completion_request(
14789        indoc! {"
14790            one.|<>
14791            two
14792            three
14793        "},
14794        vec!["first_completion", "second_completion"],
14795        true,
14796        counter.clone(),
14797        &mut cx,
14798    )
14799    .await;
14800    cx.condition(|editor, _| editor.context_menu_visible())
14801        .await;
14802    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14803
14804    let _handler = handle_signature_help_request(
14805        &mut cx,
14806        lsp::SignatureHelp {
14807            signatures: vec![lsp::SignatureInformation {
14808                label: "test signature".to_string(),
14809                documentation: None,
14810                parameters: Some(vec![lsp::ParameterInformation {
14811                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14812                    documentation: None,
14813                }]),
14814                active_parameter: None,
14815            }],
14816            active_signature: None,
14817            active_parameter: None,
14818        },
14819    );
14820    cx.update_editor(|editor, window, cx| {
14821        assert!(
14822            !editor.signature_help_state.is_shown(),
14823            "No signature help was called for"
14824        );
14825        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14826    });
14827    cx.run_until_parked();
14828    cx.update_editor(|editor, _, _| {
14829        assert!(
14830            !editor.signature_help_state.is_shown(),
14831            "No signature help should be shown when completions menu is open"
14832        );
14833    });
14834
14835    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14836        editor.context_menu_next(&Default::default(), window, cx);
14837        editor
14838            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14839            .unwrap()
14840    });
14841    cx.assert_editor_state(indoc! {"
14842        one.second_completionˇ
14843        two
14844        three
14845    "});
14846
14847    handle_resolve_completion_request(
14848        &mut cx,
14849        Some(vec![
14850            (
14851                //This overlaps with the primary completion edit which is
14852                //misbehavior from the LSP spec, test that we filter it out
14853                indoc! {"
14854                    one.second_ˇcompletion
14855                    two
14856                    threeˇ
14857                "},
14858                "overlapping additional edit",
14859            ),
14860            (
14861                indoc! {"
14862                    one.second_completion
14863                    two
14864                    threeˇ
14865                "},
14866                "\nadditional edit",
14867            ),
14868        ]),
14869    )
14870    .await;
14871    apply_additional_edits.await.unwrap();
14872    cx.assert_editor_state(indoc! {"
14873        one.second_completionˇ
14874        two
14875        three
14876        additional edit
14877    "});
14878
14879    cx.set_state(indoc! {"
14880        one.second_completion
14881        twoˇ
14882        threeˇ
14883        additional edit
14884    "});
14885    cx.simulate_keystroke(" ");
14886    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14887    cx.simulate_keystroke("s");
14888    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14889
14890    cx.assert_editor_state(indoc! {"
14891        one.second_completion
14892        two sˇ
14893        three sˇ
14894        additional edit
14895    "});
14896    handle_completion_request(
14897        indoc! {"
14898            one.second_completion
14899            two s
14900            three <s|>
14901            additional edit
14902        "},
14903        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14904        true,
14905        counter.clone(),
14906        &mut cx,
14907    )
14908    .await;
14909    cx.condition(|editor, _| editor.context_menu_visible())
14910        .await;
14911    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14912
14913    cx.simulate_keystroke("i");
14914
14915    handle_completion_request(
14916        indoc! {"
14917            one.second_completion
14918            two si
14919            three <si|>
14920            additional edit
14921        "},
14922        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14923        true,
14924        counter.clone(),
14925        &mut cx,
14926    )
14927    .await;
14928    cx.condition(|editor, _| editor.context_menu_visible())
14929        .await;
14930    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14931
14932    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14933        editor
14934            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14935            .unwrap()
14936    });
14937    cx.assert_editor_state(indoc! {"
14938        one.second_completion
14939        two sixth_completionˇ
14940        three sixth_completionˇ
14941        additional edit
14942    "});
14943
14944    apply_additional_edits.await.unwrap();
14945
14946    update_test_language_settings(&mut cx, |settings| {
14947        settings.defaults.show_completions_on_input = Some(false);
14948    });
14949    cx.set_state("editorˇ");
14950    cx.simulate_keystroke(".");
14951    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14952    cx.simulate_keystrokes("c l o");
14953    cx.assert_editor_state("editor.cloˇ");
14954    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14955    cx.update_editor(|editor, window, cx| {
14956        editor.show_completions(&ShowCompletions, window, cx);
14957    });
14958    handle_completion_request(
14959        "editor.<clo|>",
14960        vec!["close", "clobber"],
14961        true,
14962        counter.clone(),
14963        &mut cx,
14964    )
14965    .await;
14966    cx.condition(|editor, _| editor.context_menu_visible())
14967        .await;
14968    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14969
14970    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14971        editor
14972            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14973            .unwrap()
14974    });
14975    cx.assert_editor_state("editor.clobberˇ");
14976    handle_resolve_completion_request(&mut cx, None).await;
14977    apply_additional_edits.await.unwrap();
14978}
14979
14980#[gpui::test]
14981async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14982    init_test(cx, |_| {});
14983
14984    let fs = FakeFs::new(cx.executor());
14985    fs.insert_tree(
14986        path!("/a"),
14987        json!({
14988            "main.rs": "",
14989        }),
14990    )
14991    .await;
14992
14993    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14994    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14995    language_registry.add(rust_lang());
14996    let command_calls = Arc::new(AtomicUsize::new(0));
14997    let registered_command = "_the/command";
14998
14999    let closure_command_calls = command_calls.clone();
15000    let mut fake_servers = language_registry.register_fake_lsp(
15001        "Rust",
15002        FakeLspAdapter {
15003            capabilities: lsp::ServerCapabilities {
15004                completion_provider: Some(lsp::CompletionOptions {
15005                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15006                    ..lsp::CompletionOptions::default()
15007                }),
15008                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15009                    commands: vec![registered_command.to_owned()],
15010                    ..lsp::ExecuteCommandOptions::default()
15011                }),
15012                ..lsp::ServerCapabilities::default()
15013            },
15014            initializer: Some(Box::new(move |fake_server| {
15015                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15016                    move |params, _| async move {
15017                        Ok(Some(lsp::CompletionResponse::Array(vec![
15018                            lsp::CompletionItem {
15019                                label: "registered_command".to_owned(),
15020                                text_edit: gen_text_edit(&params, ""),
15021                                command: Some(lsp::Command {
15022                                    title: registered_command.to_owned(),
15023                                    command: "_the/command".to_owned(),
15024                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15025                                }),
15026                                ..lsp::CompletionItem::default()
15027                            },
15028                            lsp::CompletionItem {
15029                                label: "unregistered_command".to_owned(),
15030                                text_edit: gen_text_edit(&params, ""),
15031                                command: Some(lsp::Command {
15032                                    title: "????????????".to_owned(),
15033                                    command: "????????????".to_owned(),
15034                                    arguments: Some(vec![serde_json::Value::Null]),
15035                                }),
15036                                ..lsp::CompletionItem::default()
15037                            },
15038                        ])))
15039                    },
15040                );
15041                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15042                    let command_calls = closure_command_calls.clone();
15043                    move |params, _| {
15044                        assert_eq!(params.command, registered_command);
15045                        let command_calls = command_calls.clone();
15046                        async move {
15047                            command_calls.fetch_add(1, atomic::Ordering::Release);
15048                            Ok(Some(json!(null)))
15049                        }
15050                    }
15051                });
15052            })),
15053            ..FakeLspAdapter::default()
15054        },
15055    );
15056    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15057    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15058    let editor = workspace
15059        .update(cx, |workspace, window, cx| {
15060            workspace.open_abs_path(
15061                PathBuf::from(path!("/a/main.rs")),
15062                OpenOptions::default(),
15063                window,
15064                cx,
15065            )
15066        })
15067        .unwrap()
15068        .await
15069        .unwrap()
15070        .downcast::<Editor>()
15071        .unwrap();
15072    let _fake_server = fake_servers.next().await.unwrap();
15073
15074    editor.update_in(cx, |editor, window, cx| {
15075        cx.focus_self(window);
15076        editor.move_to_end(&MoveToEnd, window, cx);
15077        editor.handle_input(".", window, cx);
15078    });
15079    cx.run_until_parked();
15080    editor.update(cx, |editor, _| {
15081        assert!(editor.context_menu_visible());
15082        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15083        {
15084            let completion_labels = menu
15085                .completions
15086                .borrow()
15087                .iter()
15088                .map(|c| c.label.text.clone())
15089                .collect::<Vec<_>>();
15090            assert_eq!(
15091                completion_labels,
15092                &["registered_command", "unregistered_command",],
15093            );
15094        } else {
15095            panic!("expected completion menu to be open");
15096        }
15097    });
15098
15099    editor
15100        .update_in(cx, |editor, window, cx| {
15101            editor
15102                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15103                .unwrap()
15104        })
15105        .await
15106        .unwrap();
15107    cx.run_until_parked();
15108    assert_eq!(
15109        command_calls.load(atomic::Ordering::Acquire),
15110        1,
15111        "For completion with a registered command, Zed should send a command execution request",
15112    );
15113
15114    editor.update_in(cx, |editor, window, cx| {
15115        cx.focus_self(window);
15116        editor.handle_input(".", window, cx);
15117    });
15118    cx.run_until_parked();
15119    editor.update(cx, |editor, _| {
15120        assert!(editor.context_menu_visible());
15121        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15122        {
15123            let completion_labels = menu
15124                .completions
15125                .borrow()
15126                .iter()
15127                .map(|c| c.label.text.clone())
15128                .collect::<Vec<_>>();
15129            assert_eq!(
15130                completion_labels,
15131                &["registered_command", "unregistered_command",],
15132            );
15133        } else {
15134            panic!("expected completion menu to be open");
15135        }
15136    });
15137    editor
15138        .update_in(cx, |editor, window, cx| {
15139            editor.context_menu_next(&Default::default(), window, cx);
15140            editor
15141                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15142                .unwrap()
15143        })
15144        .await
15145        .unwrap();
15146    cx.run_until_parked();
15147    assert_eq!(
15148        command_calls.load(atomic::Ordering::Acquire),
15149        1,
15150        "For completion with an unregistered command, Zed should not send a command execution request",
15151    );
15152}
15153
15154#[gpui::test]
15155async fn test_completion_reuse(cx: &mut TestAppContext) {
15156    init_test(cx, |_| {});
15157
15158    let mut cx = EditorLspTestContext::new_rust(
15159        lsp::ServerCapabilities {
15160            completion_provider: Some(lsp::CompletionOptions {
15161                trigger_characters: Some(vec![".".to_string()]),
15162                ..Default::default()
15163            }),
15164            ..Default::default()
15165        },
15166        cx,
15167    )
15168    .await;
15169
15170    let counter = Arc::new(AtomicUsize::new(0));
15171    cx.set_state("objˇ");
15172    cx.simulate_keystroke(".");
15173
15174    // Initial completion request returns complete results
15175    let is_incomplete = false;
15176    handle_completion_request(
15177        "obj.|<>",
15178        vec!["a", "ab", "abc"],
15179        is_incomplete,
15180        counter.clone(),
15181        &mut cx,
15182    )
15183    .await;
15184    cx.run_until_parked();
15185    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15186    cx.assert_editor_state("obj.ˇ");
15187    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15188
15189    // Type "a" - filters existing completions
15190    cx.simulate_keystroke("a");
15191    cx.run_until_parked();
15192    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15193    cx.assert_editor_state("obj.aˇ");
15194    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15195
15196    // Type "b" - filters existing completions
15197    cx.simulate_keystroke("b");
15198    cx.run_until_parked();
15199    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15200    cx.assert_editor_state("obj.abˇ");
15201    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15202
15203    // Type "c" - filters existing completions
15204    cx.simulate_keystroke("c");
15205    cx.run_until_parked();
15206    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15207    cx.assert_editor_state("obj.abcˇ");
15208    check_displayed_completions(vec!["abc"], &mut cx);
15209
15210    // Backspace to delete "c" - filters existing completions
15211    cx.update_editor(|editor, window, cx| {
15212        editor.backspace(&Backspace, window, cx);
15213    });
15214    cx.run_until_parked();
15215    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15216    cx.assert_editor_state("obj.abˇ");
15217    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15218
15219    // Moving cursor to the left dismisses menu.
15220    cx.update_editor(|editor, window, cx| {
15221        editor.move_left(&MoveLeft, window, cx);
15222    });
15223    cx.run_until_parked();
15224    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15225    cx.assert_editor_state("obj.aˇb");
15226    cx.update_editor(|editor, _, _| {
15227        assert_eq!(editor.context_menu_visible(), false);
15228    });
15229
15230    // Type "b" - new request
15231    cx.simulate_keystroke("b");
15232    let is_incomplete = false;
15233    handle_completion_request(
15234        "obj.<ab|>a",
15235        vec!["ab", "abc"],
15236        is_incomplete,
15237        counter.clone(),
15238        &mut cx,
15239    )
15240    .await;
15241    cx.run_until_parked();
15242    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15243    cx.assert_editor_state("obj.abˇb");
15244    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15245
15246    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15247    cx.update_editor(|editor, window, cx| {
15248        editor.backspace(&Backspace, window, cx);
15249    });
15250    let is_incomplete = false;
15251    handle_completion_request(
15252        "obj.<a|>b",
15253        vec!["a", "ab", "abc"],
15254        is_incomplete,
15255        counter.clone(),
15256        &mut cx,
15257    )
15258    .await;
15259    cx.run_until_parked();
15260    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15261    cx.assert_editor_state("obj.aˇb");
15262    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15263
15264    // Backspace to delete "a" - dismisses menu.
15265    cx.update_editor(|editor, window, cx| {
15266        editor.backspace(&Backspace, window, cx);
15267    });
15268    cx.run_until_parked();
15269    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15270    cx.assert_editor_state("obj.ˇb");
15271    cx.update_editor(|editor, _, _| {
15272        assert_eq!(editor.context_menu_visible(), false);
15273    });
15274}
15275
15276#[gpui::test]
15277async fn test_word_completion(cx: &mut TestAppContext) {
15278    let lsp_fetch_timeout_ms = 10;
15279    init_test(cx, |language_settings| {
15280        language_settings.defaults.completions = Some(CompletionSettingsContent {
15281            words_min_length: Some(0),
15282            lsp_fetch_timeout_ms: Some(10),
15283            lsp_insert_mode: Some(LspInsertMode::Insert),
15284            ..Default::default()
15285        });
15286    });
15287
15288    let mut cx = EditorLspTestContext::new_rust(
15289        lsp::ServerCapabilities {
15290            completion_provider: Some(lsp::CompletionOptions {
15291                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15292                ..lsp::CompletionOptions::default()
15293            }),
15294            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15295            ..lsp::ServerCapabilities::default()
15296        },
15297        cx,
15298    )
15299    .await;
15300
15301    let throttle_completions = Arc::new(AtomicBool::new(false));
15302
15303    let lsp_throttle_completions = throttle_completions.clone();
15304    let _completion_requests_handler =
15305        cx.lsp
15306            .server
15307            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15308                let lsp_throttle_completions = lsp_throttle_completions.clone();
15309                let cx = cx.clone();
15310                async move {
15311                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15312                        cx.background_executor()
15313                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15314                            .await;
15315                    }
15316                    Ok(Some(lsp::CompletionResponse::Array(vec![
15317                        lsp::CompletionItem {
15318                            label: "first".into(),
15319                            ..lsp::CompletionItem::default()
15320                        },
15321                        lsp::CompletionItem {
15322                            label: "last".into(),
15323                            ..lsp::CompletionItem::default()
15324                        },
15325                    ])))
15326                }
15327            });
15328
15329    cx.set_state(indoc! {"
15330        oneˇ
15331        two
15332        three
15333    "});
15334    cx.simulate_keystroke(".");
15335    cx.executor().run_until_parked();
15336    cx.condition(|editor, _| editor.context_menu_visible())
15337        .await;
15338    cx.update_editor(|editor, window, cx| {
15339        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15340        {
15341            assert_eq!(
15342                completion_menu_entries(menu),
15343                &["first", "last"],
15344                "When LSP server is fast to reply, no fallback word completions are used"
15345            );
15346        } else {
15347            panic!("expected completion menu to be open");
15348        }
15349        editor.cancel(&Cancel, window, cx);
15350    });
15351    cx.executor().run_until_parked();
15352    cx.condition(|editor, _| !editor.context_menu_visible())
15353        .await;
15354
15355    throttle_completions.store(true, atomic::Ordering::Release);
15356    cx.simulate_keystroke(".");
15357    cx.executor()
15358        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15359    cx.executor().run_until_parked();
15360    cx.condition(|editor, _| editor.context_menu_visible())
15361        .await;
15362    cx.update_editor(|editor, _, _| {
15363        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15364        {
15365            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15366                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15367        } else {
15368            panic!("expected completion menu to be open");
15369        }
15370    });
15371}
15372
15373#[gpui::test]
15374async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15375    init_test(cx, |language_settings| {
15376        language_settings.defaults.completions = Some(CompletionSettingsContent {
15377            words: Some(WordsCompletionMode::Enabled),
15378            words_min_length: Some(0),
15379            lsp_insert_mode: Some(LspInsertMode::Insert),
15380            ..Default::default()
15381        });
15382    });
15383
15384    let mut cx = EditorLspTestContext::new_rust(
15385        lsp::ServerCapabilities {
15386            completion_provider: Some(lsp::CompletionOptions {
15387                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15388                ..lsp::CompletionOptions::default()
15389            }),
15390            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15391            ..lsp::ServerCapabilities::default()
15392        },
15393        cx,
15394    )
15395    .await;
15396
15397    let _completion_requests_handler =
15398        cx.lsp
15399            .server
15400            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15401                Ok(Some(lsp::CompletionResponse::Array(vec![
15402                    lsp::CompletionItem {
15403                        label: "first".into(),
15404                        ..lsp::CompletionItem::default()
15405                    },
15406                    lsp::CompletionItem {
15407                        label: "last".into(),
15408                        ..lsp::CompletionItem::default()
15409                    },
15410                ])))
15411            });
15412
15413    cx.set_state(indoc! {"ˇ
15414        first
15415        last
15416        second
15417    "});
15418    cx.simulate_keystroke(".");
15419    cx.executor().run_until_parked();
15420    cx.condition(|editor, _| editor.context_menu_visible())
15421        .await;
15422    cx.update_editor(|editor, _, _| {
15423        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15424        {
15425            assert_eq!(
15426                completion_menu_entries(menu),
15427                &["first", "last", "second"],
15428                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15429            );
15430        } else {
15431            panic!("expected completion menu to be open");
15432        }
15433    });
15434}
15435
15436#[gpui::test]
15437async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15438    init_test(cx, |language_settings| {
15439        language_settings.defaults.completions = Some(CompletionSettingsContent {
15440            words: Some(WordsCompletionMode::Disabled),
15441            words_min_length: Some(0),
15442            lsp_insert_mode: Some(LspInsertMode::Insert),
15443            ..Default::default()
15444        });
15445    });
15446
15447    let mut cx = EditorLspTestContext::new_rust(
15448        lsp::ServerCapabilities {
15449            completion_provider: Some(lsp::CompletionOptions {
15450                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15451                ..lsp::CompletionOptions::default()
15452            }),
15453            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15454            ..lsp::ServerCapabilities::default()
15455        },
15456        cx,
15457    )
15458    .await;
15459
15460    let _completion_requests_handler =
15461        cx.lsp
15462            .server
15463            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15464                panic!("LSP completions should not be queried when dealing with word completions")
15465            });
15466
15467    cx.set_state(indoc! {"ˇ
15468        first
15469        last
15470        second
15471    "});
15472    cx.update_editor(|editor, window, cx| {
15473        editor.show_word_completions(&ShowWordCompletions, window, cx);
15474    });
15475    cx.executor().run_until_parked();
15476    cx.condition(|editor, _| editor.context_menu_visible())
15477        .await;
15478    cx.update_editor(|editor, _, _| {
15479        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15480        {
15481            assert_eq!(
15482                completion_menu_entries(menu),
15483                &["first", "last", "second"],
15484                "`ShowWordCompletions` action should show word completions"
15485            );
15486        } else {
15487            panic!("expected completion menu to be open");
15488        }
15489    });
15490
15491    cx.simulate_keystroke("l");
15492    cx.executor().run_until_parked();
15493    cx.condition(|editor, _| editor.context_menu_visible())
15494        .await;
15495    cx.update_editor(|editor, _, _| {
15496        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15497        {
15498            assert_eq!(
15499                completion_menu_entries(menu),
15500                &["last"],
15501                "After showing word completions, further editing should filter them and not query the LSP"
15502            );
15503        } else {
15504            panic!("expected completion menu to be open");
15505        }
15506    });
15507}
15508
15509#[gpui::test]
15510async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15511    init_test(cx, |language_settings| {
15512        language_settings.defaults.completions = Some(CompletionSettingsContent {
15513            words_min_length: Some(0),
15514            lsp: Some(false),
15515            lsp_insert_mode: Some(LspInsertMode::Insert),
15516            ..Default::default()
15517        });
15518    });
15519
15520    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15521
15522    cx.set_state(indoc! {"ˇ
15523        0_usize
15524        let
15525        33
15526        4.5f32
15527    "});
15528    cx.update_editor(|editor, window, cx| {
15529        editor.show_completions(&ShowCompletions, window, cx);
15530    });
15531    cx.executor().run_until_parked();
15532    cx.condition(|editor, _| editor.context_menu_visible())
15533        .await;
15534    cx.update_editor(|editor, window, cx| {
15535        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15536        {
15537            assert_eq!(
15538                completion_menu_entries(menu),
15539                &["let"],
15540                "With no digits in the completion query, no digits should be in the word completions"
15541            );
15542        } else {
15543            panic!("expected completion menu to be open");
15544        }
15545        editor.cancel(&Cancel, window, cx);
15546    });
15547
15548    cx.set_state(indoc! {"15549        0_usize
15550        let
15551        3
15552        33.35f32
15553    "});
15554    cx.update_editor(|editor, window, cx| {
15555        editor.show_completions(&ShowCompletions, window, cx);
15556    });
15557    cx.executor().run_until_parked();
15558    cx.condition(|editor, _| editor.context_menu_visible())
15559        .await;
15560    cx.update_editor(|editor, _, _| {
15561        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15562        {
15563            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15564                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15565        } else {
15566            panic!("expected completion menu to be open");
15567        }
15568    });
15569}
15570
15571#[gpui::test]
15572async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15573    init_test(cx, |language_settings| {
15574        language_settings.defaults.completions = Some(CompletionSettingsContent {
15575            words: Some(WordsCompletionMode::Enabled),
15576            words_min_length: Some(3),
15577            lsp_insert_mode: Some(LspInsertMode::Insert),
15578            ..Default::default()
15579        });
15580    });
15581
15582    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15583    cx.set_state(indoc! {"ˇ
15584        wow
15585        wowen
15586        wowser
15587    "});
15588    cx.simulate_keystroke("w");
15589    cx.executor().run_until_parked();
15590    cx.update_editor(|editor, _, _| {
15591        if editor.context_menu.borrow_mut().is_some() {
15592            panic!(
15593                "expected completion menu to be hidden, as words completion threshold is not met"
15594            );
15595        }
15596    });
15597
15598    cx.update_editor(|editor, window, cx| {
15599        editor.show_word_completions(&ShowWordCompletions, window, cx);
15600    });
15601    cx.executor().run_until_parked();
15602    cx.update_editor(|editor, window, cx| {
15603        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15604        {
15605            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");
15606        } else {
15607            panic!("expected completion menu to be open after the word completions are called with an action");
15608        }
15609
15610        editor.cancel(&Cancel, window, cx);
15611    });
15612    cx.update_editor(|editor, _, _| {
15613        if editor.context_menu.borrow_mut().is_some() {
15614            panic!("expected completion menu to be hidden after canceling");
15615        }
15616    });
15617
15618    cx.simulate_keystroke("o");
15619    cx.executor().run_until_parked();
15620    cx.update_editor(|editor, _, _| {
15621        if editor.context_menu.borrow_mut().is_some() {
15622            panic!(
15623                "expected completion menu to be hidden, as words completion threshold is not met still"
15624            );
15625        }
15626    });
15627
15628    cx.simulate_keystroke("w");
15629    cx.executor().run_until_parked();
15630    cx.update_editor(|editor, _, _| {
15631        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15632        {
15633            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15634        } else {
15635            panic!("expected completion menu to be open after the word completions threshold is met");
15636        }
15637    });
15638}
15639
15640#[gpui::test]
15641async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15642    init_test(cx, |language_settings| {
15643        language_settings.defaults.completions = Some(CompletionSettingsContent {
15644            words: Some(WordsCompletionMode::Enabled),
15645            words_min_length: Some(0),
15646            lsp_insert_mode: Some(LspInsertMode::Insert),
15647            ..Default::default()
15648        });
15649    });
15650
15651    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15652    cx.update_editor(|editor, _, _| {
15653        editor.disable_word_completions();
15654    });
15655    cx.set_state(indoc! {"ˇ
15656        wow
15657        wowen
15658        wowser
15659    "});
15660    cx.simulate_keystroke("w");
15661    cx.executor().run_until_parked();
15662    cx.update_editor(|editor, _, _| {
15663        if editor.context_menu.borrow_mut().is_some() {
15664            panic!(
15665                "expected completion menu to be hidden, as words completion are disabled for this editor"
15666            );
15667        }
15668    });
15669
15670    cx.update_editor(|editor, window, cx| {
15671        editor.show_word_completions(&ShowWordCompletions, window, cx);
15672    });
15673    cx.executor().run_until_parked();
15674    cx.update_editor(|editor, _, _| {
15675        if editor.context_menu.borrow_mut().is_some() {
15676            panic!(
15677                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15678            );
15679        }
15680    });
15681}
15682
15683#[gpui::test]
15684async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15685    init_test(cx, |language_settings| {
15686        language_settings.defaults.completions = Some(CompletionSettingsContent {
15687            words: Some(WordsCompletionMode::Disabled),
15688            words_min_length: Some(0),
15689            lsp_insert_mode: Some(LspInsertMode::Insert),
15690            ..Default::default()
15691        });
15692    });
15693
15694    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15695    cx.update_editor(|editor, _, _| {
15696        editor.set_completion_provider(None);
15697    });
15698    cx.set_state(indoc! {"ˇ
15699        wow
15700        wowen
15701        wowser
15702    "});
15703    cx.simulate_keystroke("w");
15704    cx.executor().run_until_parked();
15705    cx.update_editor(|editor, _, _| {
15706        if editor.context_menu.borrow_mut().is_some() {
15707            panic!("expected completion menu to be hidden, as disabled in settings");
15708        }
15709    });
15710}
15711
15712fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15713    let position = || lsp::Position {
15714        line: params.text_document_position.position.line,
15715        character: params.text_document_position.position.character,
15716    };
15717    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15718        range: lsp::Range {
15719            start: position(),
15720            end: position(),
15721        },
15722        new_text: text.to_string(),
15723    }))
15724}
15725
15726#[gpui::test]
15727async fn test_multiline_completion(cx: &mut TestAppContext) {
15728    init_test(cx, |_| {});
15729
15730    let fs = FakeFs::new(cx.executor());
15731    fs.insert_tree(
15732        path!("/a"),
15733        json!({
15734            "main.ts": "a",
15735        }),
15736    )
15737    .await;
15738
15739    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15740    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15741    let typescript_language = Arc::new(Language::new(
15742        LanguageConfig {
15743            name: "TypeScript".into(),
15744            matcher: LanguageMatcher {
15745                path_suffixes: vec!["ts".to_string()],
15746                ..LanguageMatcher::default()
15747            },
15748            line_comments: vec!["// ".into()],
15749            ..LanguageConfig::default()
15750        },
15751        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15752    ));
15753    language_registry.add(typescript_language.clone());
15754    let mut fake_servers = language_registry.register_fake_lsp(
15755        "TypeScript",
15756        FakeLspAdapter {
15757            capabilities: lsp::ServerCapabilities {
15758                completion_provider: Some(lsp::CompletionOptions {
15759                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15760                    ..lsp::CompletionOptions::default()
15761                }),
15762                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15763                ..lsp::ServerCapabilities::default()
15764            },
15765            // Emulate vtsls label generation
15766            label_for_completion: Some(Box::new(|item, _| {
15767                let text = if let Some(description) = item
15768                    .label_details
15769                    .as_ref()
15770                    .and_then(|label_details| label_details.description.as_ref())
15771                {
15772                    format!("{} {}", item.label, description)
15773                } else if let Some(detail) = &item.detail {
15774                    format!("{} {}", item.label, detail)
15775                } else {
15776                    item.label.clone()
15777                };
15778                Some(language::CodeLabel::plain(text, None))
15779            })),
15780            ..FakeLspAdapter::default()
15781        },
15782    );
15783    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15784    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15785    let worktree_id = workspace
15786        .update(cx, |workspace, _window, cx| {
15787            workspace.project().update(cx, |project, cx| {
15788                project.worktrees(cx).next().unwrap().read(cx).id()
15789            })
15790        })
15791        .unwrap();
15792    let _buffer = project
15793        .update(cx, |project, cx| {
15794            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15795        })
15796        .await
15797        .unwrap();
15798    let editor = workspace
15799        .update(cx, |workspace, window, cx| {
15800            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15801        })
15802        .unwrap()
15803        .await
15804        .unwrap()
15805        .downcast::<Editor>()
15806        .unwrap();
15807    let fake_server = fake_servers.next().await.unwrap();
15808
15809    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15810    let multiline_label_2 = "a\nb\nc\n";
15811    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15812    let multiline_description = "d\ne\nf\n";
15813    let multiline_detail_2 = "g\nh\ni\n";
15814
15815    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15816        move |params, _| async move {
15817            Ok(Some(lsp::CompletionResponse::Array(vec![
15818                lsp::CompletionItem {
15819                    label: multiline_label.to_string(),
15820                    text_edit: gen_text_edit(&params, "new_text_1"),
15821                    ..lsp::CompletionItem::default()
15822                },
15823                lsp::CompletionItem {
15824                    label: "single line label 1".to_string(),
15825                    detail: Some(multiline_detail.to_string()),
15826                    text_edit: gen_text_edit(&params, "new_text_2"),
15827                    ..lsp::CompletionItem::default()
15828                },
15829                lsp::CompletionItem {
15830                    label: "single line label 2".to_string(),
15831                    label_details: Some(lsp::CompletionItemLabelDetails {
15832                        description: Some(multiline_description.to_string()),
15833                        detail: None,
15834                    }),
15835                    text_edit: gen_text_edit(&params, "new_text_2"),
15836                    ..lsp::CompletionItem::default()
15837                },
15838                lsp::CompletionItem {
15839                    label: multiline_label_2.to_string(),
15840                    detail: Some(multiline_detail_2.to_string()),
15841                    text_edit: gen_text_edit(&params, "new_text_3"),
15842                    ..lsp::CompletionItem::default()
15843                },
15844                lsp::CompletionItem {
15845                    label: "Label with many     spaces and \t but without newlines".to_string(),
15846                    detail: Some(
15847                        "Details with many     spaces and \t but without newlines".to_string(),
15848                    ),
15849                    text_edit: gen_text_edit(&params, "new_text_4"),
15850                    ..lsp::CompletionItem::default()
15851                },
15852            ])))
15853        },
15854    );
15855
15856    editor.update_in(cx, |editor, window, cx| {
15857        cx.focus_self(window);
15858        editor.move_to_end(&MoveToEnd, window, cx);
15859        editor.handle_input(".", window, cx);
15860    });
15861    cx.run_until_parked();
15862    completion_handle.next().await.unwrap();
15863
15864    editor.update(cx, |editor, _| {
15865        assert!(editor.context_menu_visible());
15866        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15867        {
15868            let completion_labels = menu
15869                .completions
15870                .borrow()
15871                .iter()
15872                .map(|c| c.label.text.clone())
15873                .collect::<Vec<_>>();
15874            assert_eq!(
15875                completion_labels,
15876                &[
15877                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15878                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15879                    "single line label 2 d e f ",
15880                    "a b c g h i ",
15881                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15882                ],
15883                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15884            );
15885
15886            for completion in menu
15887                .completions
15888                .borrow()
15889                .iter() {
15890                    assert_eq!(
15891                        completion.label.filter_range,
15892                        0..completion.label.text.len(),
15893                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15894                    );
15895                }
15896        } else {
15897            panic!("expected completion menu to be open");
15898        }
15899    });
15900}
15901
15902#[gpui::test]
15903async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15904    init_test(cx, |_| {});
15905    let mut cx = EditorLspTestContext::new_rust(
15906        lsp::ServerCapabilities {
15907            completion_provider: Some(lsp::CompletionOptions {
15908                trigger_characters: Some(vec![".".to_string()]),
15909                ..Default::default()
15910            }),
15911            ..Default::default()
15912        },
15913        cx,
15914    )
15915    .await;
15916    cx.lsp
15917        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15918            Ok(Some(lsp::CompletionResponse::Array(vec![
15919                lsp::CompletionItem {
15920                    label: "first".into(),
15921                    ..Default::default()
15922                },
15923                lsp::CompletionItem {
15924                    label: "last".into(),
15925                    ..Default::default()
15926                },
15927            ])))
15928        });
15929    cx.set_state("variableˇ");
15930    cx.simulate_keystroke(".");
15931    cx.executor().run_until_parked();
15932
15933    cx.update_editor(|editor, _, _| {
15934        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15935        {
15936            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15937        } else {
15938            panic!("expected completion menu to be open");
15939        }
15940    });
15941
15942    cx.update_editor(|editor, window, cx| {
15943        editor.move_page_down(&MovePageDown::default(), window, cx);
15944        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15945        {
15946            assert!(
15947                menu.selected_item == 1,
15948                "expected PageDown to select the last item from the context menu"
15949            );
15950        } else {
15951            panic!("expected completion menu to stay open after PageDown");
15952        }
15953    });
15954
15955    cx.update_editor(|editor, window, cx| {
15956        editor.move_page_up(&MovePageUp::default(), window, cx);
15957        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15958        {
15959            assert!(
15960                menu.selected_item == 0,
15961                "expected PageUp to select the first item from the context menu"
15962            );
15963        } else {
15964            panic!("expected completion menu to stay open after PageUp");
15965        }
15966    });
15967}
15968
15969#[gpui::test]
15970async fn test_as_is_completions(cx: &mut TestAppContext) {
15971    init_test(cx, |_| {});
15972    let mut cx = EditorLspTestContext::new_rust(
15973        lsp::ServerCapabilities {
15974            completion_provider: Some(lsp::CompletionOptions {
15975                ..Default::default()
15976            }),
15977            ..Default::default()
15978        },
15979        cx,
15980    )
15981    .await;
15982    cx.lsp
15983        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15984            Ok(Some(lsp::CompletionResponse::Array(vec![
15985                lsp::CompletionItem {
15986                    label: "unsafe".into(),
15987                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15988                        range: lsp::Range {
15989                            start: lsp::Position {
15990                                line: 1,
15991                                character: 2,
15992                            },
15993                            end: lsp::Position {
15994                                line: 1,
15995                                character: 3,
15996                            },
15997                        },
15998                        new_text: "unsafe".to_string(),
15999                    })),
16000                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16001                    ..Default::default()
16002                },
16003            ])))
16004        });
16005    cx.set_state("fn a() {}\n");
16006    cx.executor().run_until_parked();
16007    cx.update_editor(|editor, window, cx| {
16008        editor.trigger_completion_on_input("n", true, window, cx)
16009    });
16010    cx.executor().run_until_parked();
16011
16012    cx.update_editor(|editor, window, cx| {
16013        editor.confirm_completion(&Default::default(), window, cx)
16014    });
16015    cx.executor().run_until_parked();
16016    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16017}
16018
16019#[gpui::test]
16020async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16021    init_test(cx, |_| {});
16022    let language =
16023        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16024    let mut cx = EditorLspTestContext::new(
16025        language,
16026        lsp::ServerCapabilities {
16027            completion_provider: Some(lsp::CompletionOptions {
16028                ..lsp::CompletionOptions::default()
16029            }),
16030            ..lsp::ServerCapabilities::default()
16031        },
16032        cx,
16033    )
16034    .await;
16035
16036    cx.set_state(
16037        "#ifndef BAR_H
16038#define BAR_H
16039
16040#include <stdbool.h>
16041
16042int fn_branch(bool do_branch1, bool do_branch2);
16043
16044#endif // BAR_H
16045ˇ",
16046    );
16047    cx.executor().run_until_parked();
16048    cx.update_editor(|editor, window, cx| {
16049        editor.handle_input("#", window, cx);
16050    });
16051    cx.executor().run_until_parked();
16052    cx.update_editor(|editor, window, cx| {
16053        editor.handle_input("i", window, cx);
16054    });
16055    cx.executor().run_until_parked();
16056    cx.update_editor(|editor, window, cx| {
16057        editor.handle_input("n", window, cx);
16058    });
16059    cx.executor().run_until_parked();
16060    cx.assert_editor_state(
16061        "#ifndef BAR_H
16062#define BAR_H
16063
16064#include <stdbool.h>
16065
16066int fn_branch(bool do_branch1, bool do_branch2);
16067
16068#endif // BAR_H
16069#inˇ",
16070    );
16071
16072    cx.lsp
16073        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16074            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16075                is_incomplete: false,
16076                item_defaults: None,
16077                items: vec![lsp::CompletionItem {
16078                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16079                    label_details: Some(lsp::CompletionItemLabelDetails {
16080                        detail: Some("header".to_string()),
16081                        description: None,
16082                    }),
16083                    label: " include".to_string(),
16084                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16085                        range: lsp::Range {
16086                            start: lsp::Position {
16087                                line: 8,
16088                                character: 1,
16089                            },
16090                            end: lsp::Position {
16091                                line: 8,
16092                                character: 1,
16093                            },
16094                        },
16095                        new_text: "include \"$0\"".to_string(),
16096                    })),
16097                    sort_text: Some("40b67681include".to_string()),
16098                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16099                    filter_text: Some("include".to_string()),
16100                    insert_text: Some("include \"$0\"".to_string()),
16101                    ..lsp::CompletionItem::default()
16102                }],
16103            })))
16104        });
16105    cx.update_editor(|editor, window, cx| {
16106        editor.show_completions(&ShowCompletions, window, cx);
16107    });
16108    cx.executor().run_until_parked();
16109    cx.update_editor(|editor, window, cx| {
16110        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16111    });
16112    cx.executor().run_until_parked();
16113    cx.assert_editor_state(
16114        "#ifndef BAR_H
16115#define BAR_H
16116
16117#include <stdbool.h>
16118
16119int fn_branch(bool do_branch1, bool do_branch2);
16120
16121#endif // BAR_H
16122#include \"ˇ\"",
16123    );
16124
16125    cx.lsp
16126        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16127            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16128                is_incomplete: true,
16129                item_defaults: None,
16130                items: vec![lsp::CompletionItem {
16131                    kind: Some(lsp::CompletionItemKind::FILE),
16132                    label: "AGL/".to_string(),
16133                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16134                        range: lsp::Range {
16135                            start: lsp::Position {
16136                                line: 8,
16137                                character: 10,
16138                            },
16139                            end: lsp::Position {
16140                                line: 8,
16141                                character: 11,
16142                            },
16143                        },
16144                        new_text: "AGL/".to_string(),
16145                    })),
16146                    sort_text: Some("40b67681AGL/".to_string()),
16147                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16148                    filter_text: Some("AGL/".to_string()),
16149                    insert_text: Some("AGL/".to_string()),
16150                    ..lsp::CompletionItem::default()
16151                }],
16152            })))
16153        });
16154    cx.update_editor(|editor, window, cx| {
16155        editor.show_completions(&ShowCompletions, window, cx);
16156    });
16157    cx.executor().run_until_parked();
16158    cx.update_editor(|editor, window, cx| {
16159        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16160    });
16161    cx.executor().run_until_parked();
16162    cx.assert_editor_state(
16163        r##"#ifndef BAR_H
16164#define BAR_H
16165
16166#include <stdbool.h>
16167
16168int fn_branch(bool do_branch1, bool do_branch2);
16169
16170#endif // BAR_H
16171#include "AGL/ˇ"##,
16172    );
16173
16174    cx.update_editor(|editor, window, cx| {
16175        editor.handle_input("\"", window, cx);
16176    });
16177    cx.executor().run_until_parked();
16178    cx.assert_editor_state(
16179        r##"#ifndef BAR_H
16180#define BAR_H
16181
16182#include <stdbool.h>
16183
16184int fn_branch(bool do_branch1, bool do_branch2);
16185
16186#endif // BAR_H
16187#include "AGL/"ˇ"##,
16188    );
16189}
16190
16191#[gpui::test]
16192async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16193    init_test(cx, |_| {});
16194
16195    let mut cx = EditorLspTestContext::new_rust(
16196        lsp::ServerCapabilities {
16197            completion_provider: Some(lsp::CompletionOptions {
16198                trigger_characters: Some(vec![".".to_string()]),
16199                resolve_provider: Some(true),
16200                ..Default::default()
16201            }),
16202            ..Default::default()
16203        },
16204        cx,
16205    )
16206    .await;
16207
16208    cx.set_state("fn main() { let a = 2ˇ; }");
16209    cx.simulate_keystroke(".");
16210    let completion_item = lsp::CompletionItem {
16211        label: "Some".into(),
16212        kind: Some(lsp::CompletionItemKind::SNIPPET),
16213        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16214        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16215            kind: lsp::MarkupKind::Markdown,
16216            value: "```rust\nSome(2)\n```".to_string(),
16217        })),
16218        deprecated: Some(false),
16219        sort_text: Some("Some".to_string()),
16220        filter_text: Some("Some".to_string()),
16221        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16222        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16223            range: lsp::Range {
16224                start: lsp::Position {
16225                    line: 0,
16226                    character: 22,
16227                },
16228                end: lsp::Position {
16229                    line: 0,
16230                    character: 22,
16231                },
16232            },
16233            new_text: "Some(2)".to_string(),
16234        })),
16235        additional_text_edits: Some(vec![lsp::TextEdit {
16236            range: lsp::Range {
16237                start: lsp::Position {
16238                    line: 0,
16239                    character: 20,
16240                },
16241                end: lsp::Position {
16242                    line: 0,
16243                    character: 22,
16244                },
16245            },
16246            new_text: "".to_string(),
16247        }]),
16248        ..Default::default()
16249    };
16250
16251    let closure_completion_item = completion_item.clone();
16252    let counter = Arc::new(AtomicUsize::new(0));
16253    let counter_clone = counter.clone();
16254    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16255        let task_completion_item = closure_completion_item.clone();
16256        counter_clone.fetch_add(1, atomic::Ordering::Release);
16257        async move {
16258            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16259                is_incomplete: true,
16260                item_defaults: None,
16261                items: vec![task_completion_item],
16262            })))
16263        }
16264    });
16265
16266    cx.condition(|editor, _| editor.context_menu_visible())
16267        .await;
16268    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16269    assert!(request.next().await.is_some());
16270    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16271
16272    cx.simulate_keystrokes("S o m");
16273    cx.condition(|editor, _| editor.context_menu_visible())
16274        .await;
16275    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16276    assert!(request.next().await.is_some());
16277    assert!(request.next().await.is_some());
16278    assert!(request.next().await.is_some());
16279    request.close();
16280    assert!(request.next().await.is_none());
16281    assert_eq!(
16282        counter.load(atomic::Ordering::Acquire),
16283        4,
16284        "With the completions menu open, only one LSP request should happen per input"
16285    );
16286}
16287
16288#[gpui::test]
16289async fn test_toggle_comment(cx: &mut TestAppContext) {
16290    init_test(cx, |_| {});
16291    let mut cx = EditorTestContext::new(cx).await;
16292    let language = Arc::new(Language::new(
16293        LanguageConfig {
16294            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16295            ..Default::default()
16296        },
16297        Some(tree_sitter_rust::LANGUAGE.into()),
16298    ));
16299    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16300
16301    // If multiple selections intersect a line, the line is only toggled once.
16302    cx.set_state(indoc! {"
16303        fn a() {
16304            «//b();
16305            ˇ»// «c();
16306            //ˇ»  d();
16307        }
16308    "});
16309
16310    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16311
16312    cx.assert_editor_state(indoc! {"
16313        fn a() {
16314            «b();
16315            ˇ»«c();
16316            ˇ» d();
16317        }
16318    "});
16319
16320    // The comment prefix is inserted at the same column for every line in a
16321    // selection.
16322    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16323
16324    cx.assert_editor_state(indoc! {"
16325        fn a() {
16326            // «b();
16327            ˇ»// «c();
16328            ˇ» // d();
16329        }
16330    "});
16331
16332    // If a selection ends at the beginning of a line, that line is not toggled.
16333    cx.set_selections_state(indoc! {"
16334        fn a() {
16335            // b();
16336            «// c();
16337        ˇ»     // d();
16338        }
16339    "});
16340
16341    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16342
16343    cx.assert_editor_state(indoc! {"
16344        fn a() {
16345            // b();
16346            «c();
16347        ˇ»     // d();
16348        }
16349    "});
16350
16351    // If a selection span a single line and is empty, the line is toggled.
16352    cx.set_state(indoc! {"
16353        fn a() {
16354            a();
16355            b();
16356        ˇ
16357        }
16358    "});
16359
16360    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16361
16362    cx.assert_editor_state(indoc! {"
16363        fn a() {
16364            a();
16365            b();
16366        //•ˇ
16367        }
16368    "});
16369
16370    // If a selection span multiple lines, empty lines are not toggled.
16371    cx.set_state(indoc! {"
16372        fn a() {
16373            «a();
16374
16375            c();ˇ»
16376        }
16377    "});
16378
16379    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16380
16381    cx.assert_editor_state(indoc! {"
16382        fn a() {
16383            // «a();
16384
16385            // c();ˇ»
16386        }
16387    "});
16388
16389    // If a selection includes multiple comment prefixes, all lines are uncommented.
16390    cx.set_state(indoc! {"
16391        fn a() {
16392            «// a();
16393            /// b();
16394            //! c();ˇ»
16395        }
16396    "});
16397
16398    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16399
16400    cx.assert_editor_state(indoc! {"
16401        fn a() {
16402            «a();
16403            b();
16404            c();ˇ»
16405        }
16406    "});
16407}
16408
16409#[gpui::test]
16410async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16411    init_test(cx, |_| {});
16412    let mut cx = EditorTestContext::new(cx).await;
16413    let language = Arc::new(Language::new(
16414        LanguageConfig {
16415            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16416            ..Default::default()
16417        },
16418        Some(tree_sitter_rust::LANGUAGE.into()),
16419    ));
16420    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16421
16422    let toggle_comments = &ToggleComments {
16423        advance_downwards: false,
16424        ignore_indent: true,
16425    };
16426
16427    // If multiple selections intersect a line, the line is only toggled once.
16428    cx.set_state(indoc! {"
16429        fn a() {
16430        //    «b();
16431        //    c();
16432        //    ˇ» d();
16433        }
16434    "});
16435
16436    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16437
16438    cx.assert_editor_state(indoc! {"
16439        fn a() {
16440            «b();
16441            c();
16442            ˇ» d();
16443        }
16444    "});
16445
16446    // The comment prefix is inserted at the beginning of each line
16447    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16448
16449    cx.assert_editor_state(indoc! {"
16450        fn a() {
16451        //    «b();
16452        //    c();
16453        //    ˇ» d();
16454        }
16455    "});
16456
16457    // If a selection ends at the beginning of a line, that line is not toggled.
16458    cx.set_selections_state(indoc! {"
16459        fn a() {
16460        //    b();
16461        //    «c();
16462        ˇ»//     d();
16463        }
16464    "});
16465
16466    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16467
16468    cx.assert_editor_state(indoc! {"
16469        fn a() {
16470        //    b();
16471            «c();
16472        ˇ»//     d();
16473        }
16474    "});
16475
16476    // If a selection span a single line and is empty, the line is toggled.
16477    cx.set_state(indoc! {"
16478        fn a() {
16479            a();
16480            b();
16481        ˇ
16482        }
16483    "});
16484
16485    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16486
16487    cx.assert_editor_state(indoc! {"
16488        fn a() {
16489            a();
16490            b();
16491        //ˇ
16492        }
16493    "});
16494
16495    // If a selection span multiple lines, empty lines are not toggled.
16496    cx.set_state(indoc! {"
16497        fn a() {
16498            «a();
16499
16500            c();ˇ»
16501        }
16502    "});
16503
16504    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16505
16506    cx.assert_editor_state(indoc! {"
16507        fn a() {
16508        //    «a();
16509
16510        //    c();ˇ»
16511        }
16512    "});
16513
16514    // If a selection includes multiple comment prefixes, all lines are uncommented.
16515    cx.set_state(indoc! {"
16516        fn a() {
16517        //    «a();
16518        ///    b();
16519        //!    c();ˇ»
16520        }
16521    "});
16522
16523    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16524
16525    cx.assert_editor_state(indoc! {"
16526        fn a() {
16527            «a();
16528            b();
16529            c();ˇ»
16530        }
16531    "});
16532}
16533
16534#[gpui::test]
16535async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16536    init_test(cx, |_| {});
16537
16538    let language = Arc::new(Language::new(
16539        LanguageConfig {
16540            line_comments: vec!["// ".into()],
16541            ..Default::default()
16542        },
16543        Some(tree_sitter_rust::LANGUAGE.into()),
16544    ));
16545
16546    let mut cx = EditorTestContext::new(cx).await;
16547
16548    cx.language_registry().add(language.clone());
16549    cx.update_buffer(|buffer, cx| {
16550        buffer.set_language(Some(language), cx);
16551    });
16552
16553    let toggle_comments = &ToggleComments {
16554        advance_downwards: true,
16555        ignore_indent: false,
16556    };
16557
16558    // Single cursor on one line -> advance
16559    // Cursor moves horizontally 3 characters as well on non-blank line
16560    cx.set_state(indoc!(
16561        "fn a() {
16562             ˇdog();
16563             cat();
16564        }"
16565    ));
16566    cx.update_editor(|editor, window, cx| {
16567        editor.toggle_comments(toggle_comments, window, cx);
16568    });
16569    cx.assert_editor_state(indoc!(
16570        "fn a() {
16571             // dog();
16572             catˇ();
16573        }"
16574    ));
16575
16576    // Single selection on one line -> don't advance
16577    cx.set_state(indoc!(
16578        "fn a() {
16579             «dog()ˇ»;
16580             cat();
16581        }"
16582    ));
16583    cx.update_editor(|editor, window, cx| {
16584        editor.toggle_comments(toggle_comments, window, cx);
16585    });
16586    cx.assert_editor_state(indoc!(
16587        "fn a() {
16588             // «dog()ˇ»;
16589             cat();
16590        }"
16591    ));
16592
16593    // Multiple cursors on one line -> advance
16594    cx.set_state(indoc!(
16595        "fn a() {
16596             ˇdˇog();
16597             cat();
16598        }"
16599    ));
16600    cx.update_editor(|editor, window, cx| {
16601        editor.toggle_comments(toggle_comments, window, cx);
16602    });
16603    cx.assert_editor_state(indoc!(
16604        "fn a() {
16605             // dog();
16606             catˇ(ˇ);
16607        }"
16608    ));
16609
16610    // Multiple cursors on one line, with selection -> don't advance
16611    cx.set_state(indoc!(
16612        "fn a() {
16613             ˇdˇog«()ˇ»;
16614             cat();
16615        }"
16616    ));
16617    cx.update_editor(|editor, window, cx| {
16618        editor.toggle_comments(toggle_comments, window, cx);
16619    });
16620    cx.assert_editor_state(indoc!(
16621        "fn a() {
16622             // ˇdˇog«()ˇ»;
16623             cat();
16624        }"
16625    ));
16626
16627    // Single cursor on one line -> advance
16628    // Cursor moves to column 0 on blank line
16629    cx.set_state(indoc!(
16630        "fn a() {
16631             ˇdog();
16632
16633             cat();
16634        }"
16635    ));
16636    cx.update_editor(|editor, window, cx| {
16637        editor.toggle_comments(toggle_comments, window, cx);
16638    });
16639    cx.assert_editor_state(indoc!(
16640        "fn a() {
16641             // dog();
16642        ˇ
16643             cat();
16644        }"
16645    ));
16646
16647    // Single cursor on one line -> advance
16648    // Cursor starts and ends at column 0
16649    cx.set_state(indoc!(
16650        "fn a() {
16651         ˇ    dog();
16652             cat();
16653        }"
16654    ));
16655    cx.update_editor(|editor, window, cx| {
16656        editor.toggle_comments(toggle_comments, window, cx);
16657    });
16658    cx.assert_editor_state(indoc!(
16659        "fn a() {
16660             // dog();
16661         ˇ    cat();
16662        }"
16663    ));
16664}
16665
16666#[gpui::test]
16667async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16668    init_test(cx, |_| {});
16669
16670    let mut cx = EditorTestContext::new(cx).await;
16671
16672    let html_language = Arc::new(
16673        Language::new(
16674            LanguageConfig {
16675                name: "HTML".into(),
16676                block_comment: Some(BlockCommentConfig {
16677                    start: "<!-- ".into(),
16678                    prefix: "".into(),
16679                    end: " -->".into(),
16680                    tab_size: 0,
16681                }),
16682                ..Default::default()
16683            },
16684            Some(tree_sitter_html::LANGUAGE.into()),
16685        )
16686        .with_injection_query(
16687            r#"
16688            (script_element
16689                (raw_text) @injection.content
16690                (#set! injection.language "javascript"))
16691            "#,
16692        )
16693        .unwrap(),
16694    );
16695
16696    let javascript_language = Arc::new(Language::new(
16697        LanguageConfig {
16698            name: "JavaScript".into(),
16699            line_comments: vec!["// ".into()],
16700            ..Default::default()
16701        },
16702        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16703    ));
16704
16705    cx.language_registry().add(html_language.clone());
16706    cx.language_registry().add(javascript_language);
16707    cx.update_buffer(|buffer, cx| {
16708        buffer.set_language(Some(html_language), cx);
16709    });
16710
16711    // Toggle comments for empty selections
16712    cx.set_state(
16713        &r#"
16714            <p>A</p>ˇ
16715            <p>B</p>ˇ
16716            <p>C</p>ˇ
16717        "#
16718        .unindent(),
16719    );
16720    cx.update_editor(|editor, window, cx| {
16721        editor.toggle_comments(&ToggleComments::default(), window, cx)
16722    });
16723    cx.assert_editor_state(
16724        &r#"
16725            <!-- <p>A</p>ˇ -->
16726            <!-- <p>B</p>ˇ -->
16727            <!-- <p>C</p>ˇ -->
16728        "#
16729        .unindent(),
16730    );
16731    cx.update_editor(|editor, window, cx| {
16732        editor.toggle_comments(&ToggleComments::default(), window, cx)
16733    });
16734    cx.assert_editor_state(
16735        &r#"
16736            <p>A</p>ˇ
16737            <p>B</p>ˇ
16738            <p>C</p>ˇ
16739        "#
16740        .unindent(),
16741    );
16742
16743    // Toggle comments for mixture of empty and non-empty selections, where
16744    // multiple selections occupy a given line.
16745    cx.set_state(
16746        &r#"
16747            <p>A«</p>
16748            <p>ˇ»B</p>ˇ
16749            <p>C«</p>
16750            <p>ˇ»D</p>ˇ
16751        "#
16752        .unindent(),
16753    );
16754
16755    cx.update_editor(|editor, window, cx| {
16756        editor.toggle_comments(&ToggleComments::default(), window, cx)
16757    });
16758    cx.assert_editor_state(
16759        &r#"
16760            <!-- <p>A«</p>
16761            <p>ˇ»B</p>ˇ -->
16762            <!-- <p>C«</p>
16763            <p>ˇ»D</p>ˇ -->
16764        "#
16765        .unindent(),
16766    );
16767    cx.update_editor(|editor, window, cx| {
16768        editor.toggle_comments(&ToggleComments::default(), window, cx)
16769    });
16770    cx.assert_editor_state(
16771        &r#"
16772            <p>A«</p>
16773            <p>ˇ»B</p>ˇ
16774            <p>C«</p>
16775            <p>ˇ»D</p>ˇ
16776        "#
16777        .unindent(),
16778    );
16779
16780    // Toggle comments when different languages are active for different
16781    // selections.
16782    cx.set_state(
16783        &r#"
16784            ˇ<script>
16785                ˇvar x = new Y();
16786            ˇ</script>
16787        "#
16788        .unindent(),
16789    );
16790    cx.executor().run_until_parked();
16791    cx.update_editor(|editor, window, cx| {
16792        editor.toggle_comments(&ToggleComments::default(), window, cx)
16793    });
16794    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16795    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16796    cx.assert_editor_state(
16797        &r#"
16798            <!-- ˇ<script> -->
16799                // ˇvar x = new Y();
16800            <!-- ˇ</script> -->
16801        "#
16802        .unindent(),
16803    );
16804}
16805
16806#[gpui::test]
16807fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16808    init_test(cx, |_| {});
16809
16810    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16811    let multibuffer = cx.new(|cx| {
16812        let mut multibuffer = MultiBuffer::new(ReadWrite);
16813        multibuffer.push_excerpts(
16814            buffer.clone(),
16815            [
16816                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16817                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16818            ],
16819            cx,
16820        );
16821        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16822        multibuffer
16823    });
16824
16825    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16826    editor.update_in(cx, |editor, window, cx| {
16827        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16829            s.select_ranges([
16830                Point::new(0, 0)..Point::new(0, 0),
16831                Point::new(1, 0)..Point::new(1, 0),
16832            ])
16833        });
16834
16835        editor.handle_input("X", window, cx);
16836        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16837        assert_eq!(
16838            editor.selections.ranges(&editor.display_snapshot(cx)),
16839            [
16840                Point::new(0, 1)..Point::new(0, 1),
16841                Point::new(1, 1)..Point::new(1, 1),
16842            ]
16843        );
16844
16845        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16846        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16847            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16848        });
16849        editor.backspace(&Default::default(), window, cx);
16850        assert_eq!(editor.text(cx), "Xa\nbbb");
16851        assert_eq!(
16852            editor.selections.ranges(&editor.display_snapshot(cx)),
16853            [Point::new(1, 0)..Point::new(1, 0)]
16854        );
16855
16856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16857            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16858        });
16859        editor.backspace(&Default::default(), window, cx);
16860        assert_eq!(editor.text(cx), "X\nbb");
16861        assert_eq!(
16862            editor.selections.ranges(&editor.display_snapshot(cx)),
16863            [Point::new(0, 1)..Point::new(0, 1)]
16864        );
16865    });
16866}
16867
16868#[gpui::test]
16869fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16870    init_test(cx, |_| {});
16871
16872    let markers = vec![('[', ']').into(), ('(', ')').into()];
16873    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16874        indoc! {"
16875            [aaaa
16876            (bbbb]
16877            cccc)",
16878        },
16879        markers.clone(),
16880    );
16881    let excerpt_ranges = markers.into_iter().map(|marker| {
16882        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16883        ExcerptRange::new(context)
16884    });
16885    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16886    let multibuffer = cx.new(|cx| {
16887        let mut multibuffer = MultiBuffer::new(ReadWrite);
16888        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16889        multibuffer
16890    });
16891
16892    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16893    editor.update_in(cx, |editor, window, cx| {
16894        let (expected_text, selection_ranges) = marked_text_ranges(
16895            indoc! {"
16896                aaaa
16897                bˇbbb
16898                bˇbbˇb
16899                cccc"
16900            },
16901            true,
16902        );
16903        assert_eq!(editor.text(cx), expected_text);
16904        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16905            s.select_ranges(
16906                selection_ranges
16907                    .iter()
16908                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16909            )
16910        });
16911
16912        editor.handle_input("X", window, cx);
16913
16914        let (expected_text, expected_selections) = marked_text_ranges(
16915            indoc! {"
16916                aaaa
16917                bXˇbbXb
16918                bXˇbbXˇb
16919                cccc"
16920            },
16921            false,
16922        );
16923        assert_eq!(editor.text(cx), expected_text);
16924        assert_eq!(
16925            editor.selections.ranges(&editor.display_snapshot(cx)),
16926            expected_selections
16927                .iter()
16928                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16929                .collect::<Vec<_>>()
16930        );
16931
16932        editor.newline(&Newline, window, cx);
16933        let (expected_text, expected_selections) = marked_text_ranges(
16934            indoc! {"
16935                aaaa
16936                bX
16937                ˇbbX
16938                b
16939                bX
16940                ˇbbX
16941                ˇb
16942                cccc"
16943            },
16944            false,
16945        );
16946        assert_eq!(editor.text(cx), expected_text);
16947        assert_eq!(
16948            editor.selections.ranges(&editor.display_snapshot(cx)),
16949            expected_selections
16950                .iter()
16951                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16952                .collect::<Vec<_>>()
16953        );
16954    });
16955}
16956
16957#[gpui::test]
16958fn test_refresh_selections(cx: &mut TestAppContext) {
16959    init_test(cx, |_| {});
16960
16961    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16962    let mut excerpt1_id = None;
16963    let multibuffer = cx.new(|cx| {
16964        let mut multibuffer = MultiBuffer::new(ReadWrite);
16965        excerpt1_id = multibuffer
16966            .push_excerpts(
16967                buffer.clone(),
16968                [
16969                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16970                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16971                ],
16972                cx,
16973            )
16974            .into_iter()
16975            .next();
16976        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16977        multibuffer
16978    });
16979
16980    let editor = cx.add_window(|window, cx| {
16981        let mut editor = build_editor(multibuffer.clone(), window, cx);
16982        let snapshot = editor.snapshot(window, cx);
16983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16984            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16985        });
16986        editor.begin_selection(
16987            Point::new(2, 1).to_display_point(&snapshot),
16988            true,
16989            1,
16990            window,
16991            cx,
16992        );
16993        assert_eq!(
16994            editor.selections.ranges(&editor.display_snapshot(cx)),
16995            [
16996                Point::new(1, 3)..Point::new(1, 3),
16997                Point::new(2, 1)..Point::new(2, 1),
16998            ]
16999        );
17000        editor
17001    });
17002
17003    // Refreshing selections is a no-op when excerpts haven't changed.
17004    _ = editor.update(cx, |editor, window, cx| {
17005        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17006        assert_eq!(
17007            editor.selections.ranges(&editor.display_snapshot(cx)),
17008            [
17009                Point::new(1, 3)..Point::new(1, 3),
17010                Point::new(2, 1)..Point::new(2, 1),
17011            ]
17012        );
17013    });
17014
17015    multibuffer.update(cx, |multibuffer, cx| {
17016        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17017    });
17018    _ = editor.update(cx, |editor, window, cx| {
17019        // Removing an excerpt causes the first selection to become degenerate.
17020        assert_eq!(
17021            editor.selections.ranges(&editor.display_snapshot(cx)),
17022            [
17023                Point::new(0, 0)..Point::new(0, 0),
17024                Point::new(0, 1)..Point::new(0, 1)
17025            ]
17026        );
17027
17028        // Refreshing selections will relocate the first selection to the original buffer
17029        // location.
17030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17031        assert_eq!(
17032            editor.selections.ranges(&editor.display_snapshot(cx)),
17033            [
17034                Point::new(0, 1)..Point::new(0, 1),
17035                Point::new(0, 3)..Point::new(0, 3)
17036            ]
17037        );
17038        assert!(editor.selections.pending_anchor().is_some());
17039    });
17040}
17041
17042#[gpui::test]
17043fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17044    init_test(cx, |_| {});
17045
17046    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17047    let mut excerpt1_id = None;
17048    let multibuffer = cx.new(|cx| {
17049        let mut multibuffer = MultiBuffer::new(ReadWrite);
17050        excerpt1_id = multibuffer
17051            .push_excerpts(
17052                buffer.clone(),
17053                [
17054                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17055                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17056                ],
17057                cx,
17058            )
17059            .into_iter()
17060            .next();
17061        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17062        multibuffer
17063    });
17064
17065    let editor = cx.add_window(|window, cx| {
17066        let mut editor = build_editor(multibuffer.clone(), window, cx);
17067        let snapshot = editor.snapshot(window, cx);
17068        editor.begin_selection(
17069            Point::new(1, 3).to_display_point(&snapshot),
17070            false,
17071            1,
17072            window,
17073            cx,
17074        );
17075        assert_eq!(
17076            editor.selections.ranges(&editor.display_snapshot(cx)),
17077            [Point::new(1, 3)..Point::new(1, 3)]
17078        );
17079        editor
17080    });
17081
17082    multibuffer.update(cx, |multibuffer, cx| {
17083        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17084    });
17085    _ = editor.update(cx, |editor, window, cx| {
17086        assert_eq!(
17087            editor.selections.ranges(&editor.display_snapshot(cx)),
17088            [Point::new(0, 0)..Point::new(0, 0)]
17089        );
17090
17091        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17093        assert_eq!(
17094            editor.selections.ranges(&editor.display_snapshot(cx)),
17095            [Point::new(0, 3)..Point::new(0, 3)]
17096        );
17097        assert!(editor.selections.pending_anchor().is_some());
17098    });
17099}
17100
17101#[gpui::test]
17102async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17103    init_test(cx, |_| {});
17104
17105    let language = Arc::new(
17106        Language::new(
17107            LanguageConfig {
17108                brackets: BracketPairConfig {
17109                    pairs: vec![
17110                        BracketPair {
17111                            start: "{".to_string(),
17112                            end: "}".to_string(),
17113                            close: true,
17114                            surround: true,
17115                            newline: true,
17116                        },
17117                        BracketPair {
17118                            start: "/* ".to_string(),
17119                            end: " */".to_string(),
17120                            close: true,
17121                            surround: true,
17122                            newline: true,
17123                        },
17124                    ],
17125                    ..Default::default()
17126                },
17127                ..Default::default()
17128            },
17129            Some(tree_sitter_rust::LANGUAGE.into()),
17130        )
17131        .with_indents_query("")
17132        .unwrap(),
17133    );
17134
17135    let text = concat!(
17136        "{   }\n",     //
17137        "  x\n",       //
17138        "  /*   */\n", //
17139        "x\n",         //
17140        "{{} }\n",     //
17141    );
17142
17143    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17146    editor
17147        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17148        .await;
17149
17150    editor.update_in(cx, |editor, window, cx| {
17151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17152            s.select_display_ranges([
17153                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17154                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17155                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17156            ])
17157        });
17158        editor.newline(&Newline, window, cx);
17159
17160        assert_eq!(
17161            editor.buffer().read(cx).read(cx).text(),
17162            concat!(
17163                "{ \n",    // Suppress rustfmt
17164                "\n",      //
17165                "}\n",     //
17166                "  x\n",   //
17167                "  /* \n", //
17168                "  \n",    //
17169                "  */\n",  //
17170                "x\n",     //
17171                "{{} \n",  //
17172                "}\n",     //
17173            )
17174        );
17175    });
17176}
17177
17178#[gpui::test]
17179fn test_highlighted_ranges(cx: &mut TestAppContext) {
17180    init_test(cx, |_| {});
17181
17182    let editor = cx.add_window(|window, cx| {
17183        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17184        build_editor(buffer, window, cx)
17185    });
17186
17187    _ = editor.update(cx, |editor, window, cx| {
17188        struct Type1;
17189        struct Type2;
17190
17191        let buffer = editor.buffer.read(cx).snapshot(cx);
17192
17193        let anchor_range =
17194            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17195
17196        editor.highlight_background::<Type1>(
17197            &[
17198                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17199                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17200                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17201                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17202            ],
17203            |_, _| Hsla::red(),
17204            cx,
17205        );
17206        editor.highlight_background::<Type2>(
17207            &[
17208                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17209                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17210                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17211                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17212            ],
17213            |_, _| Hsla::green(),
17214            cx,
17215        );
17216
17217        let snapshot = editor.snapshot(window, cx);
17218        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17219            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17220            &snapshot,
17221            cx.theme(),
17222        );
17223        assert_eq!(
17224            highlighted_ranges,
17225            &[
17226                (
17227                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17228                    Hsla::green(),
17229                ),
17230                (
17231                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17232                    Hsla::red(),
17233                ),
17234                (
17235                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17236                    Hsla::green(),
17237                ),
17238                (
17239                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17240                    Hsla::red(),
17241                ),
17242            ]
17243        );
17244        assert_eq!(
17245            editor.sorted_background_highlights_in_range(
17246                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17247                &snapshot,
17248                cx.theme(),
17249            ),
17250            &[(
17251                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17252                Hsla::red(),
17253            )]
17254        );
17255    });
17256}
17257
17258#[gpui::test]
17259async fn test_following(cx: &mut TestAppContext) {
17260    init_test(cx, |_| {});
17261
17262    let fs = FakeFs::new(cx.executor());
17263    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17264
17265    let buffer = project.update(cx, |project, cx| {
17266        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17267        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17268    });
17269    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17270    let follower = cx.update(|cx| {
17271        cx.open_window(
17272            WindowOptions {
17273                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17274                    gpui::Point::new(px(0.), px(0.)),
17275                    gpui::Point::new(px(10.), px(80.)),
17276                ))),
17277                ..Default::default()
17278            },
17279            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17280        )
17281        .unwrap()
17282    });
17283
17284    let is_still_following = Rc::new(RefCell::new(true));
17285    let follower_edit_event_count = Rc::new(RefCell::new(0));
17286    let pending_update = Rc::new(RefCell::new(None));
17287    let leader_entity = leader.root(cx).unwrap();
17288    let follower_entity = follower.root(cx).unwrap();
17289    _ = follower.update(cx, {
17290        let update = pending_update.clone();
17291        let is_still_following = is_still_following.clone();
17292        let follower_edit_event_count = follower_edit_event_count.clone();
17293        |_, window, cx| {
17294            cx.subscribe_in(
17295                &leader_entity,
17296                window,
17297                move |_, leader, event, window, cx| {
17298                    leader.read(cx).add_event_to_update_proto(
17299                        event,
17300                        &mut update.borrow_mut(),
17301                        window,
17302                        cx,
17303                    );
17304                },
17305            )
17306            .detach();
17307
17308            cx.subscribe_in(
17309                &follower_entity,
17310                window,
17311                move |_, _, event: &EditorEvent, _window, _cx| {
17312                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17313                        *is_still_following.borrow_mut() = false;
17314                    }
17315
17316                    if let EditorEvent::BufferEdited = event {
17317                        *follower_edit_event_count.borrow_mut() += 1;
17318                    }
17319                },
17320            )
17321            .detach();
17322        }
17323    });
17324
17325    // Update the selections only
17326    _ = leader.update(cx, |leader, window, cx| {
17327        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17328            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17329        });
17330    });
17331    follower
17332        .update(cx, |follower, window, cx| {
17333            follower.apply_update_proto(
17334                &project,
17335                pending_update.borrow_mut().take().unwrap(),
17336                window,
17337                cx,
17338            )
17339        })
17340        .unwrap()
17341        .await
17342        .unwrap();
17343    _ = follower.update(cx, |follower, _, cx| {
17344        assert_eq!(
17345            follower.selections.ranges(&follower.display_snapshot(cx)),
17346            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17347        );
17348    });
17349    assert!(*is_still_following.borrow());
17350    assert_eq!(*follower_edit_event_count.borrow(), 0);
17351
17352    // Update the scroll position only
17353    _ = leader.update(cx, |leader, window, cx| {
17354        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17355    });
17356    follower
17357        .update(cx, |follower, window, cx| {
17358            follower.apply_update_proto(
17359                &project,
17360                pending_update.borrow_mut().take().unwrap(),
17361                window,
17362                cx,
17363            )
17364        })
17365        .unwrap()
17366        .await
17367        .unwrap();
17368    assert_eq!(
17369        follower
17370            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17371            .unwrap(),
17372        gpui::Point::new(1.5, 3.5)
17373    );
17374    assert!(*is_still_following.borrow());
17375    assert_eq!(*follower_edit_event_count.borrow(), 0);
17376
17377    // Update the selections and scroll position. The follower's scroll position is updated
17378    // via autoscroll, not via the leader's exact scroll position.
17379    _ = leader.update(cx, |leader, window, cx| {
17380        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17381            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17382        });
17383        leader.request_autoscroll(Autoscroll::newest(), cx);
17384        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17385    });
17386    follower
17387        .update(cx, |follower, window, cx| {
17388            follower.apply_update_proto(
17389                &project,
17390                pending_update.borrow_mut().take().unwrap(),
17391                window,
17392                cx,
17393            )
17394        })
17395        .unwrap()
17396        .await
17397        .unwrap();
17398    _ = follower.update(cx, |follower, _, cx| {
17399        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17400        assert_eq!(
17401            follower.selections.ranges(&follower.display_snapshot(cx)),
17402            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17403        );
17404    });
17405    assert!(*is_still_following.borrow());
17406
17407    // Creating a pending selection that precedes another selection
17408    _ = leader.update(cx, |leader, window, cx| {
17409        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17410            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17411        });
17412        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17413    });
17414    follower
17415        .update(cx, |follower, window, cx| {
17416            follower.apply_update_proto(
17417                &project,
17418                pending_update.borrow_mut().take().unwrap(),
17419                window,
17420                cx,
17421            )
17422        })
17423        .unwrap()
17424        .await
17425        .unwrap();
17426    _ = follower.update(cx, |follower, _, cx| {
17427        assert_eq!(
17428            follower.selections.ranges(&follower.display_snapshot(cx)),
17429            vec![
17430                MultiBufferOffset(0)..MultiBufferOffset(0),
17431                MultiBufferOffset(1)..MultiBufferOffset(1)
17432            ]
17433        );
17434    });
17435    assert!(*is_still_following.borrow());
17436
17437    // Extend the pending selection so that it surrounds another selection
17438    _ = leader.update(cx, |leader, window, cx| {
17439        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17440    });
17441    follower
17442        .update(cx, |follower, window, cx| {
17443            follower.apply_update_proto(
17444                &project,
17445                pending_update.borrow_mut().take().unwrap(),
17446                window,
17447                cx,
17448            )
17449        })
17450        .unwrap()
17451        .await
17452        .unwrap();
17453    _ = follower.update(cx, |follower, _, cx| {
17454        assert_eq!(
17455            follower.selections.ranges(&follower.display_snapshot(cx)),
17456            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17457        );
17458    });
17459
17460    // Scrolling locally breaks the follow
17461    _ = follower.update(cx, |follower, window, cx| {
17462        let top_anchor = follower
17463            .buffer()
17464            .read(cx)
17465            .read(cx)
17466            .anchor_after(MultiBufferOffset(0));
17467        follower.set_scroll_anchor(
17468            ScrollAnchor {
17469                anchor: top_anchor,
17470                offset: gpui::Point::new(0.0, 0.5),
17471            },
17472            window,
17473            cx,
17474        );
17475    });
17476    assert!(!(*is_still_following.borrow()));
17477}
17478
17479#[gpui::test]
17480async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17481    init_test(cx, |_| {});
17482
17483    let fs = FakeFs::new(cx.executor());
17484    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17485    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17486    let pane = workspace
17487        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17488        .unwrap();
17489
17490    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17491
17492    let leader = pane.update_in(cx, |_, window, cx| {
17493        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17494        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17495    });
17496
17497    // Start following the editor when it has no excerpts.
17498    let mut state_message =
17499        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17500    let workspace_entity = workspace.root(cx).unwrap();
17501    let follower_1 = cx
17502        .update_window(*workspace.deref(), |_, window, cx| {
17503            Editor::from_state_proto(
17504                workspace_entity,
17505                ViewId {
17506                    creator: CollaboratorId::PeerId(PeerId::default()),
17507                    id: 0,
17508                },
17509                &mut state_message,
17510                window,
17511                cx,
17512            )
17513        })
17514        .unwrap()
17515        .unwrap()
17516        .await
17517        .unwrap();
17518
17519    let update_message = Rc::new(RefCell::new(None));
17520    follower_1.update_in(cx, {
17521        let update = update_message.clone();
17522        |_, window, cx| {
17523            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17524                leader.read(cx).add_event_to_update_proto(
17525                    event,
17526                    &mut update.borrow_mut(),
17527                    window,
17528                    cx,
17529                );
17530            })
17531            .detach();
17532        }
17533    });
17534
17535    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17536        (
17537            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17538            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17539        )
17540    });
17541
17542    // Insert some excerpts.
17543    leader.update(cx, |leader, cx| {
17544        leader.buffer.update(cx, |multibuffer, cx| {
17545            multibuffer.set_excerpts_for_path(
17546                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17547                buffer_1.clone(),
17548                vec![
17549                    Point::row_range(0..3),
17550                    Point::row_range(1..6),
17551                    Point::row_range(12..15),
17552                ],
17553                0,
17554                cx,
17555            );
17556            multibuffer.set_excerpts_for_path(
17557                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17558                buffer_2.clone(),
17559                vec![Point::row_range(0..6), Point::row_range(8..12)],
17560                0,
17561                cx,
17562            );
17563        });
17564    });
17565
17566    // Apply the update of adding the excerpts.
17567    follower_1
17568        .update_in(cx, |follower, window, cx| {
17569            follower.apply_update_proto(
17570                &project,
17571                update_message.borrow().clone().unwrap(),
17572                window,
17573                cx,
17574            )
17575        })
17576        .await
17577        .unwrap();
17578    assert_eq!(
17579        follower_1.update(cx, |editor, cx| editor.text(cx)),
17580        leader.update(cx, |editor, cx| editor.text(cx))
17581    );
17582    update_message.borrow_mut().take();
17583
17584    // Start following separately after it already has excerpts.
17585    let mut state_message =
17586        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17587    let workspace_entity = workspace.root(cx).unwrap();
17588    let follower_2 = cx
17589        .update_window(*workspace.deref(), |_, window, cx| {
17590            Editor::from_state_proto(
17591                workspace_entity,
17592                ViewId {
17593                    creator: CollaboratorId::PeerId(PeerId::default()),
17594                    id: 0,
17595                },
17596                &mut state_message,
17597                window,
17598                cx,
17599            )
17600        })
17601        .unwrap()
17602        .unwrap()
17603        .await
17604        .unwrap();
17605    assert_eq!(
17606        follower_2.update(cx, |editor, cx| editor.text(cx)),
17607        leader.update(cx, |editor, cx| editor.text(cx))
17608    );
17609
17610    // Remove some excerpts.
17611    leader.update(cx, |leader, cx| {
17612        leader.buffer.update(cx, |multibuffer, cx| {
17613            let excerpt_ids = multibuffer.excerpt_ids();
17614            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17615            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17616        });
17617    });
17618
17619    // Apply the update of removing the excerpts.
17620    follower_1
17621        .update_in(cx, |follower, window, cx| {
17622            follower.apply_update_proto(
17623                &project,
17624                update_message.borrow().clone().unwrap(),
17625                window,
17626                cx,
17627            )
17628        })
17629        .await
17630        .unwrap();
17631    follower_2
17632        .update_in(cx, |follower, window, cx| {
17633            follower.apply_update_proto(
17634                &project,
17635                update_message.borrow().clone().unwrap(),
17636                window,
17637                cx,
17638            )
17639        })
17640        .await
17641        .unwrap();
17642    update_message.borrow_mut().take();
17643    assert_eq!(
17644        follower_1.update(cx, |editor, cx| editor.text(cx)),
17645        leader.update(cx, |editor, cx| editor.text(cx))
17646    );
17647}
17648
17649#[gpui::test]
17650async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17651    init_test(cx, |_| {});
17652
17653    let mut cx = EditorTestContext::new(cx).await;
17654    let lsp_store =
17655        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17656
17657    cx.set_state(indoc! {"
17658        ˇfn func(abc def: i32) -> u32 {
17659        }
17660    "});
17661
17662    cx.update(|_, cx| {
17663        lsp_store.update(cx, |lsp_store, cx| {
17664            lsp_store
17665                .update_diagnostics(
17666                    LanguageServerId(0),
17667                    lsp::PublishDiagnosticsParams {
17668                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17669                        version: None,
17670                        diagnostics: vec![
17671                            lsp::Diagnostic {
17672                                range: lsp::Range::new(
17673                                    lsp::Position::new(0, 11),
17674                                    lsp::Position::new(0, 12),
17675                                ),
17676                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17677                                ..Default::default()
17678                            },
17679                            lsp::Diagnostic {
17680                                range: lsp::Range::new(
17681                                    lsp::Position::new(0, 12),
17682                                    lsp::Position::new(0, 15),
17683                                ),
17684                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17685                                ..Default::default()
17686                            },
17687                            lsp::Diagnostic {
17688                                range: lsp::Range::new(
17689                                    lsp::Position::new(0, 25),
17690                                    lsp::Position::new(0, 28),
17691                                ),
17692                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17693                                ..Default::default()
17694                            },
17695                        ],
17696                    },
17697                    None,
17698                    DiagnosticSourceKind::Pushed,
17699                    &[],
17700                    cx,
17701                )
17702                .unwrap()
17703        });
17704    });
17705
17706    executor.run_until_parked();
17707
17708    cx.update_editor(|editor, window, cx| {
17709        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17710    });
17711
17712    cx.assert_editor_state(indoc! {"
17713        fn func(abc def: i32) -> ˇu32 {
17714        }
17715    "});
17716
17717    cx.update_editor(|editor, window, cx| {
17718        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17719    });
17720
17721    cx.assert_editor_state(indoc! {"
17722        fn func(abc ˇdef: i32) -> u32 {
17723        }
17724    "});
17725
17726    cx.update_editor(|editor, window, cx| {
17727        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17728    });
17729
17730    cx.assert_editor_state(indoc! {"
17731        fn func(abcˇ def: i32) -> u32 {
17732        }
17733    "});
17734
17735    cx.update_editor(|editor, window, cx| {
17736        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17737    });
17738
17739    cx.assert_editor_state(indoc! {"
17740        fn func(abc def: i32) -> ˇu32 {
17741        }
17742    "});
17743}
17744
17745#[gpui::test]
17746async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17747    init_test(cx, |_| {});
17748
17749    let mut cx = EditorTestContext::new(cx).await;
17750
17751    let diff_base = r#"
17752        use some::mod;
17753
17754        const A: u32 = 42;
17755
17756        fn main() {
17757            println!("hello");
17758
17759            println!("world");
17760        }
17761        "#
17762    .unindent();
17763
17764    // Edits are modified, removed, modified, added
17765    cx.set_state(
17766        &r#"
17767        use some::modified;
17768
17769        ˇ
17770        fn main() {
17771            println!("hello there");
17772
17773            println!("around the");
17774            println!("world");
17775        }
17776        "#
17777        .unindent(),
17778    );
17779
17780    cx.set_head_text(&diff_base);
17781    executor.run_until_parked();
17782
17783    cx.update_editor(|editor, window, cx| {
17784        //Wrap around the bottom of the buffer
17785        for _ in 0..3 {
17786            editor.go_to_next_hunk(&GoToHunk, window, cx);
17787        }
17788    });
17789
17790    cx.assert_editor_state(
17791        &r#"
17792        ˇuse some::modified;
17793
17794
17795        fn main() {
17796            println!("hello there");
17797
17798            println!("around the");
17799            println!("world");
17800        }
17801        "#
17802        .unindent(),
17803    );
17804
17805    cx.update_editor(|editor, window, cx| {
17806        //Wrap around the top of the buffer
17807        for _ in 0..2 {
17808            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17809        }
17810    });
17811
17812    cx.assert_editor_state(
17813        &r#"
17814        use some::modified;
17815
17816
17817        fn main() {
17818        ˇ    println!("hello there");
17819
17820            println!("around the");
17821            println!("world");
17822        }
17823        "#
17824        .unindent(),
17825    );
17826
17827    cx.update_editor(|editor, window, cx| {
17828        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17829    });
17830
17831    cx.assert_editor_state(
17832        &r#"
17833        use some::modified;
17834
17835        ˇ
17836        fn main() {
17837            println!("hello there");
17838
17839            println!("around the");
17840            println!("world");
17841        }
17842        "#
17843        .unindent(),
17844    );
17845
17846    cx.update_editor(|editor, window, cx| {
17847        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17848    });
17849
17850    cx.assert_editor_state(
17851        &r#"
17852        ˇuse some::modified;
17853
17854
17855        fn main() {
17856            println!("hello there");
17857
17858            println!("around the");
17859            println!("world");
17860        }
17861        "#
17862        .unindent(),
17863    );
17864
17865    cx.update_editor(|editor, window, cx| {
17866        for _ in 0..2 {
17867            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17868        }
17869    });
17870
17871    cx.assert_editor_state(
17872        &r#"
17873        use some::modified;
17874
17875
17876        fn main() {
17877        ˇ    println!("hello there");
17878
17879            println!("around the");
17880            println!("world");
17881        }
17882        "#
17883        .unindent(),
17884    );
17885
17886    cx.update_editor(|editor, window, cx| {
17887        editor.fold(&Fold, window, cx);
17888    });
17889
17890    cx.update_editor(|editor, window, cx| {
17891        editor.go_to_next_hunk(&GoToHunk, window, cx);
17892    });
17893
17894    cx.assert_editor_state(
17895        &r#"
17896        ˇuse some::modified;
17897
17898
17899        fn main() {
17900            println!("hello there");
17901
17902            println!("around the");
17903            println!("world");
17904        }
17905        "#
17906        .unindent(),
17907    );
17908}
17909
17910#[test]
17911fn test_split_words() {
17912    fn split(text: &str) -> Vec<&str> {
17913        split_words(text).collect()
17914    }
17915
17916    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17917    assert_eq!(split("hello_world"), &["hello_", "world"]);
17918    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17919    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17920    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17921    assert_eq!(split("helloworld"), &["helloworld"]);
17922
17923    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17924}
17925
17926#[test]
17927fn test_split_words_for_snippet_prefix() {
17928    fn split(text: &str) -> Vec<&str> {
17929        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17930    }
17931
17932    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17933    assert_eq!(split("hello_world"), &["hello_world"]);
17934    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17935    assert_eq!(split("Hello_World"), &["Hello_World"]);
17936    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17937    assert_eq!(split("helloworld"), &["helloworld"]);
17938    assert_eq!(
17939        split("this@is!@#$^many   . symbols"),
17940        &[
17941            "symbols",
17942            " symbols",
17943            ". symbols",
17944            " . symbols",
17945            "  . symbols",
17946            "   . symbols",
17947            "many   . symbols",
17948            "^many   . symbols",
17949            "$^many   . symbols",
17950            "#$^many   . symbols",
17951            "@#$^many   . symbols",
17952            "!@#$^many   . symbols",
17953            "is!@#$^many   . symbols",
17954            "@is!@#$^many   . symbols",
17955            "this@is!@#$^many   . symbols",
17956        ],
17957    );
17958    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17959}
17960
17961#[gpui::test]
17962async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17963    init_test(cx, |_| {});
17964
17965    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17966
17967    #[track_caller]
17968    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17969        let _state_context = cx.set_state(before);
17970        cx.run_until_parked();
17971        cx.update_editor(|editor, window, cx| {
17972            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17973        });
17974        cx.run_until_parked();
17975        cx.assert_editor_state(after);
17976    }
17977
17978    // Outside bracket jumps to outside of matching bracket
17979    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17980    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17981
17982    // Inside bracket jumps to inside of matching bracket
17983    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17984    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17985
17986    // When outside a bracket and inside, favor jumping to the inside bracket
17987    assert(
17988        "console.log('foo', [1, 2, 3]ˇ);",
17989        "console.log('foo', ˇ[1, 2, 3]);",
17990        &mut cx,
17991    );
17992    assert(
17993        "console.log(ˇ'foo', [1, 2, 3]);",
17994        "console.log('foo'ˇ, [1, 2, 3]);",
17995        &mut cx,
17996    );
17997
17998    // Bias forward if two options are equally likely
17999    assert(
18000        "let result = curried_fun()ˇ();",
18001        "let result = curried_fun()()ˇ;",
18002        &mut cx,
18003    );
18004
18005    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18006    assert(
18007        indoc! {"
18008            function test() {
18009                console.log('test')ˇ
18010            }"},
18011        indoc! {"
18012            function test() {
18013                console.logˇ('test')
18014            }"},
18015        &mut cx,
18016    );
18017}
18018
18019#[gpui::test]
18020async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18021    init_test(cx, |_| {});
18022    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18023    language_registry.add(markdown_lang());
18024    language_registry.add(rust_lang());
18025    let buffer = cx.new(|cx| {
18026        let mut buffer = language::Buffer::local(
18027            indoc! {"
18028            ```rs
18029            impl Worktree {
18030                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18031                }
18032            }
18033            ```
18034        "},
18035            cx,
18036        );
18037        buffer.set_language_registry(language_registry.clone());
18038        buffer.set_language(Some(markdown_lang()), cx);
18039        buffer
18040    });
18041    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18042    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18043    cx.executor().run_until_parked();
18044    _ = editor.update(cx, |editor, window, cx| {
18045        // Case 1: Test outer enclosing brackets
18046        select_ranges(
18047            editor,
18048            &indoc! {"
18049                ```rs
18050                impl Worktree {
18051                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18052                    }
1805318054                ```
18055            "},
18056            window,
18057            cx,
18058        );
18059        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18060        assert_text_with_selections(
18061            editor,
18062            &indoc! {"
18063                ```rs
18064                impl Worktree ˇ{
18065                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18066                    }
18067                }
18068                ```
18069            "},
18070            cx,
18071        );
18072        // Case 2: Test inner enclosing brackets
18073        select_ranges(
18074            editor,
18075            &indoc! {"
18076                ```rs
18077                impl Worktree {
18078                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1807918080                }
18081                ```
18082            "},
18083            window,
18084            cx,
18085        );
18086        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18087        assert_text_with_selections(
18088            editor,
18089            &indoc! {"
18090                ```rs
18091                impl Worktree {
18092                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18093                    }
18094                }
18095                ```
18096            "},
18097            cx,
18098        );
18099    });
18100}
18101
18102#[gpui::test]
18103async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18104    init_test(cx, |_| {});
18105
18106    let fs = FakeFs::new(cx.executor());
18107    fs.insert_tree(
18108        path!("/a"),
18109        json!({
18110            "main.rs": "fn main() { let a = 5; }",
18111            "other.rs": "// Test file",
18112        }),
18113    )
18114    .await;
18115    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18116
18117    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18118    language_registry.add(Arc::new(Language::new(
18119        LanguageConfig {
18120            name: "Rust".into(),
18121            matcher: LanguageMatcher {
18122                path_suffixes: vec!["rs".to_string()],
18123                ..Default::default()
18124            },
18125            brackets: BracketPairConfig {
18126                pairs: vec![BracketPair {
18127                    start: "{".to_string(),
18128                    end: "}".to_string(),
18129                    close: true,
18130                    surround: true,
18131                    newline: true,
18132                }],
18133                disabled_scopes_by_bracket_ix: Vec::new(),
18134            },
18135            ..Default::default()
18136        },
18137        Some(tree_sitter_rust::LANGUAGE.into()),
18138    )));
18139    let mut fake_servers = language_registry.register_fake_lsp(
18140        "Rust",
18141        FakeLspAdapter {
18142            capabilities: lsp::ServerCapabilities {
18143                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18144                    first_trigger_character: "{".to_string(),
18145                    more_trigger_character: None,
18146                }),
18147                ..Default::default()
18148            },
18149            ..Default::default()
18150        },
18151    );
18152
18153    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18154
18155    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18156
18157    let worktree_id = workspace
18158        .update(cx, |workspace, _, cx| {
18159            workspace.project().update(cx, |project, cx| {
18160                project.worktrees(cx).next().unwrap().read(cx).id()
18161            })
18162        })
18163        .unwrap();
18164
18165    let buffer = project
18166        .update(cx, |project, cx| {
18167            project.open_local_buffer(path!("/a/main.rs"), cx)
18168        })
18169        .await
18170        .unwrap();
18171    let editor_handle = workspace
18172        .update(cx, |workspace, window, cx| {
18173            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18174        })
18175        .unwrap()
18176        .await
18177        .unwrap()
18178        .downcast::<Editor>()
18179        .unwrap();
18180
18181    cx.executor().start_waiting();
18182    let fake_server = fake_servers.next().await.unwrap();
18183
18184    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18185        |params, _| async move {
18186            assert_eq!(
18187                params.text_document_position.text_document.uri,
18188                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18189            );
18190            assert_eq!(
18191                params.text_document_position.position,
18192                lsp::Position::new(0, 21),
18193            );
18194
18195            Ok(Some(vec![lsp::TextEdit {
18196                new_text: "]".to_string(),
18197                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18198            }]))
18199        },
18200    );
18201
18202    editor_handle.update_in(cx, |editor, window, cx| {
18203        window.focus(&editor.focus_handle(cx), cx);
18204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18205            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18206        });
18207        editor.handle_input("{", window, cx);
18208    });
18209
18210    cx.executor().run_until_parked();
18211
18212    buffer.update(cx, |buffer, _| {
18213        assert_eq!(
18214            buffer.text(),
18215            "fn main() { let a = {5}; }",
18216            "No extra braces from on type formatting should appear in the buffer"
18217        )
18218    });
18219}
18220
18221#[gpui::test(iterations = 20, seeds(31))]
18222async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18223    init_test(cx, |_| {});
18224
18225    let mut cx = EditorLspTestContext::new_rust(
18226        lsp::ServerCapabilities {
18227            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18228                first_trigger_character: ".".to_string(),
18229                more_trigger_character: None,
18230            }),
18231            ..Default::default()
18232        },
18233        cx,
18234    )
18235    .await;
18236
18237    cx.update_buffer(|buffer, _| {
18238        // This causes autoindent to be async.
18239        buffer.set_sync_parse_timeout(Duration::ZERO)
18240    });
18241
18242    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18243    cx.simulate_keystroke("\n");
18244    cx.run_until_parked();
18245
18246    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18247    let mut request =
18248        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18249            let buffer_cloned = buffer_cloned.clone();
18250            async move {
18251                buffer_cloned.update(&mut cx, |buffer, _| {
18252                    assert_eq!(
18253                        buffer.text(),
18254                        "fn c() {\n    d()\n        .\n}\n",
18255                        "OnTypeFormatting should triggered after autoindent applied"
18256                    )
18257                })?;
18258
18259                Ok(Some(vec![]))
18260            }
18261        });
18262
18263    cx.simulate_keystroke(".");
18264    cx.run_until_parked();
18265
18266    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18267    assert!(request.next().await.is_some());
18268    request.close();
18269    assert!(request.next().await.is_none());
18270}
18271
18272#[gpui::test]
18273async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18274    init_test(cx, |_| {});
18275
18276    let fs = FakeFs::new(cx.executor());
18277    fs.insert_tree(
18278        path!("/a"),
18279        json!({
18280            "main.rs": "fn main() { let a = 5; }",
18281            "other.rs": "// Test file",
18282        }),
18283    )
18284    .await;
18285
18286    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18287
18288    let server_restarts = Arc::new(AtomicUsize::new(0));
18289    let closure_restarts = Arc::clone(&server_restarts);
18290    let language_server_name = "test language server";
18291    let language_name: LanguageName = "Rust".into();
18292
18293    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18294    language_registry.add(Arc::new(Language::new(
18295        LanguageConfig {
18296            name: language_name.clone(),
18297            matcher: LanguageMatcher {
18298                path_suffixes: vec!["rs".to_string()],
18299                ..Default::default()
18300            },
18301            ..Default::default()
18302        },
18303        Some(tree_sitter_rust::LANGUAGE.into()),
18304    )));
18305    let mut fake_servers = language_registry.register_fake_lsp(
18306        "Rust",
18307        FakeLspAdapter {
18308            name: language_server_name,
18309            initialization_options: Some(json!({
18310                "testOptionValue": true
18311            })),
18312            initializer: Some(Box::new(move |fake_server| {
18313                let task_restarts = Arc::clone(&closure_restarts);
18314                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18315                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18316                    futures::future::ready(Ok(()))
18317                });
18318            })),
18319            ..Default::default()
18320        },
18321    );
18322
18323    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18324    let _buffer = project
18325        .update(cx, |project, cx| {
18326            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18327        })
18328        .await
18329        .unwrap();
18330    let _fake_server = fake_servers.next().await.unwrap();
18331    update_test_language_settings(cx, |language_settings| {
18332        language_settings.languages.0.insert(
18333            language_name.clone().0,
18334            LanguageSettingsContent {
18335                tab_size: NonZeroU32::new(8),
18336                ..Default::default()
18337            },
18338        );
18339    });
18340    cx.executor().run_until_parked();
18341    assert_eq!(
18342        server_restarts.load(atomic::Ordering::Acquire),
18343        0,
18344        "Should not restart LSP server on an unrelated change"
18345    );
18346
18347    update_test_project_settings(cx, |project_settings| {
18348        project_settings.lsp.insert(
18349            "Some other server name".into(),
18350            LspSettings {
18351                binary: None,
18352                settings: None,
18353                initialization_options: Some(json!({
18354                    "some other init value": false
18355                })),
18356                enable_lsp_tasks: false,
18357                fetch: None,
18358            },
18359        );
18360    });
18361    cx.executor().run_until_parked();
18362    assert_eq!(
18363        server_restarts.load(atomic::Ordering::Acquire),
18364        0,
18365        "Should not restart LSP server on an unrelated LSP settings change"
18366    );
18367
18368    update_test_project_settings(cx, |project_settings| {
18369        project_settings.lsp.insert(
18370            language_server_name.into(),
18371            LspSettings {
18372                binary: None,
18373                settings: None,
18374                initialization_options: Some(json!({
18375                    "anotherInitValue": false
18376                })),
18377                enable_lsp_tasks: false,
18378                fetch: None,
18379            },
18380        );
18381    });
18382    cx.executor().run_until_parked();
18383    assert_eq!(
18384        server_restarts.load(atomic::Ordering::Acquire),
18385        1,
18386        "Should restart LSP server on a related LSP settings change"
18387    );
18388
18389    update_test_project_settings(cx, |project_settings| {
18390        project_settings.lsp.insert(
18391            language_server_name.into(),
18392            LspSettings {
18393                binary: None,
18394                settings: None,
18395                initialization_options: Some(json!({
18396                    "anotherInitValue": false
18397                })),
18398                enable_lsp_tasks: false,
18399                fetch: None,
18400            },
18401        );
18402    });
18403    cx.executor().run_until_parked();
18404    assert_eq!(
18405        server_restarts.load(atomic::Ordering::Acquire),
18406        1,
18407        "Should not restart LSP server on a related LSP settings change that is the same"
18408    );
18409
18410    update_test_project_settings(cx, |project_settings| {
18411        project_settings.lsp.insert(
18412            language_server_name.into(),
18413            LspSettings {
18414                binary: None,
18415                settings: None,
18416                initialization_options: None,
18417                enable_lsp_tasks: false,
18418                fetch: None,
18419            },
18420        );
18421    });
18422    cx.executor().run_until_parked();
18423    assert_eq!(
18424        server_restarts.load(atomic::Ordering::Acquire),
18425        2,
18426        "Should restart LSP server on another related LSP settings change"
18427    );
18428}
18429
18430#[gpui::test]
18431async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18432    init_test(cx, |_| {});
18433
18434    let mut cx = EditorLspTestContext::new_rust(
18435        lsp::ServerCapabilities {
18436            completion_provider: Some(lsp::CompletionOptions {
18437                trigger_characters: Some(vec![".".to_string()]),
18438                resolve_provider: Some(true),
18439                ..Default::default()
18440            }),
18441            ..Default::default()
18442        },
18443        cx,
18444    )
18445    .await;
18446
18447    cx.set_state("fn main() { let a = 2ˇ; }");
18448    cx.simulate_keystroke(".");
18449    let completion_item = lsp::CompletionItem {
18450        label: "some".into(),
18451        kind: Some(lsp::CompletionItemKind::SNIPPET),
18452        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18453        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18454            kind: lsp::MarkupKind::Markdown,
18455            value: "```rust\nSome(2)\n```".to_string(),
18456        })),
18457        deprecated: Some(false),
18458        sort_text: Some("fffffff2".to_string()),
18459        filter_text: Some("some".to_string()),
18460        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18461        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18462            range: lsp::Range {
18463                start: lsp::Position {
18464                    line: 0,
18465                    character: 22,
18466                },
18467                end: lsp::Position {
18468                    line: 0,
18469                    character: 22,
18470                },
18471            },
18472            new_text: "Some(2)".to_string(),
18473        })),
18474        additional_text_edits: Some(vec![lsp::TextEdit {
18475            range: lsp::Range {
18476                start: lsp::Position {
18477                    line: 0,
18478                    character: 20,
18479                },
18480                end: lsp::Position {
18481                    line: 0,
18482                    character: 22,
18483                },
18484            },
18485            new_text: "".to_string(),
18486        }]),
18487        ..Default::default()
18488    };
18489
18490    let closure_completion_item = completion_item.clone();
18491    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18492        let task_completion_item = closure_completion_item.clone();
18493        async move {
18494            Ok(Some(lsp::CompletionResponse::Array(vec![
18495                task_completion_item,
18496            ])))
18497        }
18498    });
18499
18500    request.next().await;
18501
18502    cx.condition(|editor, _| editor.context_menu_visible())
18503        .await;
18504    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18505        editor
18506            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18507            .unwrap()
18508    });
18509    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18510
18511    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18512        let task_completion_item = completion_item.clone();
18513        async move { Ok(task_completion_item) }
18514    })
18515    .next()
18516    .await
18517    .unwrap();
18518    apply_additional_edits.await.unwrap();
18519    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18520}
18521
18522#[gpui::test]
18523async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18524    init_test(cx, |_| {});
18525
18526    let mut cx = EditorLspTestContext::new_rust(
18527        lsp::ServerCapabilities {
18528            completion_provider: Some(lsp::CompletionOptions {
18529                trigger_characters: Some(vec![".".to_string()]),
18530                resolve_provider: Some(true),
18531                ..Default::default()
18532            }),
18533            ..Default::default()
18534        },
18535        cx,
18536    )
18537    .await;
18538
18539    cx.set_state("fn main() { let a = 2ˇ; }");
18540    cx.simulate_keystroke(".");
18541
18542    let item1 = lsp::CompletionItem {
18543        label: "method id()".to_string(),
18544        filter_text: Some("id".to_string()),
18545        detail: None,
18546        documentation: None,
18547        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18548            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18549            new_text: ".id".to_string(),
18550        })),
18551        ..lsp::CompletionItem::default()
18552    };
18553
18554    let item2 = lsp::CompletionItem {
18555        label: "other".to_string(),
18556        filter_text: Some("other".to_string()),
18557        detail: None,
18558        documentation: None,
18559        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18560            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18561            new_text: ".other".to_string(),
18562        })),
18563        ..lsp::CompletionItem::default()
18564    };
18565
18566    let item1 = item1.clone();
18567    cx.set_request_handler::<lsp::request::Completion, _, _>({
18568        let item1 = item1.clone();
18569        move |_, _, _| {
18570            let item1 = item1.clone();
18571            let item2 = item2.clone();
18572            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18573        }
18574    })
18575    .next()
18576    .await;
18577
18578    cx.condition(|editor, _| editor.context_menu_visible())
18579        .await;
18580    cx.update_editor(|editor, _, _| {
18581        let context_menu = editor.context_menu.borrow_mut();
18582        let context_menu = context_menu
18583            .as_ref()
18584            .expect("Should have the context menu deployed");
18585        match context_menu {
18586            CodeContextMenu::Completions(completions_menu) => {
18587                let completions = completions_menu.completions.borrow_mut();
18588                assert_eq!(
18589                    completions
18590                        .iter()
18591                        .map(|completion| &completion.label.text)
18592                        .collect::<Vec<_>>(),
18593                    vec!["method id()", "other"]
18594                )
18595            }
18596            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18597        }
18598    });
18599
18600    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18601        let item1 = item1.clone();
18602        move |_, item_to_resolve, _| {
18603            let item1 = item1.clone();
18604            async move {
18605                if item1 == item_to_resolve {
18606                    Ok(lsp::CompletionItem {
18607                        label: "method id()".to_string(),
18608                        filter_text: Some("id".to_string()),
18609                        detail: Some("Now resolved!".to_string()),
18610                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18611                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18612                            range: lsp::Range::new(
18613                                lsp::Position::new(0, 22),
18614                                lsp::Position::new(0, 22),
18615                            ),
18616                            new_text: ".id".to_string(),
18617                        })),
18618                        ..lsp::CompletionItem::default()
18619                    })
18620                } else {
18621                    Ok(item_to_resolve)
18622                }
18623            }
18624        }
18625    })
18626    .next()
18627    .await
18628    .unwrap();
18629    cx.run_until_parked();
18630
18631    cx.update_editor(|editor, window, cx| {
18632        editor.context_menu_next(&Default::default(), window, cx);
18633    });
18634
18635    cx.update_editor(|editor, _, _| {
18636        let context_menu = editor.context_menu.borrow_mut();
18637        let context_menu = context_menu
18638            .as_ref()
18639            .expect("Should have the context menu deployed");
18640        match context_menu {
18641            CodeContextMenu::Completions(completions_menu) => {
18642                let completions = completions_menu.completions.borrow_mut();
18643                assert_eq!(
18644                    completions
18645                        .iter()
18646                        .map(|completion| &completion.label.text)
18647                        .collect::<Vec<_>>(),
18648                    vec!["method id() Now resolved!", "other"],
18649                    "Should update first completion label, but not second as the filter text did not match."
18650                );
18651            }
18652            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18653        }
18654    });
18655}
18656
18657#[gpui::test]
18658async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18659    init_test(cx, |_| {});
18660    let mut cx = EditorLspTestContext::new_rust(
18661        lsp::ServerCapabilities {
18662            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18663            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18664            completion_provider: Some(lsp::CompletionOptions {
18665                resolve_provider: Some(true),
18666                ..Default::default()
18667            }),
18668            ..Default::default()
18669        },
18670        cx,
18671    )
18672    .await;
18673    cx.set_state(indoc! {"
18674        struct TestStruct {
18675            field: i32
18676        }
18677
18678        fn mainˇ() {
18679            let unused_var = 42;
18680            let test_struct = TestStruct { field: 42 };
18681        }
18682    "});
18683    let symbol_range = cx.lsp_range(indoc! {"
18684        struct TestStruct {
18685            field: i32
18686        }
18687
18688        «fn main»() {
18689            let unused_var = 42;
18690            let test_struct = TestStruct { field: 42 };
18691        }
18692    "});
18693    let mut hover_requests =
18694        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18695            Ok(Some(lsp::Hover {
18696                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18697                    kind: lsp::MarkupKind::Markdown,
18698                    value: "Function documentation".to_string(),
18699                }),
18700                range: Some(symbol_range),
18701            }))
18702        });
18703
18704    // Case 1: Test that code action menu hide hover popover
18705    cx.dispatch_action(Hover);
18706    hover_requests.next().await;
18707    cx.condition(|editor, _| editor.hover_state.visible()).await;
18708    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18709        move |_, _, _| async move {
18710            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18711                lsp::CodeAction {
18712                    title: "Remove unused variable".to_string(),
18713                    kind: Some(CodeActionKind::QUICKFIX),
18714                    edit: Some(lsp::WorkspaceEdit {
18715                        changes: Some(
18716                            [(
18717                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18718                                vec![lsp::TextEdit {
18719                                    range: lsp::Range::new(
18720                                        lsp::Position::new(5, 4),
18721                                        lsp::Position::new(5, 27),
18722                                    ),
18723                                    new_text: "".to_string(),
18724                                }],
18725                            )]
18726                            .into_iter()
18727                            .collect(),
18728                        ),
18729                        ..Default::default()
18730                    }),
18731                    ..Default::default()
18732                },
18733            )]))
18734        },
18735    );
18736    cx.update_editor(|editor, window, cx| {
18737        editor.toggle_code_actions(
18738            &ToggleCodeActions {
18739                deployed_from: None,
18740                quick_launch: false,
18741            },
18742            window,
18743            cx,
18744        );
18745    });
18746    code_action_requests.next().await;
18747    cx.run_until_parked();
18748    cx.condition(|editor, _| editor.context_menu_visible())
18749        .await;
18750    cx.update_editor(|editor, _, _| {
18751        assert!(
18752            !editor.hover_state.visible(),
18753            "Hover popover should be hidden when code action menu is shown"
18754        );
18755        // Hide code actions
18756        editor.context_menu.take();
18757    });
18758
18759    // Case 2: Test that code completions hide hover popover
18760    cx.dispatch_action(Hover);
18761    hover_requests.next().await;
18762    cx.condition(|editor, _| editor.hover_state.visible()).await;
18763    let counter = Arc::new(AtomicUsize::new(0));
18764    let mut completion_requests =
18765        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18766            let counter = counter.clone();
18767            async move {
18768                counter.fetch_add(1, atomic::Ordering::Release);
18769                Ok(Some(lsp::CompletionResponse::Array(vec![
18770                    lsp::CompletionItem {
18771                        label: "main".into(),
18772                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18773                        detail: Some("() -> ()".to_string()),
18774                        ..Default::default()
18775                    },
18776                    lsp::CompletionItem {
18777                        label: "TestStruct".into(),
18778                        kind: Some(lsp::CompletionItemKind::STRUCT),
18779                        detail: Some("struct TestStruct".to_string()),
18780                        ..Default::default()
18781                    },
18782                ])))
18783            }
18784        });
18785    cx.update_editor(|editor, window, cx| {
18786        editor.show_completions(&ShowCompletions, window, cx);
18787    });
18788    completion_requests.next().await;
18789    cx.condition(|editor, _| editor.context_menu_visible())
18790        .await;
18791    cx.update_editor(|editor, _, _| {
18792        assert!(
18793            !editor.hover_state.visible(),
18794            "Hover popover should be hidden when completion menu is shown"
18795        );
18796    });
18797}
18798
18799#[gpui::test]
18800async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18801    init_test(cx, |_| {});
18802
18803    let mut cx = EditorLspTestContext::new_rust(
18804        lsp::ServerCapabilities {
18805            completion_provider: Some(lsp::CompletionOptions {
18806                trigger_characters: Some(vec![".".to_string()]),
18807                resolve_provider: Some(true),
18808                ..Default::default()
18809            }),
18810            ..Default::default()
18811        },
18812        cx,
18813    )
18814    .await;
18815
18816    cx.set_state("fn main() { let a = 2ˇ; }");
18817    cx.simulate_keystroke(".");
18818
18819    let unresolved_item_1 = lsp::CompletionItem {
18820        label: "id".to_string(),
18821        filter_text: Some("id".to_string()),
18822        detail: None,
18823        documentation: None,
18824        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18825            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18826            new_text: ".id".to_string(),
18827        })),
18828        ..lsp::CompletionItem::default()
18829    };
18830    let resolved_item_1 = lsp::CompletionItem {
18831        additional_text_edits: Some(vec![lsp::TextEdit {
18832            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18833            new_text: "!!".to_string(),
18834        }]),
18835        ..unresolved_item_1.clone()
18836    };
18837    let unresolved_item_2 = lsp::CompletionItem {
18838        label: "other".to_string(),
18839        filter_text: Some("other".to_string()),
18840        detail: None,
18841        documentation: None,
18842        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18843            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18844            new_text: ".other".to_string(),
18845        })),
18846        ..lsp::CompletionItem::default()
18847    };
18848    let resolved_item_2 = lsp::CompletionItem {
18849        additional_text_edits: Some(vec![lsp::TextEdit {
18850            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18851            new_text: "??".to_string(),
18852        }]),
18853        ..unresolved_item_2.clone()
18854    };
18855
18856    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18857    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18858    cx.lsp
18859        .server
18860        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18861            let unresolved_item_1 = unresolved_item_1.clone();
18862            let resolved_item_1 = resolved_item_1.clone();
18863            let unresolved_item_2 = unresolved_item_2.clone();
18864            let resolved_item_2 = resolved_item_2.clone();
18865            let resolve_requests_1 = resolve_requests_1.clone();
18866            let resolve_requests_2 = resolve_requests_2.clone();
18867            move |unresolved_request, _| {
18868                let unresolved_item_1 = unresolved_item_1.clone();
18869                let resolved_item_1 = resolved_item_1.clone();
18870                let unresolved_item_2 = unresolved_item_2.clone();
18871                let resolved_item_2 = resolved_item_2.clone();
18872                let resolve_requests_1 = resolve_requests_1.clone();
18873                let resolve_requests_2 = resolve_requests_2.clone();
18874                async move {
18875                    if unresolved_request == unresolved_item_1 {
18876                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18877                        Ok(resolved_item_1.clone())
18878                    } else if unresolved_request == unresolved_item_2 {
18879                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18880                        Ok(resolved_item_2.clone())
18881                    } else {
18882                        panic!("Unexpected completion item {unresolved_request:?}")
18883                    }
18884                }
18885            }
18886        })
18887        .detach();
18888
18889    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18890        let unresolved_item_1 = unresolved_item_1.clone();
18891        let unresolved_item_2 = unresolved_item_2.clone();
18892        async move {
18893            Ok(Some(lsp::CompletionResponse::Array(vec![
18894                unresolved_item_1,
18895                unresolved_item_2,
18896            ])))
18897        }
18898    })
18899    .next()
18900    .await;
18901
18902    cx.condition(|editor, _| editor.context_menu_visible())
18903        .await;
18904    cx.update_editor(|editor, _, _| {
18905        let context_menu = editor.context_menu.borrow_mut();
18906        let context_menu = context_menu
18907            .as_ref()
18908            .expect("Should have the context menu deployed");
18909        match context_menu {
18910            CodeContextMenu::Completions(completions_menu) => {
18911                let completions = completions_menu.completions.borrow_mut();
18912                assert_eq!(
18913                    completions
18914                        .iter()
18915                        .map(|completion| &completion.label.text)
18916                        .collect::<Vec<_>>(),
18917                    vec!["id", "other"]
18918                )
18919            }
18920            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18921        }
18922    });
18923    cx.run_until_parked();
18924
18925    cx.update_editor(|editor, window, cx| {
18926        editor.context_menu_next(&ContextMenuNext, window, cx);
18927    });
18928    cx.run_until_parked();
18929    cx.update_editor(|editor, window, cx| {
18930        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18931    });
18932    cx.run_until_parked();
18933    cx.update_editor(|editor, window, cx| {
18934        editor.context_menu_next(&ContextMenuNext, window, cx);
18935    });
18936    cx.run_until_parked();
18937    cx.update_editor(|editor, window, cx| {
18938        editor
18939            .compose_completion(&ComposeCompletion::default(), window, cx)
18940            .expect("No task returned")
18941    })
18942    .await
18943    .expect("Completion failed");
18944    cx.run_until_parked();
18945
18946    cx.update_editor(|editor, _, cx| {
18947        assert_eq!(
18948            resolve_requests_1.load(atomic::Ordering::Acquire),
18949            1,
18950            "Should always resolve once despite multiple selections"
18951        );
18952        assert_eq!(
18953            resolve_requests_2.load(atomic::Ordering::Acquire),
18954            1,
18955            "Should always resolve once after multiple selections and applying the completion"
18956        );
18957        assert_eq!(
18958            editor.text(cx),
18959            "fn main() { let a = ??.other; }",
18960            "Should use resolved data when applying the completion"
18961        );
18962    });
18963}
18964
18965#[gpui::test]
18966async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18967    init_test(cx, |_| {});
18968
18969    let item_0 = lsp::CompletionItem {
18970        label: "abs".into(),
18971        insert_text: Some("abs".into()),
18972        data: Some(json!({ "very": "special"})),
18973        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18974        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18975            lsp::InsertReplaceEdit {
18976                new_text: "abs".to_string(),
18977                insert: lsp::Range::default(),
18978                replace: lsp::Range::default(),
18979            },
18980        )),
18981        ..lsp::CompletionItem::default()
18982    };
18983    let items = iter::once(item_0.clone())
18984        .chain((11..51).map(|i| lsp::CompletionItem {
18985            label: format!("item_{}", i),
18986            insert_text: Some(format!("item_{}", i)),
18987            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18988            ..lsp::CompletionItem::default()
18989        }))
18990        .collect::<Vec<_>>();
18991
18992    let default_commit_characters = vec!["?".to_string()];
18993    let default_data = json!({ "default": "data"});
18994    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18995    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18996    let default_edit_range = lsp::Range {
18997        start: lsp::Position {
18998            line: 0,
18999            character: 5,
19000        },
19001        end: lsp::Position {
19002            line: 0,
19003            character: 5,
19004        },
19005    };
19006
19007    let mut cx = EditorLspTestContext::new_rust(
19008        lsp::ServerCapabilities {
19009            completion_provider: Some(lsp::CompletionOptions {
19010                trigger_characters: Some(vec![".".to_string()]),
19011                resolve_provider: Some(true),
19012                ..Default::default()
19013            }),
19014            ..Default::default()
19015        },
19016        cx,
19017    )
19018    .await;
19019
19020    cx.set_state("fn main() { let a = 2ˇ; }");
19021    cx.simulate_keystroke(".");
19022
19023    let completion_data = default_data.clone();
19024    let completion_characters = default_commit_characters.clone();
19025    let completion_items = items.clone();
19026    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19027        let default_data = completion_data.clone();
19028        let default_commit_characters = completion_characters.clone();
19029        let items = completion_items.clone();
19030        async move {
19031            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19032                items,
19033                item_defaults: Some(lsp::CompletionListItemDefaults {
19034                    data: Some(default_data.clone()),
19035                    commit_characters: Some(default_commit_characters.clone()),
19036                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19037                        default_edit_range,
19038                    )),
19039                    insert_text_format: Some(default_insert_text_format),
19040                    insert_text_mode: Some(default_insert_text_mode),
19041                }),
19042                ..lsp::CompletionList::default()
19043            })))
19044        }
19045    })
19046    .next()
19047    .await;
19048
19049    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19050    cx.lsp
19051        .server
19052        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19053            let closure_resolved_items = resolved_items.clone();
19054            move |item_to_resolve, _| {
19055                let closure_resolved_items = closure_resolved_items.clone();
19056                async move {
19057                    closure_resolved_items.lock().push(item_to_resolve.clone());
19058                    Ok(item_to_resolve)
19059                }
19060            }
19061        })
19062        .detach();
19063
19064    cx.condition(|editor, _| editor.context_menu_visible())
19065        .await;
19066    cx.run_until_parked();
19067    cx.update_editor(|editor, _, _| {
19068        let menu = editor.context_menu.borrow_mut();
19069        match menu.as_ref().expect("should have the completions menu") {
19070            CodeContextMenu::Completions(completions_menu) => {
19071                assert_eq!(
19072                    completions_menu
19073                        .entries
19074                        .borrow()
19075                        .iter()
19076                        .map(|mat| mat.string.clone())
19077                        .collect::<Vec<String>>(),
19078                    items
19079                        .iter()
19080                        .map(|completion| completion.label.clone())
19081                        .collect::<Vec<String>>()
19082                );
19083            }
19084            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19085        }
19086    });
19087    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19088    // with 4 from the end.
19089    assert_eq!(
19090        *resolved_items.lock(),
19091        [&items[0..16], &items[items.len() - 4..items.len()]]
19092            .concat()
19093            .iter()
19094            .cloned()
19095            .map(|mut item| {
19096                if item.data.is_none() {
19097                    item.data = Some(default_data.clone());
19098                }
19099                item
19100            })
19101            .collect::<Vec<lsp::CompletionItem>>(),
19102        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19103    );
19104    resolved_items.lock().clear();
19105
19106    cx.update_editor(|editor, window, cx| {
19107        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19108    });
19109    cx.run_until_parked();
19110    // Completions that have already been resolved are skipped.
19111    assert_eq!(
19112        *resolved_items.lock(),
19113        items[items.len() - 17..items.len() - 4]
19114            .iter()
19115            .cloned()
19116            .map(|mut item| {
19117                if item.data.is_none() {
19118                    item.data = Some(default_data.clone());
19119                }
19120                item
19121            })
19122            .collect::<Vec<lsp::CompletionItem>>()
19123    );
19124    resolved_items.lock().clear();
19125}
19126
19127#[gpui::test]
19128async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19129    init_test(cx, |_| {});
19130
19131    let mut cx = EditorLspTestContext::new(
19132        Language::new(
19133            LanguageConfig {
19134                matcher: LanguageMatcher {
19135                    path_suffixes: vec!["jsx".into()],
19136                    ..Default::default()
19137                },
19138                overrides: [(
19139                    "element".into(),
19140                    LanguageConfigOverride {
19141                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19142                        ..Default::default()
19143                    },
19144                )]
19145                .into_iter()
19146                .collect(),
19147                ..Default::default()
19148            },
19149            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19150        )
19151        .with_override_query("(jsx_self_closing_element) @element")
19152        .unwrap(),
19153        lsp::ServerCapabilities {
19154            completion_provider: Some(lsp::CompletionOptions {
19155                trigger_characters: Some(vec![":".to_string()]),
19156                ..Default::default()
19157            }),
19158            ..Default::default()
19159        },
19160        cx,
19161    )
19162    .await;
19163
19164    cx.lsp
19165        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19166            Ok(Some(lsp::CompletionResponse::Array(vec![
19167                lsp::CompletionItem {
19168                    label: "bg-blue".into(),
19169                    ..Default::default()
19170                },
19171                lsp::CompletionItem {
19172                    label: "bg-red".into(),
19173                    ..Default::default()
19174                },
19175                lsp::CompletionItem {
19176                    label: "bg-yellow".into(),
19177                    ..Default::default()
19178                },
19179            ])))
19180        });
19181
19182    cx.set_state(r#"<p class="bgˇ" />"#);
19183
19184    // Trigger completion when typing a dash, because the dash is an extra
19185    // word character in the 'element' scope, which contains the cursor.
19186    cx.simulate_keystroke("-");
19187    cx.executor().run_until_parked();
19188    cx.update_editor(|editor, _, _| {
19189        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19190        {
19191            assert_eq!(
19192                completion_menu_entries(menu),
19193                &["bg-blue", "bg-red", "bg-yellow"]
19194            );
19195        } else {
19196            panic!("expected completion menu to be open");
19197        }
19198    });
19199
19200    cx.simulate_keystroke("l");
19201    cx.executor().run_until_parked();
19202    cx.update_editor(|editor, _, _| {
19203        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19204        {
19205            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19206        } else {
19207            panic!("expected completion menu to be open");
19208        }
19209    });
19210
19211    // When filtering completions, consider the character after the '-' to
19212    // be the start of a subword.
19213    cx.set_state(r#"<p class="yelˇ" />"#);
19214    cx.simulate_keystroke("l");
19215    cx.executor().run_until_parked();
19216    cx.update_editor(|editor, _, _| {
19217        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19218        {
19219            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19220        } else {
19221            panic!("expected completion menu to be open");
19222        }
19223    });
19224}
19225
19226fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19227    let entries = menu.entries.borrow();
19228    entries.iter().map(|mat| mat.string.clone()).collect()
19229}
19230
19231#[gpui::test]
19232async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19233    init_test(cx, |settings| {
19234        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19235    });
19236
19237    let fs = FakeFs::new(cx.executor());
19238    fs.insert_file(path!("/file.ts"), Default::default()).await;
19239
19240    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19241    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19242
19243    language_registry.add(Arc::new(Language::new(
19244        LanguageConfig {
19245            name: "TypeScript".into(),
19246            matcher: LanguageMatcher {
19247                path_suffixes: vec!["ts".to_string()],
19248                ..Default::default()
19249            },
19250            ..Default::default()
19251        },
19252        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19253    )));
19254    update_test_language_settings(cx, |settings| {
19255        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19256    });
19257
19258    let test_plugin = "test_plugin";
19259    let _ = language_registry.register_fake_lsp(
19260        "TypeScript",
19261        FakeLspAdapter {
19262            prettier_plugins: vec![test_plugin],
19263            ..Default::default()
19264        },
19265    );
19266
19267    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19268    let buffer = project
19269        .update(cx, |project, cx| {
19270            project.open_local_buffer(path!("/file.ts"), cx)
19271        })
19272        .await
19273        .unwrap();
19274
19275    let buffer_text = "one\ntwo\nthree\n";
19276    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19277    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19278    editor.update_in(cx, |editor, window, cx| {
19279        editor.set_text(buffer_text, window, cx)
19280    });
19281
19282    editor
19283        .update_in(cx, |editor, window, cx| {
19284            editor.perform_format(
19285                project.clone(),
19286                FormatTrigger::Manual,
19287                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19288                window,
19289                cx,
19290            )
19291        })
19292        .unwrap()
19293        .await;
19294    assert_eq!(
19295        editor.update(cx, |editor, cx| editor.text(cx)),
19296        buffer_text.to_string() + prettier_format_suffix,
19297        "Test prettier formatting was not applied to the original buffer text",
19298    );
19299
19300    update_test_language_settings(cx, |settings| {
19301        settings.defaults.formatter = Some(FormatterList::default())
19302    });
19303    let format = editor.update_in(cx, |editor, window, cx| {
19304        editor.perform_format(
19305            project.clone(),
19306            FormatTrigger::Manual,
19307            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19308            window,
19309            cx,
19310        )
19311    });
19312    format.await.unwrap();
19313    assert_eq!(
19314        editor.update(cx, |editor, cx| editor.text(cx)),
19315        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19316        "Autoformatting (via test prettier) was not applied to the original buffer text",
19317    );
19318}
19319
19320#[gpui::test]
19321async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19322    init_test(cx, |settings| {
19323        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19324    });
19325
19326    let fs = FakeFs::new(cx.executor());
19327    fs.insert_file(path!("/file.settings"), Default::default())
19328        .await;
19329
19330    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19331    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19332
19333    let ts_lang = Arc::new(Language::new(
19334        LanguageConfig {
19335            name: "TypeScript".into(),
19336            matcher: LanguageMatcher {
19337                path_suffixes: vec!["ts".to_string()],
19338                ..LanguageMatcher::default()
19339            },
19340            prettier_parser_name: Some("typescript".to_string()),
19341            ..LanguageConfig::default()
19342        },
19343        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19344    ));
19345
19346    language_registry.add(ts_lang.clone());
19347
19348    update_test_language_settings(cx, |settings| {
19349        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19350    });
19351
19352    let test_plugin = "test_plugin";
19353    let _ = language_registry.register_fake_lsp(
19354        "TypeScript",
19355        FakeLspAdapter {
19356            prettier_plugins: vec![test_plugin],
19357            ..Default::default()
19358        },
19359    );
19360
19361    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19362    let buffer = project
19363        .update(cx, |project, cx| {
19364            project.open_local_buffer(path!("/file.settings"), cx)
19365        })
19366        .await
19367        .unwrap();
19368
19369    project.update(cx, |project, cx| {
19370        project.set_language_for_buffer(&buffer, ts_lang, cx)
19371    });
19372
19373    let buffer_text = "one\ntwo\nthree\n";
19374    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19375    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19376    editor.update_in(cx, |editor, window, cx| {
19377        editor.set_text(buffer_text, window, cx)
19378    });
19379
19380    editor
19381        .update_in(cx, |editor, window, cx| {
19382            editor.perform_format(
19383                project.clone(),
19384                FormatTrigger::Manual,
19385                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19386                window,
19387                cx,
19388            )
19389        })
19390        .unwrap()
19391        .await;
19392    assert_eq!(
19393        editor.update(cx, |editor, cx| editor.text(cx)),
19394        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19395        "Test prettier formatting was not applied to the original buffer text",
19396    );
19397
19398    update_test_language_settings(cx, |settings| {
19399        settings.defaults.formatter = Some(FormatterList::default())
19400    });
19401    let format = editor.update_in(cx, |editor, window, cx| {
19402        editor.perform_format(
19403            project.clone(),
19404            FormatTrigger::Manual,
19405            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19406            window,
19407            cx,
19408        )
19409    });
19410    format.await.unwrap();
19411
19412    assert_eq!(
19413        editor.update(cx, |editor, cx| editor.text(cx)),
19414        buffer_text.to_string()
19415            + prettier_format_suffix
19416            + "\ntypescript\n"
19417            + prettier_format_suffix
19418            + "\ntypescript",
19419        "Autoformatting (via test prettier) was not applied to the original buffer text",
19420    );
19421}
19422
19423#[gpui::test]
19424async fn test_addition_reverts(cx: &mut TestAppContext) {
19425    init_test(cx, |_| {});
19426    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19427    let base_text = indoc! {r#"
19428        struct Row;
19429        struct Row1;
19430        struct Row2;
19431
19432        struct Row4;
19433        struct Row5;
19434        struct Row6;
19435
19436        struct Row8;
19437        struct Row9;
19438        struct Row10;"#};
19439
19440    // When addition hunks are not adjacent to carets, no hunk revert is performed
19441    assert_hunk_revert(
19442        indoc! {r#"struct Row;
19443                   struct Row1;
19444                   struct Row1.1;
19445                   struct Row1.2;
19446                   struct Row2;ˇ
19447
19448                   struct Row4;
19449                   struct Row5;
19450                   struct Row6;
19451
19452                   struct Row8;
19453                   ˇstruct Row9;
19454                   struct Row9.1;
19455                   struct Row9.2;
19456                   struct Row9.3;
19457                   struct Row10;"#},
19458        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19459        indoc! {r#"struct Row;
19460                   struct Row1;
19461                   struct Row1.1;
19462                   struct Row1.2;
19463                   struct Row2;ˇ
19464
19465                   struct Row4;
19466                   struct Row5;
19467                   struct Row6;
19468
19469                   struct Row8;
19470                   ˇstruct Row9;
19471                   struct Row9.1;
19472                   struct Row9.2;
19473                   struct Row9.3;
19474                   struct Row10;"#},
19475        base_text,
19476        &mut cx,
19477    );
19478    // Same for selections
19479    assert_hunk_revert(
19480        indoc! {r#"struct Row;
19481                   struct Row1;
19482                   struct Row2;
19483                   struct Row2.1;
19484                   struct Row2.2;
19485                   «ˇ
19486                   struct Row4;
19487                   struct» Row5;
19488                   «struct Row6;
19489                   ˇ»
19490                   struct Row9.1;
19491                   struct Row9.2;
19492                   struct Row9.3;
19493                   struct Row8;
19494                   struct Row9;
19495                   struct Row10;"#},
19496        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19497        indoc! {r#"struct Row;
19498                   struct Row1;
19499                   struct Row2;
19500                   struct Row2.1;
19501                   struct Row2.2;
19502                   «ˇ
19503                   struct Row4;
19504                   struct» Row5;
19505                   «struct Row6;
19506                   ˇ»
19507                   struct Row9.1;
19508                   struct Row9.2;
19509                   struct Row9.3;
19510                   struct Row8;
19511                   struct Row9;
19512                   struct Row10;"#},
19513        base_text,
19514        &mut cx,
19515    );
19516
19517    // When carets and selections intersect the addition hunks, those are reverted.
19518    // Adjacent carets got merged.
19519    assert_hunk_revert(
19520        indoc! {r#"struct Row;
19521                   ˇ// something on the top
19522                   struct Row1;
19523                   struct Row2;
19524                   struct Roˇw3.1;
19525                   struct Row2.2;
19526                   struct Row2.3;ˇ
19527
19528                   struct Row4;
19529                   struct ˇRow5.1;
19530                   struct Row5.2;
19531                   struct «Rowˇ»5.3;
19532                   struct Row5;
19533                   struct Row6;
19534                   ˇ
19535                   struct Row9.1;
19536                   struct «Rowˇ»9.2;
19537                   struct «ˇRow»9.3;
19538                   struct Row8;
19539                   struct Row9;
19540                   «ˇ// something on bottom»
19541                   struct Row10;"#},
19542        vec![
19543            DiffHunkStatusKind::Added,
19544            DiffHunkStatusKind::Added,
19545            DiffHunkStatusKind::Added,
19546            DiffHunkStatusKind::Added,
19547            DiffHunkStatusKind::Added,
19548        ],
19549        indoc! {r#"struct Row;
19550                   ˇstruct Row1;
19551                   struct Row2;
19552                   ˇ
19553                   struct Row4;
19554                   ˇstruct Row5;
19555                   struct Row6;
19556                   ˇ
19557                   ˇstruct Row8;
19558                   struct Row9;
19559                   ˇstruct Row10;"#},
19560        base_text,
19561        &mut cx,
19562    );
19563}
19564
19565#[gpui::test]
19566async fn test_modification_reverts(cx: &mut TestAppContext) {
19567    init_test(cx, |_| {});
19568    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19569    let base_text = indoc! {r#"
19570        struct Row;
19571        struct Row1;
19572        struct Row2;
19573
19574        struct Row4;
19575        struct Row5;
19576        struct Row6;
19577
19578        struct Row8;
19579        struct Row9;
19580        struct Row10;"#};
19581
19582    // Modification hunks behave the same as the addition ones.
19583    assert_hunk_revert(
19584        indoc! {r#"struct Row;
19585                   struct Row1;
19586                   struct Row33;
19587                   ˇ
19588                   struct Row4;
19589                   struct Row5;
19590                   struct Row6;
19591                   ˇ
19592                   struct Row99;
19593                   struct Row9;
19594                   struct Row10;"#},
19595        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19596        indoc! {r#"struct Row;
19597                   struct Row1;
19598                   struct Row33;
19599                   ˇ
19600                   struct Row4;
19601                   struct Row5;
19602                   struct Row6;
19603                   ˇ
19604                   struct Row99;
19605                   struct Row9;
19606                   struct Row10;"#},
19607        base_text,
19608        &mut cx,
19609    );
19610    assert_hunk_revert(
19611        indoc! {r#"struct Row;
19612                   struct Row1;
19613                   struct Row33;
19614                   «ˇ
19615                   struct Row4;
19616                   struct» Row5;
19617                   «struct Row6;
19618                   ˇ»
19619                   struct Row99;
19620                   struct Row9;
19621                   struct Row10;"#},
19622        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19623        indoc! {r#"struct Row;
19624                   struct Row1;
19625                   struct Row33;
19626                   «ˇ
19627                   struct Row4;
19628                   struct» Row5;
19629                   «struct Row6;
19630                   ˇ»
19631                   struct Row99;
19632                   struct Row9;
19633                   struct Row10;"#},
19634        base_text,
19635        &mut cx,
19636    );
19637
19638    assert_hunk_revert(
19639        indoc! {r#"ˇstruct Row1.1;
19640                   struct Row1;
19641                   «ˇstr»uct Row22;
19642
19643                   struct ˇRow44;
19644                   struct Row5;
19645                   struct «Rˇ»ow66;ˇ
19646
19647                   «struˇ»ct Row88;
19648                   struct Row9;
19649                   struct Row1011;ˇ"#},
19650        vec![
19651            DiffHunkStatusKind::Modified,
19652            DiffHunkStatusKind::Modified,
19653            DiffHunkStatusKind::Modified,
19654            DiffHunkStatusKind::Modified,
19655            DiffHunkStatusKind::Modified,
19656            DiffHunkStatusKind::Modified,
19657        ],
19658        indoc! {r#"struct Row;
19659                   ˇstruct Row1;
19660                   struct Row2;
19661                   ˇ
19662                   struct Row4;
19663                   ˇstruct Row5;
19664                   struct Row6;
19665                   ˇ
19666                   struct Row8;
19667                   ˇstruct Row9;
19668                   struct Row10;ˇ"#},
19669        base_text,
19670        &mut cx,
19671    );
19672}
19673
19674#[gpui::test]
19675async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19676    init_test(cx, |_| {});
19677    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19678    let base_text = indoc! {r#"
19679        one
19680
19681        two
19682        three
19683        "#};
19684
19685    cx.set_head_text(base_text);
19686    cx.set_state("\nˇ\n");
19687    cx.executor().run_until_parked();
19688    cx.update_editor(|editor, _window, cx| {
19689        editor.expand_selected_diff_hunks(cx);
19690    });
19691    cx.executor().run_until_parked();
19692    cx.update_editor(|editor, window, cx| {
19693        editor.backspace(&Default::default(), window, cx);
19694    });
19695    cx.run_until_parked();
19696    cx.assert_state_with_diff(
19697        indoc! {r#"
19698
19699        - two
19700        - threeˇ
19701        +
19702        "#}
19703        .to_string(),
19704    );
19705}
19706
19707#[gpui::test]
19708async fn test_deletion_reverts(cx: &mut TestAppContext) {
19709    init_test(cx, |_| {});
19710    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19711    let base_text = indoc! {r#"struct Row;
19712struct Row1;
19713struct Row2;
19714
19715struct Row4;
19716struct Row5;
19717struct Row6;
19718
19719struct Row8;
19720struct Row9;
19721struct Row10;"#};
19722
19723    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19724    assert_hunk_revert(
19725        indoc! {r#"struct Row;
19726                   struct Row2;
19727
19728                   ˇstruct Row4;
19729                   struct Row5;
19730                   struct Row6;
19731                   ˇ
19732                   struct Row8;
19733                   struct Row10;"#},
19734        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19735        indoc! {r#"struct Row;
19736                   struct Row2;
19737
19738                   ˇstruct Row4;
19739                   struct Row5;
19740                   struct Row6;
19741                   ˇ
19742                   struct Row8;
19743                   struct Row10;"#},
19744        base_text,
19745        &mut cx,
19746    );
19747    assert_hunk_revert(
19748        indoc! {r#"struct Row;
19749                   struct Row2;
19750
19751                   «ˇstruct Row4;
19752                   struct» Row5;
19753                   «struct Row6;
19754                   ˇ»
19755                   struct Row8;
19756                   struct Row10;"#},
19757        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19758        indoc! {r#"struct Row;
19759                   struct Row2;
19760
19761                   «ˇstruct Row4;
19762                   struct» Row5;
19763                   «struct Row6;
19764                   ˇ»
19765                   struct Row8;
19766                   struct Row10;"#},
19767        base_text,
19768        &mut cx,
19769    );
19770
19771    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19772    assert_hunk_revert(
19773        indoc! {r#"struct Row;
19774                   ˇstruct Row2;
19775
19776                   struct Row4;
19777                   struct Row5;
19778                   struct Row6;
19779
19780                   struct Row8;ˇ
19781                   struct Row10;"#},
19782        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19783        indoc! {r#"struct Row;
19784                   struct Row1;
19785                   ˇstruct Row2;
19786
19787                   struct Row4;
19788                   struct Row5;
19789                   struct Row6;
19790
19791                   struct Row8;ˇ
19792                   struct Row9;
19793                   struct Row10;"#},
19794        base_text,
19795        &mut cx,
19796    );
19797    assert_hunk_revert(
19798        indoc! {r#"struct Row;
19799                   struct Row2«ˇ;
19800                   struct Row4;
19801                   struct» Row5;
19802                   «struct Row6;
19803
19804                   struct Row8;ˇ»
19805                   struct Row10;"#},
19806        vec![
19807            DiffHunkStatusKind::Deleted,
19808            DiffHunkStatusKind::Deleted,
19809            DiffHunkStatusKind::Deleted,
19810        ],
19811        indoc! {r#"struct Row;
19812                   struct Row1;
19813                   struct Row2«ˇ;
19814
19815                   struct Row4;
19816                   struct» Row5;
19817                   «struct Row6;
19818
19819                   struct Row8;ˇ»
19820                   struct Row9;
19821                   struct Row10;"#},
19822        base_text,
19823        &mut cx,
19824    );
19825}
19826
19827#[gpui::test]
19828async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19829    init_test(cx, |_| {});
19830
19831    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19832    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19833    let base_text_3 =
19834        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19835
19836    let text_1 = edit_first_char_of_every_line(base_text_1);
19837    let text_2 = edit_first_char_of_every_line(base_text_2);
19838    let text_3 = edit_first_char_of_every_line(base_text_3);
19839
19840    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19841    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19842    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19843
19844    let multibuffer = cx.new(|cx| {
19845        let mut multibuffer = MultiBuffer::new(ReadWrite);
19846        multibuffer.push_excerpts(
19847            buffer_1.clone(),
19848            [
19849                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19850                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19851                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19852            ],
19853            cx,
19854        );
19855        multibuffer.push_excerpts(
19856            buffer_2.clone(),
19857            [
19858                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19859                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19860                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19861            ],
19862            cx,
19863        );
19864        multibuffer.push_excerpts(
19865            buffer_3.clone(),
19866            [
19867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19870            ],
19871            cx,
19872        );
19873        multibuffer
19874    });
19875
19876    let fs = FakeFs::new(cx.executor());
19877    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19878    let (editor, cx) = cx
19879        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19880    editor.update_in(cx, |editor, _window, cx| {
19881        for (buffer, diff_base) in [
19882            (buffer_1.clone(), base_text_1),
19883            (buffer_2.clone(), base_text_2),
19884            (buffer_3.clone(), base_text_3),
19885        ] {
19886            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19887            editor
19888                .buffer
19889                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19890        }
19891    });
19892    cx.executor().run_until_parked();
19893
19894    editor.update_in(cx, |editor, window, cx| {
19895        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}");
19896        editor.select_all(&SelectAll, window, cx);
19897        editor.git_restore(&Default::default(), window, cx);
19898    });
19899    cx.executor().run_until_parked();
19900
19901    // When all ranges are selected, all buffer hunks are reverted.
19902    editor.update(cx, |editor, cx| {
19903        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");
19904    });
19905    buffer_1.update(cx, |buffer, _| {
19906        assert_eq!(buffer.text(), base_text_1);
19907    });
19908    buffer_2.update(cx, |buffer, _| {
19909        assert_eq!(buffer.text(), base_text_2);
19910    });
19911    buffer_3.update(cx, |buffer, _| {
19912        assert_eq!(buffer.text(), base_text_3);
19913    });
19914
19915    editor.update_in(cx, |editor, window, cx| {
19916        editor.undo(&Default::default(), window, cx);
19917    });
19918
19919    editor.update_in(cx, |editor, window, cx| {
19920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19921            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19922        });
19923        editor.git_restore(&Default::default(), window, cx);
19924    });
19925
19926    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19927    // but not affect buffer_2 and its related excerpts.
19928    editor.update(cx, |editor, cx| {
19929        assert_eq!(
19930            editor.text(cx),
19931            "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}"
19932        );
19933    });
19934    buffer_1.update(cx, |buffer, _| {
19935        assert_eq!(buffer.text(), base_text_1);
19936    });
19937    buffer_2.update(cx, |buffer, _| {
19938        assert_eq!(
19939            buffer.text(),
19940            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19941        );
19942    });
19943    buffer_3.update(cx, |buffer, _| {
19944        assert_eq!(
19945            buffer.text(),
19946            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19947        );
19948    });
19949
19950    fn edit_first_char_of_every_line(text: &str) -> String {
19951        text.split('\n')
19952            .map(|line| format!("X{}", &line[1..]))
19953            .collect::<Vec<_>>()
19954            .join("\n")
19955    }
19956}
19957
19958#[gpui::test]
19959async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19960    init_test(cx, |_| {});
19961
19962    let cols = 4;
19963    let rows = 10;
19964    let sample_text_1 = sample_text(rows, cols, 'a');
19965    assert_eq!(
19966        sample_text_1,
19967        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19968    );
19969    let sample_text_2 = sample_text(rows, cols, 'l');
19970    assert_eq!(
19971        sample_text_2,
19972        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19973    );
19974    let sample_text_3 = sample_text(rows, cols, 'v');
19975    assert_eq!(
19976        sample_text_3,
19977        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19978    );
19979
19980    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19981    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19982    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19983
19984    let multi_buffer = cx.new(|cx| {
19985        let mut multibuffer = MultiBuffer::new(ReadWrite);
19986        multibuffer.push_excerpts(
19987            buffer_1.clone(),
19988            [
19989                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19990                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19991                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19992            ],
19993            cx,
19994        );
19995        multibuffer.push_excerpts(
19996            buffer_2.clone(),
19997            [
19998                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19999                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20000                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20001            ],
20002            cx,
20003        );
20004        multibuffer.push_excerpts(
20005            buffer_3.clone(),
20006            [
20007                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20008                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20009                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20010            ],
20011            cx,
20012        );
20013        multibuffer
20014    });
20015
20016    let fs = FakeFs::new(cx.executor());
20017    fs.insert_tree(
20018        "/a",
20019        json!({
20020            "main.rs": sample_text_1,
20021            "other.rs": sample_text_2,
20022            "lib.rs": sample_text_3,
20023        }),
20024    )
20025    .await;
20026    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20027    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20028    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20029    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20030        Editor::new(
20031            EditorMode::full(),
20032            multi_buffer,
20033            Some(project.clone()),
20034            window,
20035            cx,
20036        )
20037    });
20038    let multibuffer_item_id = workspace
20039        .update(cx, |workspace, window, cx| {
20040            assert!(
20041                workspace.active_item(cx).is_none(),
20042                "active item should be None before the first item is added"
20043            );
20044            workspace.add_item_to_active_pane(
20045                Box::new(multi_buffer_editor.clone()),
20046                None,
20047                true,
20048                window,
20049                cx,
20050            );
20051            let active_item = workspace
20052                .active_item(cx)
20053                .expect("should have an active item after adding the multi buffer");
20054            assert_eq!(
20055                active_item.buffer_kind(cx),
20056                ItemBufferKind::Multibuffer,
20057                "A multi buffer was expected to active after adding"
20058            );
20059            active_item.item_id()
20060        })
20061        .unwrap();
20062    cx.executor().run_until_parked();
20063
20064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20065        editor.change_selections(
20066            SelectionEffects::scroll(Autoscroll::Next),
20067            window,
20068            cx,
20069            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20070        );
20071        editor.open_excerpts(&OpenExcerpts, window, cx);
20072    });
20073    cx.executor().run_until_parked();
20074    let first_item_id = workspace
20075        .update(cx, |workspace, window, cx| {
20076            let active_item = workspace
20077                .active_item(cx)
20078                .expect("should have an active item after navigating into the 1st buffer");
20079            let first_item_id = active_item.item_id();
20080            assert_ne!(
20081                first_item_id, multibuffer_item_id,
20082                "Should navigate into the 1st buffer and activate it"
20083            );
20084            assert_eq!(
20085                active_item.buffer_kind(cx),
20086                ItemBufferKind::Singleton,
20087                "New active item should be a singleton buffer"
20088            );
20089            assert_eq!(
20090                active_item
20091                    .act_as::<Editor>(cx)
20092                    .expect("should have navigated into an editor for the 1st buffer")
20093                    .read(cx)
20094                    .text(cx),
20095                sample_text_1
20096            );
20097
20098            workspace
20099                .go_back(workspace.active_pane().downgrade(), window, cx)
20100                .detach_and_log_err(cx);
20101
20102            first_item_id
20103        })
20104        .unwrap();
20105    cx.executor().run_until_parked();
20106    workspace
20107        .update(cx, |workspace, _, cx| {
20108            let active_item = workspace
20109                .active_item(cx)
20110                .expect("should have an active item after navigating back");
20111            assert_eq!(
20112                active_item.item_id(),
20113                multibuffer_item_id,
20114                "Should navigate back to the multi buffer"
20115            );
20116            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20117        })
20118        .unwrap();
20119
20120    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20121        editor.change_selections(
20122            SelectionEffects::scroll(Autoscroll::Next),
20123            window,
20124            cx,
20125            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20126        );
20127        editor.open_excerpts(&OpenExcerpts, window, cx);
20128    });
20129    cx.executor().run_until_parked();
20130    let second_item_id = workspace
20131        .update(cx, |workspace, window, cx| {
20132            let active_item = workspace
20133                .active_item(cx)
20134                .expect("should have an active item after navigating into the 2nd buffer");
20135            let second_item_id = active_item.item_id();
20136            assert_ne!(
20137                second_item_id, multibuffer_item_id,
20138                "Should navigate away from the multibuffer"
20139            );
20140            assert_ne!(
20141                second_item_id, first_item_id,
20142                "Should navigate into the 2nd buffer and activate it"
20143            );
20144            assert_eq!(
20145                active_item.buffer_kind(cx),
20146                ItemBufferKind::Singleton,
20147                "New active item should be a singleton buffer"
20148            );
20149            assert_eq!(
20150                active_item
20151                    .act_as::<Editor>(cx)
20152                    .expect("should have navigated into an editor")
20153                    .read(cx)
20154                    .text(cx),
20155                sample_text_2
20156            );
20157
20158            workspace
20159                .go_back(workspace.active_pane().downgrade(), window, cx)
20160                .detach_and_log_err(cx);
20161
20162            second_item_id
20163        })
20164        .unwrap();
20165    cx.executor().run_until_parked();
20166    workspace
20167        .update(cx, |workspace, _, cx| {
20168            let active_item = workspace
20169                .active_item(cx)
20170                .expect("should have an active item after navigating back from the 2nd buffer");
20171            assert_eq!(
20172                active_item.item_id(),
20173                multibuffer_item_id,
20174                "Should navigate back from the 2nd buffer to the multi buffer"
20175            );
20176            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20177        })
20178        .unwrap();
20179
20180    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20181        editor.change_selections(
20182            SelectionEffects::scroll(Autoscroll::Next),
20183            window,
20184            cx,
20185            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20186        );
20187        editor.open_excerpts(&OpenExcerpts, window, cx);
20188    });
20189    cx.executor().run_until_parked();
20190    workspace
20191        .update(cx, |workspace, window, cx| {
20192            let active_item = workspace
20193                .active_item(cx)
20194                .expect("should have an active item after navigating into the 3rd buffer");
20195            let third_item_id = active_item.item_id();
20196            assert_ne!(
20197                third_item_id, multibuffer_item_id,
20198                "Should navigate into the 3rd buffer and activate it"
20199            );
20200            assert_ne!(third_item_id, first_item_id);
20201            assert_ne!(third_item_id, second_item_id);
20202            assert_eq!(
20203                active_item.buffer_kind(cx),
20204                ItemBufferKind::Singleton,
20205                "New active item should be a singleton buffer"
20206            );
20207            assert_eq!(
20208                active_item
20209                    .act_as::<Editor>(cx)
20210                    .expect("should have navigated into an editor")
20211                    .read(cx)
20212                    .text(cx),
20213                sample_text_3
20214            );
20215
20216            workspace
20217                .go_back(workspace.active_pane().downgrade(), window, cx)
20218                .detach_and_log_err(cx);
20219        })
20220        .unwrap();
20221    cx.executor().run_until_parked();
20222    workspace
20223        .update(cx, |workspace, _, cx| {
20224            let active_item = workspace
20225                .active_item(cx)
20226                .expect("should have an active item after navigating back from the 3rd buffer");
20227            assert_eq!(
20228                active_item.item_id(),
20229                multibuffer_item_id,
20230                "Should navigate back from the 3rd buffer to the multi buffer"
20231            );
20232            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20233        })
20234        .unwrap();
20235}
20236
20237#[gpui::test]
20238async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20239    init_test(cx, |_| {});
20240
20241    let mut cx = EditorTestContext::new(cx).await;
20242
20243    let diff_base = r#"
20244        use some::mod;
20245
20246        const A: u32 = 42;
20247
20248        fn main() {
20249            println!("hello");
20250
20251            println!("world");
20252        }
20253        "#
20254    .unindent();
20255
20256    cx.set_state(
20257        &r#"
20258        use some::modified;
20259
20260        ˇ
20261        fn main() {
20262            println!("hello there");
20263
20264            println!("around the");
20265            println!("world");
20266        }
20267        "#
20268        .unindent(),
20269    );
20270
20271    cx.set_head_text(&diff_base);
20272    executor.run_until_parked();
20273
20274    cx.update_editor(|editor, window, cx| {
20275        editor.go_to_next_hunk(&GoToHunk, window, cx);
20276        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20277    });
20278    executor.run_until_parked();
20279    cx.assert_state_with_diff(
20280        r#"
20281          use some::modified;
20282
20283
20284          fn main() {
20285        -     println!("hello");
20286        + ˇ    println!("hello there");
20287
20288              println!("around the");
20289              println!("world");
20290          }
20291        "#
20292        .unindent(),
20293    );
20294
20295    cx.update_editor(|editor, window, cx| {
20296        for _ in 0..2 {
20297            editor.go_to_next_hunk(&GoToHunk, window, cx);
20298            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20299        }
20300    });
20301    executor.run_until_parked();
20302    cx.assert_state_with_diff(
20303        r#"
20304        - use some::mod;
20305        + ˇuse some::modified;
20306
20307
20308          fn main() {
20309        -     println!("hello");
20310        +     println!("hello there");
20311
20312        +     println!("around the");
20313              println!("world");
20314          }
20315        "#
20316        .unindent(),
20317    );
20318
20319    cx.update_editor(|editor, window, cx| {
20320        editor.go_to_next_hunk(&GoToHunk, window, cx);
20321        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20322    });
20323    executor.run_until_parked();
20324    cx.assert_state_with_diff(
20325        r#"
20326        - use some::mod;
20327        + use some::modified;
20328
20329        - const A: u32 = 42;
20330          ˇ
20331          fn main() {
20332        -     println!("hello");
20333        +     println!("hello there");
20334
20335        +     println!("around the");
20336              println!("world");
20337          }
20338        "#
20339        .unindent(),
20340    );
20341
20342    cx.update_editor(|editor, window, cx| {
20343        editor.cancel(&Cancel, window, cx);
20344    });
20345
20346    cx.assert_state_with_diff(
20347        r#"
20348          use some::modified;
20349
20350          ˇ
20351          fn main() {
20352              println!("hello there");
20353
20354              println!("around the");
20355              println!("world");
20356          }
20357        "#
20358        .unindent(),
20359    );
20360}
20361
20362#[gpui::test]
20363async fn test_diff_base_change_with_expanded_diff_hunks(
20364    executor: BackgroundExecutor,
20365    cx: &mut TestAppContext,
20366) {
20367    init_test(cx, |_| {});
20368
20369    let mut cx = EditorTestContext::new(cx).await;
20370
20371    let diff_base = r#"
20372        use some::mod1;
20373        use some::mod2;
20374
20375        const A: u32 = 42;
20376        const B: u32 = 42;
20377        const C: u32 = 42;
20378
20379        fn main() {
20380            println!("hello");
20381
20382            println!("world");
20383        }
20384        "#
20385    .unindent();
20386
20387    cx.set_state(
20388        &r#"
20389        use some::mod2;
20390
20391        const A: u32 = 42;
20392        const C: u32 = 42;
20393
20394        fn main(ˇ) {
20395            //println!("hello");
20396
20397            println!("world");
20398            //
20399            //
20400        }
20401        "#
20402        .unindent(),
20403    );
20404
20405    cx.set_head_text(&diff_base);
20406    executor.run_until_parked();
20407
20408    cx.update_editor(|editor, window, cx| {
20409        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20410    });
20411    executor.run_until_parked();
20412    cx.assert_state_with_diff(
20413        r#"
20414        - use some::mod1;
20415          use some::mod2;
20416
20417          const A: u32 = 42;
20418        - const B: u32 = 42;
20419          const C: u32 = 42;
20420
20421          fn main(ˇ) {
20422        -     println!("hello");
20423        +     //println!("hello");
20424
20425              println!("world");
20426        +     //
20427        +     //
20428          }
20429        "#
20430        .unindent(),
20431    );
20432
20433    cx.set_head_text("new diff base!");
20434    executor.run_until_parked();
20435    cx.assert_state_with_diff(
20436        r#"
20437        - new diff base!
20438        + use some::mod2;
20439        +
20440        + const A: u32 = 42;
20441        + const C: u32 = 42;
20442        +
20443        + fn main(ˇ) {
20444        +     //println!("hello");
20445        +
20446        +     println!("world");
20447        +     //
20448        +     //
20449        + }
20450        "#
20451        .unindent(),
20452    );
20453}
20454
20455#[gpui::test]
20456async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20457    init_test(cx, |_| {});
20458
20459    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20460    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20461    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20462    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20463    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20464    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20465
20466    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20467    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20468    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20469
20470    let multi_buffer = cx.new(|cx| {
20471        let mut multibuffer = MultiBuffer::new(ReadWrite);
20472        multibuffer.push_excerpts(
20473            buffer_1.clone(),
20474            [
20475                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20476                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20477                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20478            ],
20479            cx,
20480        );
20481        multibuffer.push_excerpts(
20482            buffer_2.clone(),
20483            [
20484                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20485                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20486                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20487            ],
20488            cx,
20489        );
20490        multibuffer.push_excerpts(
20491            buffer_3.clone(),
20492            [
20493                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20494                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20495                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20496            ],
20497            cx,
20498        );
20499        multibuffer
20500    });
20501
20502    let editor =
20503        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20504    editor
20505        .update(cx, |editor, _window, cx| {
20506            for (buffer, diff_base) in [
20507                (buffer_1.clone(), file_1_old),
20508                (buffer_2.clone(), file_2_old),
20509                (buffer_3.clone(), file_3_old),
20510            ] {
20511                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20512                editor
20513                    .buffer
20514                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20515            }
20516        })
20517        .unwrap();
20518
20519    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20520    cx.run_until_parked();
20521
20522    cx.assert_editor_state(
20523        &"
20524            ˇaaa
20525            ccc
20526            ddd
20527
20528            ggg
20529            hhh
20530
20531
20532            lll
20533            mmm
20534            NNN
20535
20536            qqq
20537            rrr
20538
20539            uuu
20540            111
20541            222
20542            333
20543
20544            666
20545            777
20546
20547            000
20548            !!!"
20549        .unindent(),
20550    );
20551
20552    cx.update_editor(|editor, window, cx| {
20553        editor.select_all(&SelectAll, window, cx);
20554        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20555    });
20556    cx.executor().run_until_parked();
20557
20558    cx.assert_state_with_diff(
20559        "
20560            «aaa
20561          - bbb
20562            ccc
20563            ddd
20564
20565            ggg
20566            hhh
20567
20568
20569            lll
20570            mmm
20571          - nnn
20572          + NNN
20573
20574            qqq
20575            rrr
20576
20577            uuu
20578            111
20579            222
20580            333
20581
20582          + 666
20583            777
20584
20585            000
20586            !!!ˇ»"
20587            .unindent(),
20588    );
20589}
20590
20591#[gpui::test]
20592async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20593    init_test(cx, |_| {});
20594
20595    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20596    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20597
20598    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20599    let multi_buffer = cx.new(|cx| {
20600        let mut multibuffer = MultiBuffer::new(ReadWrite);
20601        multibuffer.push_excerpts(
20602            buffer.clone(),
20603            [
20604                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20605                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20606                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20607            ],
20608            cx,
20609        );
20610        multibuffer
20611    });
20612
20613    let editor =
20614        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20615    editor
20616        .update(cx, |editor, _window, cx| {
20617            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20618            editor
20619                .buffer
20620                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20621        })
20622        .unwrap();
20623
20624    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20625    cx.run_until_parked();
20626
20627    cx.update_editor(|editor, window, cx| {
20628        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20629    });
20630    cx.executor().run_until_parked();
20631
20632    // When the start of a hunk coincides with the start of its excerpt,
20633    // the hunk is expanded. When the start of a hunk is earlier than
20634    // the start of its excerpt, the hunk is not expanded.
20635    cx.assert_state_with_diff(
20636        "
20637            ˇaaa
20638          - bbb
20639          + BBB
20640
20641          - ddd
20642          - eee
20643          + DDD
20644          + EEE
20645            fff
20646
20647            iii
20648        "
20649        .unindent(),
20650    );
20651}
20652
20653#[gpui::test]
20654async fn test_edits_around_expanded_insertion_hunks(
20655    executor: BackgroundExecutor,
20656    cx: &mut TestAppContext,
20657) {
20658    init_test(cx, |_| {});
20659
20660    let mut cx = EditorTestContext::new(cx).await;
20661
20662    let diff_base = r#"
20663        use some::mod1;
20664        use some::mod2;
20665
20666        const A: u32 = 42;
20667
20668        fn main() {
20669            println!("hello");
20670
20671            println!("world");
20672        }
20673        "#
20674    .unindent();
20675    executor.run_until_parked();
20676    cx.set_state(
20677        &r#"
20678        use some::mod1;
20679        use some::mod2;
20680
20681        const A: u32 = 42;
20682        const B: u32 = 42;
20683        const C: u32 = 42;
20684        ˇ
20685
20686        fn main() {
20687            println!("hello");
20688
20689            println!("world");
20690        }
20691        "#
20692        .unindent(),
20693    );
20694
20695    cx.set_head_text(&diff_base);
20696    executor.run_until_parked();
20697
20698    cx.update_editor(|editor, window, cx| {
20699        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20700    });
20701    executor.run_until_parked();
20702
20703    cx.assert_state_with_diff(
20704        r#"
20705        use some::mod1;
20706        use some::mod2;
20707
20708        const A: u32 = 42;
20709      + const B: u32 = 42;
20710      + const C: u32 = 42;
20711      + ˇ
20712
20713        fn main() {
20714            println!("hello");
20715
20716            println!("world");
20717        }
20718      "#
20719        .unindent(),
20720    );
20721
20722    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20723    executor.run_until_parked();
20724
20725    cx.assert_state_with_diff(
20726        r#"
20727        use some::mod1;
20728        use some::mod2;
20729
20730        const A: u32 = 42;
20731      + const B: u32 = 42;
20732      + const C: u32 = 42;
20733      + const D: u32 = 42;
20734      + ˇ
20735
20736        fn main() {
20737            println!("hello");
20738
20739            println!("world");
20740        }
20741      "#
20742        .unindent(),
20743    );
20744
20745    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20746    executor.run_until_parked();
20747
20748    cx.assert_state_with_diff(
20749        r#"
20750        use some::mod1;
20751        use some::mod2;
20752
20753        const A: u32 = 42;
20754      + const B: u32 = 42;
20755      + const C: u32 = 42;
20756      + const D: u32 = 42;
20757      + const E: u32 = 42;
20758      + ˇ
20759
20760        fn main() {
20761            println!("hello");
20762
20763            println!("world");
20764        }
20765      "#
20766        .unindent(),
20767    );
20768
20769    cx.update_editor(|editor, window, cx| {
20770        editor.delete_line(&DeleteLine, window, cx);
20771    });
20772    executor.run_until_parked();
20773
20774    cx.assert_state_with_diff(
20775        r#"
20776        use some::mod1;
20777        use some::mod2;
20778
20779        const A: u32 = 42;
20780      + const B: u32 = 42;
20781      + const C: u32 = 42;
20782      + const D: u32 = 42;
20783      + const E: u32 = 42;
20784        ˇ
20785        fn main() {
20786            println!("hello");
20787
20788            println!("world");
20789        }
20790      "#
20791        .unindent(),
20792    );
20793
20794    cx.update_editor(|editor, window, cx| {
20795        editor.move_up(&MoveUp, window, cx);
20796        editor.delete_line(&DeleteLine, window, cx);
20797        editor.move_up(&MoveUp, window, cx);
20798        editor.delete_line(&DeleteLine, window, cx);
20799        editor.move_up(&MoveUp, window, cx);
20800        editor.delete_line(&DeleteLine, window, cx);
20801    });
20802    executor.run_until_parked();
20803    cx.assert_state_with_diff(
20804        r#"
20805        use some::mod1;
20806        use some::mod2;
20807
20808        const A: u32 = 42;
20809      + const B: u32 = 42;
20810        ˇ
20811        fn main() {
20812            println!("hello");
20813
20814            println!("world");
20815        }
20816      "#
20817        .unindent(),
20818    );
20819
20820    cx.update_editor(|editor, window, cx| {
20821        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20822        editor.delete_line(&DeleteLine, window, cx);
20823    });
20824    executor.run_until_parked();
20825    cx.assert_state_with_diff(
20826        r#"
20827        ˇ
20828        fn main() {
20829            println!("hello");
20830
20831            println!("world");
20832        }
20833      "#
20834        .unindent(),
20835    );
20836}
20837
20838#[gpui::test]
20839async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20840    init_test(cx, |_| {});
20841
20842    let mut cx = EditorTestContext::new(cx).await;
20843    cx.set_head_text(indoc! { "
20844        one
20845        two
20846        three
20847        four
20848        five
20849        "
20850    });
20851    cx.set_state(indoc! { "
20852        one
20853        ˇthree
20854        five
20855    "});
20856    cx.run_until_parked();
20857    cx.update_editor(|editor, window, cx| {
20858        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20859    });
20860    cx.assert_state_with_diff(
20861        indoc! { "
20862        one
20863      - two
20864        ˇthree
20865      - four
20866        five
20867    "}
20868        .to_string(),
20869    );
20870    cx.update_editor(|editor, window, cx| {
20871        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20872    });
20873
20874    cx.assert_state_with_diff(
20875        indoc! { "
20876        one
20877        ˇthree
20878        five
20879    "}
20880        .to_string(),
20881    );
20882
20883    cx.update_editor(|editor, window, cx| {
20884        editor.move_up(&MoveUp, window, cx);
20885        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20886    });
20887    cx.assert_state_with_diff(
20888        indoc! { "
20889        ˇone
20890      - two
20891        three
20892        five
20893    "}
20894        .to_string(),
20895    );
20896
20897    cx.update_editor(|editor, window, cx| {
20898        editor.move_down(&MoveDown, window, cx);
20899        editor.move_down(&MoveDown, window, cx);
20900        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20901    });
20902    cx.assert_state_with_diff(
20903        indoc! { "
20904        one
20905      - two
20906        ˇthree
20907      - four
20908        five
20909    "}
20910        .to_string(),
20911    );
20912
20913    cx.set_state(indoc! { "
20914        one
20915        ˇTWO
20916        three
20917        four
20918        five
20919    "});
20920    cx.run_until_parked();
20921    cx.update_editor(|editor, window, cx| {
20922        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20923    });
20924
20925    cx.assert_state_with_diff(
20926        indoc! { "
20927            one
20928          - two
20929          + ˇTWO
20930            three
20931            four
20932            five
20933        "}
20934        .to_string(),
20935    );
20936    cx.update_editor(|editor, window, cx| {
20937        editor.move_up(&Default::default(), window, cx);
20938        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20939    });
20940    cx.assert_state_with_diff(
20941        indoc! { "
20942            one
20943            ˇTWO
20944            three
20945            four
20946            five
20947        "}
20948        .to_string(),
20949    );
20950}
20951
20952#[gpui::test]
20953async fn test_toggling_adjacent_diff_hunks_2(
20954    executor: BackgroundExecutor,
20955    cx: &mut TestAppContext,
20956) {
20957    init_test(cx, |_| {});
20958
20959    let mut cx = EditorTestContext::new(cx).await;
20960
20961    let diff_base = r#"
20962        lineA
20963        lineB
20964        lineC
20965        lineD
20966        "#
20967    .unindent();
20968
20969    cx.set_state(
20970        &r#"
20971        ˇlineA1
20972        lineB
20973        lineD
20974        "#
20975        .unindent(),
20976    );
20977    cx.set_head_text(&diff_base);
20978    executor.run_until_parked();
20979
20980    cx.update_editor(|editor, window, cx| {
20981        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20982    });
20983    executor.run_until_parked();
20984    cx.assert_state_with_diff(
20985        r#"
20986        - lineA
20987        + ˇlineA1
20988          lineB
20989          lineD
20990        "#
20991        .unindent(),
20992    );
20993
20994    cx.update_editor(|editor, window, cx| {
20995        editor.move_down(&MoveDown, window, cx);
20996        editor.move_right(&MoveRight, window, cx);
20997        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20998    });
20999    executor.run_until_parked();
21000    cx.assert_state_with_diff(
21001        r#"
21002        - lineA
21003        + lineA1
21004          lˇineB
21005        - lineC
21006          lineD
21007        "#
21008        .unindent(),
21009    );
21010}
21011
21012#[gpui::test]
21013async fn test_edits_around_expanded_deletion_hunks(
21014    executor: BackgroundExecutor,
21015    cx: &mut TestAppContext,
21016) {
21017    init_test(cx, |_| {});
21018
21019    let mut cx = EditorTestContext::new(cx).await;
21020
21021    let diff_base = r#"
21022        use some::mod1;
21023        use some::mod2;
21024
21025        const A: u32 = 42;
21026        const B: u32 = 42;
21027        const C: u32 = 42;
21028
21029
21030        fn main() {
21031            println!("hello");
21032
21033            println!("world");
21034        }
21035    "#
21036    .unindent();
21037    executor.run_until_parked();
21038    cx.set_state(
21039        &r#"
21040        use some::mod1;
21041        use some::mod2;
21042
21043        ˇconst B: u32 = 42;
21044        const C: u32 = 42;
21045
21046
21047        fn main() {
21048            println!("hello");
21049
21050            println!("world");
21051        }
21052        "#
21053        .unindent(),
21054    );
21055
21056    cx.set_head_text(&diff_base);
21057    executor.run_until_parked();
21058
21059    cx.update_editor(|editor, window, cx| {
21060        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21061    });
21062    executor.run_until_parked();
21063
21064    cx.assert_state_with_diff(
21065        r#"
21066        use some::mod1;
21067        use some::mod2;
21068
21069      - const A: u32 = 42;
21070        ˇconst B: u32 = 42;
21071        const C: u32 = 42;
21072
21073
21074        fn main() {
21075            println!("hello");
21076
21077            println!("world");
21078        }
21079      "#
21080        .unindent(),
21081    );
21082
21083    cx.update_editor(|editor, window, cx| {
21084        editor.delete_line(&DeleteLine, window, cx);
21085    });
21086    executor.run_until_parked();
21087    cx.assert_state_with_diff(
21088        r#"
21089        use some::mod1;
21090        use some::mod2;
21091
21092      - const A: u32 = 42;
21093      - const B: u32 = 42;
21094        ˇconst C: u32 = 42;
21095
21096
21097        fn main() {
21098            println!("hello");
21099
21100            println!("world");
21101        }
21102      "#
21103        .unindent(),
21104    );
21105
21106    cx.update_editor(|editor, window, cx| {
21107        editor.delete_line(&DeleteLine, window, cx);
21108    });
21109    executor.run_until_parked();
21110    cx.assert_state_with_diff(
21111        r#"
21112        use some::mod1;
21113        use some::mod2;
21114
21115      - const A: u32 = 42;
21116      - const B: u32 = 42;
21117      - const C: u32 = 42;
21118        ˇ
21119
21120        fn main() {
21121            println!("hello");
21122
21123            println!("world");
21124        }
21125      "#
21126        .unindent(),
21127    );
21128
21129    cx.update_editor(|editor, window, cx| {
21130        editor.handle_input("replacement", window, cx);
21131    });
21132    executor.run_until_parked();
21133    cx.assert_state_with_diff(
21134        r#"
21135        use some::mod1;
21136        use some::mod2;
21137
21138      - const A: u32 = 42;
21139      - const B: u32 = 42;
21140      - const C: u32 = 42;
21141      -
21142      + replacementˇ
21143
21144        fn main() {
21145            println!("hello");
21146
21147            println!("world");
21148        }
21149      "#
21150        .unindent(),
21151    );
21152}
21153
21154#[gpui::test]
21155async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21156    init_test(cx, |_| {});
21157
21158    let mut cx = EditorTestContext::new(cx).await;
21159
21160    let base_text = r#"
21161        one
21162        two
21163        three
21164        four
21165        five
21166    "#
21167    .unindent();
21168    executor.run_until_parked();
21169    cx.set_state(
21170        &r#"
21171        one
21172        two
21173        fˇour
21174        five
21175        "#
21176        .unindent(),
21177    );
21178
21179    cx.set_head_text(&base_text);
21180    executor.run_until_parked();
21181
21182    cx.update_editor(|editor, window, cx| {
21183        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21184    });
21185    executor.run_until_parked();
21186
21187    cx.assert_state_with_diff(
21188        r#"
21189          one
21190          two
21191        - three
21192          fˇour
21193          five
21194        "#
21195        .unindent(),
21196    );
21197
21198    cx.update_editor(|editor, window, cx| {
21199        editor.backspace(&Backspace, window, cx);
21200        editor.backspace(&Backspace, window, cx);
21201    });
21202    executor.run_until_parked();
21203    cx.assert_state_with_diff(
21204        r#"
21205          one
21206          two
21207        - threeˇ
21208        - four
21209        + our
21210          five
21211        "#
21212        .unindent(),
21213    );
21214}
21215
21216#[gpui::test]
21217async fn test_edit_after_expanded_modification_hunk(
21218    executor: BackgroundExecutor,
21219    cx: &mut TestAppContext,
21220) {
21221    init_test(cx, |_| {});
21222
21223    let mut cx = EditorTestContext::new(cx).await;
21224
21225    let diff_base = r#"
21226        use some::mod1;
21227        use some::mod2;
21228
21229        const A: u32 = 42;
21230        const B: u32 = 42;
21231        const C: u32 = 42;
21232        const D: u32 = 42;
21233
21234
21235        fn main() {
21236            println!("hello");
21237
21238            println!("world");
21239        }"#
21240    .unindent();
21241
21242    cx.set_state(
21243        &r#"
21244        use some::mod1;
21245        use some::mod2;
21246
21247        const A: u32 = 42;
21248        const B: u32 = 42;
21249        const C: u32 = 43ˇ
21250        const D: u32 = 42;
21251
21252
21253        fn main() {
21254            println!("hello");
21255
21256            println!("world");
21257        }"#
21258        .unindent(),
21259    );
21260
21261    cx.set_head_text(&diff_base);
21262    executor.run_until_parked();
21263    cx.update_editor(|editor, window, cx| {
21264        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21265    });
21266    executor.run_until_parked();
21267
21268    cx.assert_state_with_diff(
21269        r#"
21270        use some::mod1;
21271        use some::mod2;
21272
21273        const A: u32 = 42;
21274        const B: u32 = 42;
21275      - const C: u32 = 42;
21276      + const C: u32 = 43ˇ
21277        const D: u32 = 42;
21278
21279
21280        fn main() {
21281            println!("hello");
21282
21283            println!("world");
21284        }"#
21285        .unindent(),
21286    );
21287
21288    cx.update_editor(|editor, window, cx| {
21289        editor.handle_input("\nnew_line\n", window, cx);
21290    });
21291    executor.run_until_parked();
21292
21293    cx.assert_state_with_diff(
21294        r#"
21295        use some::mod1;
21296        use some::mod2;
21297
21298        const A: u32 = 42;
21299        const B: u32 = 42;
21300      - const C: u32 = 42;
21301      + const C: u32 = 43
21302      + new_line
21303      + ˇ
21304        const D: u32 = 42;
21305
21306
21307        fn main() {
21308            println!("hello");
21309
21310            println!("world");
21311        }"#
21312        .unindent(),
21313    );
21314}
21315
21316#[gpui::test]
21317async fn test_stage_and_unstage_added_file_hunk(
21318    executor: BackgroundExecutor,
21319    cx: &mut TestAppContext,
21320) {
21321    init_test(cx, |_| {});
21322
21323    let mut cx = EditorTestContext::new(cx).await;
21324    cx.update_editor(|editor, _, cx| {
21325        editor.set_expand_all_diff_hunks(cx);
21326    });
21327
21328    let working_copy = r#"
21329            ˇfn main() {
21330                println!("hello, world!");
21331            }
21332        "#
21333    .unindent();
21334
21335    cx.set_state(&working_copy);
21336    executor.run_until_parked();
21337
21338    cx.assert_state_with_diff(
21339        r#"
21340            + ˇfn main() {
21341            +     println!("hello, world!");
21342            + }
21343        "#
21344        .unindent(),
21345    );
21346    cx.assert_index_text(None);
21347
21348    cx.update_editor(|editor, window, cx| {
21349        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21350    });
21351    executor.run_until_parked();
21352    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21353    cx.assert_state_with_diff(
21354        r#"
21355            + ˇfn main() {
21356            +     println!("hello, world!");
21357            + }
21358        "#
21359        .unindent(),
21360    );
21361
21362    cx.update_editor(|editor, window, cx| {
21363        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21364    });
21365    executor.run_until_parked();
21366    cx.assert_index_text(None);
21367}
21368
21369async fn setup_indent_guides_editor(
21370    text: &str,
21371    cx: &mut TestAppContext,
21372) -> (BufferId, EditorTestContext) {
21373    init_test(cx, |_| {});
21374
21375    let mut cx = EditorTestContext::new(cx).await;
21376
21377    let buffer_id = cx.update_editor(|editor, window, cx| {
21378        editor.set_text(text, window, cx);
21379        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21380
21381        buffer_ids[0]
21382    });
21383
21384    (buffer_id, cx)
21385}
21386
21387fn assert_indent_guides(
21388    range: Range<u32>,
21389    expected: Vec<IndentGuide>,
21390    active_indices: Option<Vec<usize>>,
21391    cx: &mut EditorTestContext,
21392) {
21393    let indent_guides = cx.update_editor(|editor, window, cx| {
21394        let snapshot = editor.snapshot(window, cx).display_snapshot;
21395        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21396            editor,
21397            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21398            true,
21399            &snapshot,
21400            cx,
21401        );
21402
21403        indent_guides.sort_by(|a, b| {
21404            a.depth.cmp(&b.depth).then(
21405                a.start_row
21406                    .cmp(&b.start_row)
21407                    .then(a.end_row.cmp(&b.end_row)),
21408            )
21409        });
21410        indent_guides
21411    });
21412
21413    if let Some(expected) = active_indices {
21414        let active_indices = cx.update_editor(|editor, window, cx| {
21415            let snapshot = editor.snapshot(window, cx).display_snapshot;
21416            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21417        });
21418
21419        assert_eq!(
21420            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21421            expected,
21422            "Active indent guide indices do not match"
21423        );
21424    }
21425
21426    assert_eq!(indent_guides, expected, "Indent guides do not match");
21427}
21428
21429fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21430    IndentGuide {
21431        buffer_id,
21432        start_row: MultiBufferRow(start_row),
21433        end_row: MultiBufferRow(end_row),
21434        depth,
21435        tab_size: 4,
21436        settings: IndentGuideSettings {
21437            enabled: true,
21438            line_width: 1,
21439            active_line_width: 1,
21440            coloring: IndentGuideColoring::default(),
21441            background_coloring: IndentGuideBackgroundColoring::default(),
21442        },
21443    }
21444}
21445
21446#[gpui::test]
21447async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21448    let (buffer_id, mut cx) = setup_indent_guides_editor(
21449        &"
21450        fn main() {
21451            let a = 1;
21452        }"
21453        .unindent(),
21454        cx,
21455    )
21456    .await;
21457
21458    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21459}
21460
21461#[gpui::test]
21462async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21463    let (buffer_id, mut cx) = setup_indent_guides_editor(
21464        &"
21465        fn main() {
21466            let a = 1;
21467            let b = 2;
21468        }"
21469        .unindent(),
21470        cx,
21471    )
21472    .await;
21473
21474    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21475}
21476
21477#[gpui::test]
21478async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21479    let (buffer_id, mut cx) = setup_indent_guides_editor(
21480        &"
21481        fn main() {
21482            let a = 1;
21483            if a == 3 {
21484                let b = 2;
21485            } else {
21486                let c = 3;
21487            }
21488        }"
21489        .unindent(),
21490        cx,
21491    )
21492    .await;
21493
21494    assert_indent_guides(
21495        0..8,
21496        vec![
21497            indent_guide(buffer_id, 1, 6, 0),
21498            indent_guide(buffer_id, 3, 3, 1),
21499            indent_guide(buffer_id, 5, 5, 1),
21500        ],
21501        None,
21502        &mut cx,
21503    );
21504}
21505
21506#[gpui::test]
21507async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21508    let (buffer_id, mut cx) = setup_indent_guides_editor(
21509        &"
21510        fn main() {
21511            let a = 1;
21512                let b = 2;
21513            let c = 3;
21514        }"
21515        .unindent(),
21516        cx,
21517    )
21518    .await;
21519
21520    assert_indent_guides(
21521        0..5,
21522        vec![
21523            indent_guide(buffer_id, 1, 3, 0),
21524            indent_guide(buffer_id, 2, 2, 1),
21525        ],
21526        None,
21527        &mut cx,
21528    );
21529}
21530
21531#[gpui::test]
21532async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21533    let (buffer_id, mut cx) = setup_indent_guides_editor(
21534        &"
21535        fn main() {
21536            let a = 1;
21537
21538            let c = 3;
21539        }"
21540        .unindent(),
21541        cx,
21542    )
21543    .await;
21544
21545    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21546}
21547
21548#[gpui::test]
21549async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21550    let (buffer_id, mut cx) = setup_indent_guides_editor(
21551        &"
21552        fn main() {
21553            let a = 1;
21554
21555            let c = 3;
21556
21557            if a == 3 {
21558                let b = 2;
21559            } else {
21560                let c = 3;
21561            }
21562        }"
21563        .unindent(),
21564        cx,
21565    )
21566    .await;
21567
21568    assert_indent_guides(
21569        0..11,
21570        vec![
21571            indent_guide(buffer_id, 1, 9, 0),
21572            indent_guide(buffer_id, 6, 6, 1),
21573            indent_guide(buffer_id, 8, 8, 1),
21574        ],
21575        None,
21576        &mut cx,
21577    );
21578}
21579
21580#[gpui::test]
21581async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21582    let (buffer_id, mut cx) = setup_indent_guides_editor(
21583        &"
21584        fn main() {
21585            let a = 1;
21586
21587            let c = 3;
21588
21589            if a == 3 {
21590                let b = 2;
21591            } else {
21592                let c = 3;
21593            }
21594        }"
21595        .unindent(),
21596        cx,
21597    )
21598    .await;
21599
21600    assert_indent_guides(
21601        1..11,
21602        vec![
21603            indent_guide(buffer_id, 1, 9, 0),
21604            indent_guide(buffer_id, 6, 6, 1),
21605            indent_guide(buffer_id, 8, 8, 1),
21606        ],
21607        None,
21608        &mut cx,
21609    );
21610}
21611
21612#[gpui::test]
21613async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21614    let (buffer_id, mut cx) = setup_indent_guides_editor(
21615        &"
21616        fn main() {
21617            let a = 1;
21618
21619            let c = 3;
21620
21621            if a == 3 {
21622                let b = 2;
21623            } else {
21624                let c = 3;
21625            }
21626        }"
21627        .unindent(),
21628        cx,
21629    )
21630    .await;
21631
21632    assert_indent_guides(
21633        1..10,
21634        vec![
21635            indent_guide(buffer_id, 1, 9, 0),
21636            indent_guide(buffer_id, 6, 6, 1),
21637            indent_guide(buffer_id, 8, 8, 1),
21638        ],
21639        None,
21640        &mut cx,
21641    );
21642}
21643
21644#[gpui::test]
21645async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21646    let (buffer_id, mut cx) = setup_indent_guides_editor(
21647        &"
21648        fn main() {
21649            if a {
21650                b(
21651                    c,
21652                    d,
21653                )
21654            } else {
21655                e(
21656                    f
21657                )
21658            }
21659        }"
21660        .unindent(),
21661        cx,
21662    )
21663    .await;
21664
21665    assert_indent_guides(
21666        0..11,
21667        vec![
21668            indent_guide(buffer_id, 1, 10, 0),
21669            indent_guide(buffer_id, 2, 5, 1),
21670            indent_guide(buffer_id, 7, 9, 1),
21671            indent_guide(buffer_id, 3, 4, 2),
21672            indent_guide(buffer_id, 8, 8, 2),
21673        ],
21674        None,
21675        &mut cx,
21676    );
21677
21678    cx.update_editor(|editor, window, cx| {
21679        editor.fold_at(MultiBufferRow(2), window, cx);
21680        assert_eq!(
21681            editor.display_text(cx),
21682            "
21683            fn main() {
21684                if a {
21685                    b(⋯
21686                    )
21687                } else {
21688                    e(
21689                        f
21690                    )
21691                }
21692            }"
21693            .unindent()
21694        );
21695    });
21696
21697    assert_indent_guides(
21698        0..11,
21699        vec![
21700            indent_guide(buffer_id, 1, 10, 0),
21701            indent_guide(buffer_id, 2, 5, 1),
21702            indent_guide(buffer_id, 7, 9, 1),
21703            indent_guide(buffer_id, 8, 8, 2),
21704        ],
21705        None,
21706        &mut cx,
21707    );
21708}
21709
21710#[gpui::test]
21711async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21712    let (buffer_id, mut cx) = setup_indent_guides_editor(
21713        &"
21714        block1
21715            block2
21716                block3
21717                    block4
21718            block2
21719        block1
21720        block1"
21721            .unindent(),
21722        cx,
21723    )
21724    .await;
21725
21726    assert_indent_guides(
21727        1..10,
21728        vec![
21729            indent_guide(buffer_id, 1, 4, 0),
21730            indent_guide(buffer_id, 2, 3, 1),
21731            indent_guide(buffer_id, 3, 3, 2),
21732        ],
21733        None,
21734        &mut cx,
21735    );
21736}
21737
21738#[gpui::test]
21739async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21740    let (buffer_id, mut cx) = setup_indent_guides_editor(
21741        &"
21742        block1
21743            block2
21744                block3
21745
21746        block1
21747        block1"
21748            .unindent(),
21749        cx,
21750    )
21751    .await;
21752
21753    assert_indent_guides(
21754        0..6,
21755        vec![
21756            indent_guide(buffer_id, 1, 2, 0),
21757            indent_guide(buffer_id, 2, 2, 1),
21758        ],
21759        None,
21760        &mut cx,
21761    );
21762}
21763
21764#[gpui::test]
21765async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21766    let (buffer_id, mut cx) = setup_indent_guides_editor(
21767        &"
21768        function component() {
21769        \treturn (
21770        \t\t\t
21771        \t\t<div>
21772        \t\t\t<abc></abc>
21773        \t\t</div>
21774        \t)
21775        }"
21776        .unindent(),
21777        cx,
21778    )
21779    .await;
21780
21781    assert_indent_guides(
21782        0..8,
21783        vec![
21784            indent_guide(buffer_id, 1, 6, 0),
21785            indent_guide(buffer_id, 2, 5, 1),
21786            indent_guide(buffer_id, 4, 4, 2),
21787        ],
21788        None,
21789        &mut cx,
21790    );
21791}
21792
21793#[gpui::test]
21794async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21795    let (buffer_id, mut cx) = setup_indent_guides_editor(
21796        &"
21797        function component() {
21798        \treturn (
21799        \t
21800        \t\t<div>
21801        \t\t\t<abc></abc>
21802        \t\t</div>
21803        \t)
21804        }"
21805        .unindent(),
21806        cx,
21807    )
21808    .await;
21809
21810    assert_indent_guides(
21811        0..8,
21812        vec![
21813            indent_guide(buffer_id, 1, 6, 0),
21814            indent_guide(buffer_id, 2, 5, 1),
21815            indent_guide(buffer_id, 4, 4, 2),
21816        ],
21817        None,
21818        &mut cx,
21819    );
21820}
21821
21822#[gpui::test]
21823async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21824    let (buffer_id, mut cx) = setup_indent_guides_editor(
21825        &"
21826        block1
21827
21828
21829
21830            block2
21831        "
21832        .unindent(),
21833        cx,
21834    )
21835    .await;
21836
21837    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21838}
21839
21840#[gpui::test]
21841async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21842    let (buffer_id, mut cx) = setup_indent_guides_editor(
21843        &"
21844        def a:
21845        \tb = 3
21846        \tif True:
21847        \t\tc = 4
21848        \t\td = 5
21849        \tprint(b)
21850        "
21851        .unindent(),
21852        cx,
21853    )
21854    .await;
21855
21856    assert_indent_guides(
21857        0..6,
21858        vec![
21859            indent_guide(buffer_id, 1, 5, 0),
21860            indent_guide(buffer_id, 3, 4, 1),
21861        ],
21862        None,
21863        &mut cx,
21864    );
21865}
21866
21867#[gpui::test]
21868async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21869    let (buffer_id, mut cx) = setup_indent_guides_editor(
21870        &"
21871    fn main() {
21872        let a = 1;
21873    }"
21874        .unindent(),
21875        cx,
21876    )
21877    .await;
21878
21879    cx.update_editor(|editor, window, cx| {
21880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21881            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21882        });
21883    });
21884
21885    assert_indent_guides(
21886        0..3,
21887        vec![indent_guide(buffer_id, 1, 1, 0)],
21888        Some(vec![0]),
21889        &mut cx,
21890    );
21891}
21892
21893#[gpui::test]
21894async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21895    let (buffer_id, mut cx) = setup_indent_guides_editor(
21896        &"
21897    fn main() {
21898        if 1 == 2 {
21899            let a = 1;
21900        }
21901    }"
21902        .unindent(),
21903        cx,
21904    )
21905    .await;
21906
21907    cx.update_editor(|editor, window, cx| {
21908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21909            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21910        });
21911    });
21912
21913    assert_indent_guides(
21914        0..4,
21915        vec![
21916            indent_guide(buffer_id, 1, 3, 0),
21917            indent_guide(buffer_id, 2, 2, 1),
21918        ],
21919        Some(vec![1]),
21920        &mut cx,
21921    );
21922
21923    cx.update_editor(|editor, window, cx| {
21924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21925            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21926        });
21927    });
21928
21929    assert_indent_guides(
21930        0..4,
21931        vec![
21932            indent_guide(buffer_id, 1, 3, 0),
21933            indent_guide(buffer_id, 2, 2, 1),
21934        ],
21935        Some(vec![1]),
21936        &mut cx,
21937    );
21938
21939    cx.update_editor(|editor, window, cx| {
21940        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21941            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21942        });
21943    });
21944
21945    assert_indent_guides(
21946        0..4,
21947        vec![
21948            indent_guide(buffer_id, 1, 3, 0),
21949            indent_guide(buffer_id, 2, 2, 1),
21950        ],
21951        Some(vec![0]),
21952        &mut cx,
21953    );
21954}
21955
21956#[gpui::test]
21957async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21958    let (buffer_id, mut cx) = setup_indent_guides_editor(
21959        &"
21960    fn main() {
21961        let a = 1;
21962
21963        let b = 2;
21964    }"
21965        .unindent(),
21966        cx,
21967    )
21968    .await;
21969
21970    cx.update_editor(|editor, window, cx| {
21971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21972            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21973        });
21974    });
21975
21976    assert_indent_guides(
21977        0..5,
21978        vec![indent_guide(buffer_id, 1, 3, 0)],
21979        Some(vec![0]),
21980        &mut cx,
21981    );
21982}
21983
21984#[gpui::test]
21985async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21986    let (buffer_id, mut cx) = setup_indent_guides_editor(
21987        &"
21988    def m:
21989        a = 1
21990        pass"
21991            .unindent(),
21992        cx,
21993    )
21994    .await;
21995
21996    cx.update_editor(|editor, window, cx| {
21997        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21998            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21999        });
22000    });
22001
22002    assert_indent_guides(
22003        0..3,
22004        vec![indent_guide(buffer_id, 1, 2, 0)],
22005        Some(vec![0]),
22006        &mut cx,
22007    );
22008}
22009
22010#[gpui::test]
22011async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22012    init_test(cx, |_| {});
22013    let mut cx = EditorTestContext::new(cx).await;
22014    let text = indoc! {
22015        "
22016        impl A {
22017            fn b() {
22018                0;
22019                3;
22020                5;
22021                6;
22022                7;
22023            }
22024        }
22025        "
22026    };
22027    let base_text = indoc! {
22028        "
22029        impl A {
22030            fn b() {
22031                0;
22032                1;
22033                2;
22034                3;
22035                4;
22036            }
22037            fn c() {
22038                5;
22039                6;
22040                7;
22041            }
22042        }
22043        "
22044    };
22045
22046    cx.update_editor(|editor, window, cx| {
22047        editor.set_text(text, window, cx);
22048
22049        editor.buffer().update(cx, |multibuffer, cx| {
22050            let buffer = multibuffer.as_singleton().unwrap();
22051            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
22052
22053            multibuffer.set_all_diff_hunks_expanded(cx);
22054            multibuffer.add_diff(diff, cx);
22055
22056            buffer.read(cx).remote_id()
22057        })
22058    });
22059    cx.run_until_parked();
22060
22061    cx.assert_state_with_diff(
22062        indoc! { "
22063          impl A {
22064              fn b() {
22065                  0;
22066        -         1;
22067        -         2;
22068                  3;
22069        -         4;
22070        -     }
22071        -     fn c() {
22072                  5;
22073                  6;
22074                  7;
22075              }
22076          }
22077          ˇ"
22078        }
22079        .to_string(),
22080    );
22081
22082    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22083        editor
22084            .snapshot(window, cx)
22085            .buffer_snapshot()
22086            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22087            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22088            .collect::<Vec<_>>()
22089    });
22090    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22091    assert_eq!(
22092        actual_guides,
22093        vec![
22094            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22095            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22096            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22097        ]
22098    );
22099}
22100
22101#[gpui::test]
22102async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22103    init_test(cx, |_| {});
22104    let mut cx = EditorTestContext::new(cx).await;
22105
22106    let diff_base = r#"
22107        a
22108        b
22109        c
22110        "#
22111    .unindent();
22112
22113    cx.set_state(
22114        &r#"
22115        ˇA
22116        b
22117        C
22118        "#
22119        .unindent(),
22120    );
22121    cx.set_head_text(&diff_base);
22122    cx.update_editor(|editor, window, cx| {
22123        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22124    });
22125    executor.run_until_parked();
22126
22127    let both_hunks_expanded = r#"
22128        - a
22129        + ˇA
22130          b
22131        - c
22132        + C
22133        "#
22134    .unindent();
22135
22136    cx.assert_state_with_diff(both_hunks_expanded.clone());
22137
22138    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22139        let snapshot = editor.snapshot(window, cx);
22140        let hunks = editor
22141            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22142            .collect::<Vec<_>>();
22143        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22144        hunks
22145            .into_iter()
22146            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22147            .collect::<Vec<_>>()
22148    });
22149    assert_eq!(hunk_ranges.len(), 2);
22150
22151    cx.update_editor(|editor, _, cx| {
22152        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22153    });
22154    executor.run_until_parked();
22155
22156    let second_hunk_expanded = r#"
22157          ˇA
22158          b
22159        - c
22160        + C
22161        "#
22162    .unindent();
22163
22164    cx.assert_state_with_diff(second_hunk_expanded);
22165
22166    cx.update_editor(|editor, _, cx| {
22167        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22168    });
22169    executor.run_until_parked();
22170
22171    cx.assert_state_with_diff(both_hunks_expanded.clone());
22172
22173    cx.update_editor(|editor, _, cx| {
22174        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22175    });
22176    executor.run_until_parked();
22177
22178    let first_hunk_expanded = r#"
22179        - a
22180        + ˇA
22181          b
22182          C
22183        "#
22184    .unindent();
22185
22186    cx.assert_state_with_diff(first_hunk_expanded);
22187
22188    cx.update_editor(|editor, _, cx| {
22189        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22190    });
22191    executor.run_until_parked();
22192
22193    cx.assert_state_with_diff(both_hunks_expanded);
22194
22195    cx.set_state(
22196        &r#"
22197        ˇA
22198        b
22199        "#
22200        .unindent(),
22201    );
22202    cx.run_until_parked();
22203
22204    // TODO this cursor position seems bad
22205    cx.assert_state_with_diff(
22206        r#"
22207        - ˇa
22208        + A
22209          b
22210        "#
22211        .unindent(),
22212    );
22213
22214    cx.update_editor(|editor, window, cx| {
22215        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22216    });
22217
22218    cx.assert_state_with_diff(
22219        r#"
22220            - ˇa
22221            + A
22222              b
22223            - c
22224            "#
22225        .unindent(),
22226    );
22227
22228    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22229        let snapshot = editor.snapshot(window, cx);
22230        let hunks = editor
22231            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22232            .collect::<Vec<_>>();
22233        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22234        hunks
22235            .into_iter()
22236            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22237            .collect::<Vec<_>>()
22238    });
22239    assert_eq!(hunk_ranges.len(), 2);
22240
22241    cx.update_editor(|editor, _, cx| {
22242        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22243    });
22244    executor.run_until_parked();
22245
22246    cx.assert_state_with_diff(
22247        r#"
22248        - ˇa
22249        + A
22250          b
22251        "#
22252        .unindent(),
22253    );
22254}
22255
22256#[gpui::test]
22257async fn test_toggle_deletion_hunk_at_start_of_file(
22258    executor: BackgroundExecutor,
22259    cx: &mut TestAppContext,
22260) {
22261    init_test(cx, |_| {});
22262    let mut cx = EditorTestContext::new(cx).await;
22263
22264    let diff_base = r#"
22265        a
22266        b
22267        c
22268        "#
22269    .unindent();
22270
22271    cx.set_state(
22272        &r#"
22273        ˇb
22274        c
22275        "#
22276        .unindent(),
22277    );
22278    cx.set_head_text(&diff_base);
22279    cx.update_editor(|editor, window, cx| {
22280        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22281    });
22282    executor.run_until_parked();
22283
22284    let hunk_expanded = r#"
22285        - a
22286          ˇb
22287          c
22288        "#
22289    .unindent();
22290
22291    cx.assert_state_with_diff(hunk_expanded.clone());
22292
22293    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22294        let snapshot = editor.snapshot(window, cx);
22295        let hunks = editor
22296            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22297            .collect::<Vec<_>>();
22298        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22299        hunks
22300            .into_iter()
22301            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22302            .collect::<Vec<_>>()
22303    });
22304    assert_eq!(hunk_ranges.len(), 1);
22305
22306    cx.update_editor(|editor, _, cx| {
22307        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22308    });
22309    executor.run_until_parked();
22310
22311    let hunk_collapsed = r#"
22312          ˇb
22313          c
22314        "#
22315    .unindent();
22316
22317    cx.assert_state_with_diff(hunk_collapsed);
22318
22319    cx.update_editor(|editor, _, cx| {
22320        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22321    });
22322    executor.run_until_parked();
22323
22324    cx.assert_state_with_diff(hunk_expanded);
22325}
22326
22327#[gpui::test]
22328async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22329    executor: BackgroundExecutor,
22330    cx: &mut TestAppContext,
22331) {
22332    init_test(cx, |_| {});
22333    let mut cx = EditorTestContext::new(cx).await;
22334
22335    cx.set_state("ˇnew\nsecond\nthird\n");
22336    cx.set_head_text("old\nsecond\nthird\n");
22337    cx.update_editor(|editor, window, cx| {
22338        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22339    });
22340    executor.run_until_parked();
22341    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22342
22343    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22344    cx.update_editor(|editor, window, cx| {
22345        let snapshot = editor.snapshot(window, cx);
22346        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22347        let hunks = editor
22348            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22349            .collect::<Vec<_>>();
22350        assert_eq!(hunks.len(), 1);
22351        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22352        editor.toggle_single_diff_hunk(hunk_range, cx)
22353    });
22354    executor.run_until_parked();
22355    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22356
22357    // Keep the editor scrolled to the top so the full hunk remains visible.
22358    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22359}
22360
22361#[gpui::test]
22362async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22363    init_test(cx, |_| {});
22364
22365    let fs = FakeFs::new(cx.executor());
22366    fs.insert_tree(
22367        path!("/test"),
22368        json!({
22369            ".git": {},
22370            "file-1": "ONE\n",
22371            "file-2": "TWO\n",
22372            "file-3": "THREE\n",
22373        }),
22374    )
22375    .await;
22376
22377    fs.set_head_for_repo(
22378        path!("/test/.git").as_ref(),
22379        &[
22380            ("file-1", "one\n".into()),
22381            ("file-2", "two\n".into()),
22382            ("file-3", "three\n".into()),
22383        ],
22384        "deadbeef",
22385    );
22386
22387    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22388    let mut buffers = vec![];
22389    for i in 1..=3 {
22390        let buffer = project
22391            .update(cx, |project, cx| {
22392                let path = format!(path!("/test/file-{}"), i);
22393                project.open_local_buffer(path, cx)
22394            })
22395            .await
22396            .unwrap();
22397        buffers.push(buffer);
22398    }
22399
22400    let multibuffer = cx.new(|cx| {
22401        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22402        multibuffer.set_all_diff_hunks_expanded(cx);
22403        for buffer in &buffers {
22404            let snapshot = buffer.read(cx).snapshot();
22405            multibuffer.set_excerpts_for_path(
22406                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22407                buffer.clone(),
22408                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22409                2,
22410                cx,
22411            );
22412        }
22413        multibuffer
22414    });
22415
22416    let editor = cx.add_window(|window, cx| {
22417        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22418    });
22419    cx.run_until_parked();
22420
22421    let snapshot = editor
22422        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22423        .unwrap();
22424    let hunks = snapshot
22425        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22426        .map(|hunk| match hunk {
22427            DisplayDiffHunk::Unfolded {
22428                display_row_range, ..
22429            } => display_row_range,
22430            DisplayDiffHunk::Folded { .. } => unreachable!(),
22431        })
22432        .collect::<Vec<_>>();
22433    assert_eq!(
22434        hunks,
22435        [
22436            DisplayRow(2)..DisplayRow(4),
22437            DisplayRow(7)..DisplayRow(9),
22438            DisplayRow(12)..DisplayRow(14),
22439        ]
22440    );
22441}
22442
22443#[gpui::test]
22444async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22445    init_test(cx, |_| {});
22446
22447    let mut cx = EditorTestContext::new(cx).await;
22448    cx.set_head_text(indoc! { "
22449        one
22450        two
22451        three
22452        four
22453        five
22454        "
22455    });
22456    cx.set_index_text(indoc! { "
22457        one
22458        two
22459        three
22460        four
22461        five
22462        "
22463    });
22464    cx.set_state(indoc! {"
22465        one
22466        TWO
22467        ˇTHREE
22468        FOUR
22469        five
22470    "});
22471    cx.run_until_parked();
22472    cx.update_editor(|editor, window, cx| {
22473        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22474    });
22475    cx.run_until_parked();
22476    cx.assert_index_text(Some(indoc! {"
22477        one
22478        TWO
22479        THREE
22480        FOUR
22481        five
22482    "}));
22483    cx.set_state(indoc! { "
22484        one
22485        TWO
22486        ˇTHREE-HUNDRED
22487        FOUR
22488        five
22489    "});
22490    cx.run_until_parked();
22491    cx.update_editor(|editor, window, cx| {
22492        let snapshot = editor.snapshot(window, cx);
22493        let hunks = editor
22494            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22495            .collect::<Vec<_>>();
22496        assert_eq!(hunks.len(), 1);
22497        assert_eq!(
22498            hunks[0].status(),
22499            DiffHunkStatus {
22500                kind: DiffHunkStatusKind::Modified,
22501                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22502            }
22503        );
22504
22505        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22506    });
22507    cx.run_until_parked();
22508    cx.assert_index_text(Some(indoc! {"
22509        one
22510        TWO
22511        THREE-HUNDRED
22512        FOUR
22513        five
22514    "}));
22515}
22516
22517#[gpui::test]
22518fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22519    init_test(cx, |_| {});
22520
22521    let editor = cx.add_window(|window, cx| {
22522        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22523        build_editor(buffer, window, cx)
22524    });
22525
22526    let render_args = Arc::new(Mutex::new(None));
22527    let snapshot = editor
22528        .update(cx, |editor, window, cx| {
22529            let snapshot = editor.buffer().read(cx).snapshot(cx);
22530            let range =
22531                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22532
22533            struct RenderArgs {
22534                row: MultiBufferRow,
22535                folded: bool,
22536                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22537            }
22538
22539            let crease = Crease::inline(
22540                range,
22541                FoldPlaceholder::test(),
22542                {
22543                    let toggle_callback = render_args.clone();
22544                    move |row, folded, callback, _window, _cx| {
22545                        *toggle_callback.lock() = Some(RenderArgs {
22546                            row,
22547                            folded,
22548                            callback,
22549                        });
22550                        div()
22551                    }
22552                },
22553                |_row, _folded, _window, _cx| div(),
22554            );
22555
22556            editor.insert_creases(Some(crease), cx);
22557            let snapshot = editor.snapshot(window, cx);
22558            let _div =
22559                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22560            snapshot
22561        })
22562        .unwrap();
22563
22564    let render_args = render_args.lock().take().unwrap();
22565    assert_eq!(render_args.row, MultiBufferRow(1));
22566    assert!(!render_args.folded);
22567    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22568
22569    cx.update_window(*editor, |_, window, cx| {
22570        (render_args.callback)(true, window, cx)
22571    })
22572    .unwrap();
22573    let snapshot = editor
22574        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22575        .unwrap();
22576    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22577
22578    cx.update_window(*editor, |_, window, cx| {
22579        (render_args.callback)(false, window, cx)
22580    })
22581    .unwrap();
22582    let snapshot = editor
22583        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22584        .unwrap();
22585    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22586}
22587
22588#[gpui::test]
22589async fn test_input_text(cx: &mut TestAppContext) {
22590    init_test(cx, |_| {});
22591    let mut cx = EditorTestContext::new(cx).await;
22592
22593    cx.set_state(
22594        &r#"ˇone
22595        two
22596
22597        three
22598        fourˇ
22599        five
22600
22601        siˇx"#
22602            .unindent(),
22603    );
22604
22605    cx.dispatch_action(HandleInput(String::new()));
22606    cx.assert_editor_state(
22607        &r#"ˇone
22608        two
22609
22610        three
22611        fourˇ
22612        five
22613
22614        siˇx"#
22615            .unindent(),
22616    );
22617
22618    cx.dispatch_action(HandleInput("AAAA".to_string()));
22619    cx.assert_editor_state(
22620        &r#"AAAAˇone
22621        two
22622
22623        three
22624        fourAAAAˇ
22625        five
22626
22627        siAAAAˇx"#
22628            .unindent(),
22629    );
22630}
22631
22632#[gpui::test]
22633async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22634    init_test(cx, |_| {});
22635
22636    let mut cx = EditorTestContext::new(cx).await;
22637    cx.set_state(
22638        r#"let foo = 1;
22639let foo = 2;
22640let foo = 3;
22641let fooˇ = 4;
22642let foo = 5;
22643let foo = 6;
22644let foo = 7;
22645let foo = 8;
22646let foo = 9;
22647let foo = 10;
22648let foo = 11;
22649let foo = 12;
22650let foo = 13;
22651let foo = 14;
22652let foo = 15;"#,
22653    );
22654
22655    cx.update_editor(|e, window, cx| {
22656        assert_eq!(
22657            e.next_scroll_position,
22658            NextScrollCursorCenterTopBottom::Center,
22659            "Default next scroll direction is center",
22660        );
22661
22662        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22663        assert_eq!(
22664            e.next_scroll_position,
22665            NextScrollCursorCenterTopBottom::Top,
22666            "After center, next scroll direction should be top",
22667        );
22668
22669        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22670        assert_eq!(
22671            e.next_scroll_position,
22672            NextScrollCursorCenterTopBottom::Bottom,
22673            "After top, next scroll direction should be bottom",
22674        );
22675
22676        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22677        assert_eq!(
22678            e.next_scroll_position,
22679            NextScrollCursorCenterTopBottom::Center,
22680            "After bottom, scrolling should start over",
22681        );
22682
22683        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22684        assert_eq!(
22685            e.next_scroll_position,
22686            NextScrollCursorCenterTopBottom::Top,
22687            "Scrolling continues if retriggered fast enough"
22688        );
22689    });
22690
22691    cx.executor()
22692        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22693    cx.executor().run_until_parked();
22694    cx.update_editor(|e, _, _| {
22695        assert_eq!(
22696            e.next_scroll_position,
22697            NextScrollCursorCenterTopBottom::Center,
22698            "If scrolling is not triggered fast enough, it should reset"
22699        );
22700    });
22701}
22702
22703#[gpui::test]
22704async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22705    init_test(cx, |_| {});
22706    let mut cx = EditorLspTestContext::new_rust(
22707        lsp::ServerCapabilities {
22708            definition_provider: Some(lsp::OneOf::Left(true)),
22709            references_provider: Some(lsp::OneOf::Left(true)),
22710            ..lsp::ServerCapabilities::default()
22711        },
22712        cx,
22713    )
22714    .await;
22715
22716    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22717        let go_to_definition = cx
22718            .lsp
22719            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22720                move |params, _| async move {
22721                    if empty_go_to_definition {
22722                        Ok(None)
22723                    } else {
22724                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22725                            uri: params.text_document_position_params.text_document.uri,
22726                            range: lsp::Range::new(
22727                                lsp::Position::new(4, 3),
22728                                lsp::Position::new(4, 6),
22729                            ),
22730                        })))
22731                    }
22732                },
22733            );
22734        let references = cx
22735            .lsp
22736            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22737                Ok(Some(vec![lsp::Location {
22738                    uri: params.text_document_position.text_document.uri,
22739                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22740                }]))
22741            });
22742        (go_to_definition, references)
22743    };
22744
22745    cx.set_state(
22746        &r#"fn one() {
22747            let mut a = ˇtwo();
22748        }
22749
22750        fn two() {}"#
22751            .unindent(),
22752    );
22753    set_up_lsp_handlers(false, &mut cx);
22754    let navigated = cx
22755        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22756        .await
22757        .expect("Failed to navigate to definition");
22758    assert_eq!(
22759        navigated,
22760        Navigated::Yes,
22761        "Should have navigated to definition from the GetDefinition response"
22762    );
22763    cx.assert_editor_state(
22764        &r#"fn one() {
22765            let mut a = two();
22766        }
22767
22768        fn «twoˇ»() {}"#
22769            .unindent(),
22770    );
22771
22772    let editors = cx.update_workspace(|workspace, _, cx| {
22773        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22774    });
22775    cx.update_editor(|_, _, test_editor_cx| {
22776        assert_eq!(
22777            editors.len(),
22778            1,
22779            "Initially, only one, test, editor should be open in the workspace"
22780        );
22781        assert_eq!(
22782            test_editor_cx.entity(),
22783            editors.last().expect("Asserted len is 1").clone()
22784        );
22785    });
22786
22787    set_up_lsp_handlers(true, &mut cx);
22788    let navigated = cx
22789        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22790        .await
22791        .expect("Failed to navigate to lookup references");
22792    assert_eq!(
22793        navigated,
22794        Navigated::Yes,
22795        "Should have navigated to references as a fallback after empty GoToDefinition response"
22796    );
22797    // We should not change the selections in the existing file,
22798    // if opening another milti buffer with the references
22799    cx.assert_editor_state(
22800        &r#"fn one() {
22801            let mut a = two();
22802        }
22803
22804        fn «twoˇ»() {}"#
22805            .unindent(),
22806    );
22807    let editors = cx.update_workspace(|workspace, _, cx| {
22808        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22809    });
22810    cx.update_editor(|_, _, test_editor_cx| {
22811        assert_eq!(
22812            editors.len(),
22813            2,
22814            "After falling back to references search, we open a new editor with the results"
22815        );
22816        let references_fallback_text = editors
22817            .into_iter()
22818            .find(|new_editor| *new_editor != test_editor_cx.entity())
22819            .expect("Should have one non-test editor now")
22820            .read(test_editor_cx)
22821            .text(test_editor_cx);
22822        assert_eq!(
22823            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22824            "Should use the range from the references response and not the GoToDefinition one"
22825        );
22826    });
22827}
22828
22829#[gpui::test]
22830async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22831    init_test(cx, |_| {});
22832    cx.update(|cx| {
22833        let mut editor_settings = EditorSettings::get_global(cx).clone();
22834        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22835        EditorSettings::override_global(editor_settings, cx);
22836    });
22837    let mut cx = EditorLspTestContext::new_rust(
22838        lsp::ServerCapabilities {
22839            definition_provider: Some(lsp::OneOf::Left(true)),
22840            references_provider: Some(lsp::OneOf::Left(true)),
22841            ..lsp::ServerCapabilities::default()
22842        },
22843        cx,
22844    )
22845    .await;
22846    let original_state = r#"fn one() {
22847        let mut a = ˇtwo();
22848    }
22849
22850    fn two() {}"#
22851        .unindent();
22852    cx.set_state(&original_state);
22853
22854    let mut go_to_definition = cx
22855        .lsp
22856        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22857            move |_, _| async move { Ok(None) },
22858        );
22859    let _references = cx
22860        .lsp
22861        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22862            panic!("Should not call for references with no go to definition fallback")
22863        });
22864
22865    let navigated = cx
22866        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22867        .await
22868        .expect("Failed to navigate to lookup references");
22869    go_to_definition
22870        .next()
22871        .await
22872        .expect("Should have called the go_to_definition handler");
22873
22874    assert_eq!(
22875        navigated,
22876        Navigated::No,
22877        "Should have navigated to references as a fallback after empty GoToDefinition response"
22878    );
22879    cx.assert_editor_state(&original_state);
22880    let editors = cx.update_workspace(|workspace, _, cx| {
22881        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22882    });
22883    cx.update_editor(|_, _, _| {
22884        assert_eq!(
22885            editors.len(),
22886            1,
22887            "After unsuccessful fallback, no other editor should have been opened"
22888        );
22889    });
22890}
22891
22892#[gpui::test]
22893async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22894    init_test(cx, |_| {});
22895    let mut cx = EditorLspTestContext::new_rust(
22896        lsp::ServerCapabilities {
22897            references_provider: Some(lsp::OneOf::Left(true)),
22898            ..lsp::ServerCapabilities::default()
22899        },
22900        cx,
22901    )
22902    .await;
22903
22904    cx.set_state(
22905        &r#"
22906        fn one() {
22907            let mut a = two();
22908        }
22909
22910        fn ˇtwo() {}"#
22911            .unindent(),
22912    );
22913    cx.lsp
22914        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22915            Ok(Some(vec![
22916                lsp::Location {
22917                    uri: params.text_document_position.text_document.uri.clone(),
22918                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22919                },
22920                lsp::Location {
22921                    uri: params.text_document_position.text_document.uri,
22922                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22923                },
22924            ]))
22925        });
22926    let navigated = cx
22927        .update_editor(|editor, window, cx| {
22928            editor.find_all_references(&FindAllReferences::default(), window, cx)
22929        })
22930        .unwrap()
22931        .await
22932        .expect("Failed to navigate to references");
22933    assert_eq!(
22934        navigated,
22935        Navigated::Yes,
22936        "Should have navigated to references from the FindAllReferences response"
22937    );
22938    cx.assert_editor_state(
22939        &r#"fn one() {
22940            let mut a = two();
22941        }
22942
22943        fn ˇtwo() {}"#
22944            .unindent(),
22945    );
22946
22947    let editors = cx.update_workspace(|workspace, _, cx| {
22948        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22949    });
22950    cx.update_editor(|_, _, _| {
22951        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22952    });
22953
22954    cx.set_state(
22955        &r#"fn one() {
22956            let mut a = ˇtwo();
22957        }
22958
22959        fn two() {}"#
22960            .unindent(),
22961    );
22962    let navigated = cx
22963        .update_editor(|editor, window, cx| {
22964            editor.find_all_references(&FindAllReferences::default(), window, cx)
22965        })
22966        .unwrap()
22967        .await
22968        .expect("Failed to navigate to references");
22969    assert_eq!(
22970        navigated,
22971        Navigated::Yes,
22972        "Should have navigated to references from the FindAllReferences response"
22973    );
22974    cx.assert_editor_state(
22975        &r#"fn one() {
22976            let mut a = ˇtwo();
22977        }
22978
22979        fn two() {}"#
22980            .unindent(),
22981    );
22982    let editors = cx.update_workspace(|workspace, _, cx| {
22983        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22984    });
22985    cx.update_editor(|_, _, _| {
22986        assert_eq!(
22987            editors.len(),
22988            2,
22989            "should have re-used the previous multibuffer"
22990        );
22991    });
22992
22993    cx.set_state(
22994        &r#"fn one() {
22995            let mut a = ˇtwo();
22996        }
22997        fn three() {}
22998        fn two() {}"#
22999            .unindent(),
23000    );
23001    cx.lsp
23002        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23003            Ok(Some(vec![
23004                lsp::Location {
23005                    uri: params.text_document_position.text_document.uri.clone(),
23006                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23007                },
23008                lsp::Location {
23009                    uri: params.text_document_position.text_document.uri,
23010                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23011                },
23012            ]))
23013        });
23014    let navigated = cx
23015        .update_editor(|editor, window, cx| {
23016            editor.find_all_references(&FindAllReferences::default(), window, cx)
23017        })
23018        .unwrap()
23019        .await
23020        .expect("Failed to navigate to references");
23021    assert_eq!(
23022        navigated,
23023        Navigated::Yes,
23024        "Should have navigated to references from the FindAllReferences response"
23025    );
23026    cx.assert_editor_state(
23027        &r#"fn one() {
23028                let mut a = ˇtwo();
23029            }
23030            fn three() {}
23031            fn two() {}"#
23032            .unindent(),
23033    );
23034    let editors = cx.update_workspace(|workspace, _, cx| {
23035        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23036    });
23037    cx.update_editor(|_, _, _| {
23038        assert_eq!(
23039            editors.len(),
23040            3,
23041            "should have used a new multibuffer as offsets changed"
23042        );
23043    });
23044}
23045#[gpui::test]
23046async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23047    init_test(cx, |_| {});
23048
23049    let language = Arc::new(Language::new(
23050        LanguageConfig::default(),
23051        Some(tree_sitter_rust::LANGUAGE.into()),
23052    ));
23053
23054    let text = r#"
23055        #[cfg(test)]
23056        mod tests() {
23057            #[test]
23058            fn runnable_1() {
23059                let a = 1;
23060            }
23061
23062            #[test]
23063            fn runnable_2() {
23064                let a = 1;
23065                let b = 2;
23066            }
23067        }
23068    "#
23069    .unindent();
23070
23071    let fs = FakeFs::new(cx.executor());
23072    fs.insert_file("/file.rs", Default::default()).await;
23073
23074    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23075    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23076    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23077    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23078    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23079
23080    let editor = cx.new_window_entity(|window, cx| {
23081        Editor::new(
23082            EditorMode::full(),
23083            multi_buffer,
23084            Some(project.clone()),
23085            window,
23086            cx,
23087        )
23088    });
23089
23090    editor.update_in(cx, |editor, window, cx| {
23091        let snapshot = editor.buffer().read(cx).snapshot(cx);
23092        editor.tasks.insert(
23093            (buffer.read(cx).remote_id(), 3),
23094            RunnableTasks {
23095                templates: vec![],
23096                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23097                column: 0,
23098                extra_variables: HashMap::default(),
23099                context_range: BufferOffset(43)..BufferOffset(85),
23100            },
23101        );
23102        editor.tasks.insert(
23103            (buffer.read(cx).remote_id(), 8),
23104            RunnableTasks {
23105                templates: vec![],
23106                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23107                column: 0,
23108                extra_variables: HashMap::default(),
23109                context_range: BufferOffset(86)..BufferOffset(191),
23110            },
23111        );
23112
23113        // Test finding task when cursor is inside function body
23114        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23115            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23116        });
23117        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23118        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23119
23120        // Test finding task when cursor is on function name
23121        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23122            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23123        });
23124        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23125        assert_eq!(row, 8, "Should find task when cursor is on function name");
23126    });
23127}
23128
23129#[gpui::test]
23130async fn test_folding_buffers(cx: &mut TestAppContext) {
23131    init_test(cx, |_| {});
23132
23133    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23134    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23135    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23136
23137    let fs = FakeFs::new(cx.executor());
23138    fs.insert_tree(
23139        path!("/a"),
23140        json!({
23141            "first.rs": sample_text_1,
23142            "second.rs": sample_text_2,
23143            "third.rs": sample_text_3,
23144        }),
23145    )
23146    .await;
23147    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23148    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23149    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23150    let worktree = project.update(cx, |project, cx| {
23151        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23152        assert_eq!(worktrees.len(), 1);
23153        worktrees.pop().unwrap()
23154    });
23155    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23156
23157    let buffer_1 = project
23158        .update(cx, |project, cx| {
23159            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23160        })
23161        .await
23162        .unwrap();
23163    let buffer_2 = project
23164        .update(cx, |project, cx| {
23165            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23166        })
23167        .await
23168        .unwrap();
23169    let buffer_3 = project
23170        .update(cx, |project, cx| {
23171            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23172        })
23173        .await
23174        .unwrap();
23175
23176    let multi_buffer = cx.new(|cx| {
23177        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23178        multi_buffer.push_excerpts(
23179            buffer_1.clone(),
23180            [
23181                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23182                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23183                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23184            ],
23185            cx,
23186        );
23187        multi_buffer.push_excerpts(
23188            buffer_2.clone(),
23189            [
23190                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23191                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23192                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23193            ],
23194            cx,
23195        );
23196        multi_buffer.push_excerpts(
23197            buffer_3.clone(),
23198            [
23199                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23200                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23201                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23202            ],
23203            cx,
23204        );
23205        multi_buffer
23206    });
23207    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23208        Editor::new(
23209            EditorMode::full(),
23210            multi_buffer.clone(),
23211            Some(project.clone()),
23212            window,
23213            cx,
23214        )
23215    });
23216
23217    assert_eq!(
23218        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23219        "\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",
23220    );
23221
23222    multi_buffer_editor.update(cx, |editor, cx| {
23223        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23224    });
23225    assert_eq!(
23226        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23227        "\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",
23228        "After folding the first buffer, its text should not be displayed"
23229    );
23230
23231    multi_buffer_editor.update(cx, |editor, cx| {
23232        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23233    });
23234    assert_eq!(
23235        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23236        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23237        "After folding the second buffer, its text should not be displayed"
23238    );
23239
23240    multi_buffer_editor.update(cx, |editor, cx| {
23241        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23242    });
23243    assert_eq!(
23244        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23245        "\n\n\n\n\n",
23246        "After folding the third buffer, its text should not be displayed"
23247    );
23248
23249    // Emulate selection inside the fold logic, that should work
23250    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23251        editor
23252            .snapshot(window, cx)
23253            .next_line_boundary(Point::new(0, 4));
23254    });
23255
23256    multi_buffer_editor.update(cx, |editor, cx| {
23257        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23258    });
23259    assert_eq!(
23260        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23261        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23262        "After unfolding the second buffer, its text should be displayed"
23263    );
23264
23265    // Typing inside of buffer 1 causes that buffer to be unfolded.
23266    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23267        assert_eq!(
23268            multi_buffer
23269                .read(cx)
23270                .snapshot(cx)
23271                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23272                .collect::<String>(),
23273            "bbbb"
23274        );
23275        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23276            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23277        });
23278        editor.handle_input("B", window, cx);
23279    });
23280
23281    assert_eq!(
23282        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23283        "\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",
23284        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23285    );
23286
23287    multi_buffer_editor.update(cx, |editor, cx| {
23288        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23289    });
23290    assert_eq!(
23291        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23292        "\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",
23293        "After unfolding the all buffers, all original text should be displayed"
23294    );
23295}
23296
23297#[gpui::test]
23298async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23299    init_test(cx, |_| {});
23300
23301    let sample_text_1 = "1111\n2222\n3333".to_string();
23302    let sample_text_2 = "4444\n5555\n6666".to_string();
23303    let sample_text_3 = "7777\n8888\n9999".to_string();
23304
23305    let fs = FakeFs::new(cx.executor());
23306    fs.insert_tree(
23307        path!("/a"),
23308        json!({
23309            "first.rs": sample_text_1,
23310            "second.rs": sample_text_2,
23311            "third.rs": sample_text_3,
23312        }),
23313    )
23314    .await;
23315    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23316    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23317    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23318    let worktree = project.update(cx, |project, cx| {
23319        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23320        assert_eq!(worktrees.len(), 1);
23321        worktrees.pop().unwrap()
23322    });
23323    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23324
23325    let buffer_1 = project
23326        .update(cx, |project, cx| {
23327            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23328        })
23329        .await
23330        .unwrap();
23331    let buffer_2 = project
23332        .update(cx, |project, cx| {
23333            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23334        })
23335        .await
23336        .unwrap();
23337    let buffer_3 = project
23338        .update(cx, |project, cx| {
23339            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23340        })
23341        .await
23342        .unwrap();
23343
23344    let multi_buffer = cx.new(|cx| {
23345        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23346        multi_buffer.push_excerpts(
23347            buffer_1.clone(),
23348            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23349            cx,
23350        );
23351        multi_buffer.push_excerpts(
23352            buffer_2.clone(),
23353            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23354            cx,
23355        );
23356        multi_buffer.push_excerpts(
23357            buffer_3.clone(),
23358            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23359            cx,
23360        );
23361        multi_buffer
23362    });
23363
23364    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23365        Editor::new(
23366            EditorMode::full(),
23367            multi_buffer,
23368            Some(project.clone()),
23369            window,
23370            cx,
23371        )
23372    });
23373
23374    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23375    assert_eq!(
23376        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23377        full_text,
23378    );
23379
23380    multi_buffer_editor.update(cx, |editor, cx| {
23381        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23382    });
23383    assert_eq!(
23384        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23385        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23386        "After folding the first buffer, its text should not be displayed"
23387    );
23388
23389    multi_buffer_editor.update(cx, |editor, cx| {
23390        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23391    });
23392
23393    assert_eq!(
23394        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23395        "\n\n\n\n\n\n7777\n8888\n9999",
23396        "After folding the second buffer, its text should not be displayed"
23397    );
23398
23399    multi_buffer_editor.update(cx, |editor, cx| {
23400        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23401    });
23402    assert_eq!(
23403        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23404        "\n\n\n\n\n",
23405        "After folding the third buffer, its text should not be displayed"
23406    );
23407
23408    multi_buffer_editor.update(cx, |editor, cx| {
23409        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23410    });
23411    assert_eq!(
23412        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23413        "\n\n\n\n4444\n5555\n6666\n\n",
23414        "After unfolding the second buffer, its text should be displayed"
23415    );
23416
23417    multi_buffer_editor.update(cx, |editor, cx| {
23418        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23419    });
23420    assert_eq!(
23421        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23422        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23423        "After unfolding the first buffer, its text should be displayed"
23424    );
23425
23426    multi_buffer_editor.update(cx, |editor, cx| {
23427        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23428    });
23429    assert_eq!(
23430        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23431        full_text,
23432        "After unfolding all buffers, all original text should be displayed"
23433    );
23434}
23435
23436#[gpui::test]
23437async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23438    init_test(cx, |_| {});
23439
23440    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23441
23442    let fs = FakeFs::new(cx.executor());
23443    fs.insert_tree(
23444        path!("/a"),
23445        json!({
23446            "main.rs": sample_text,
23447        }),
23448    )
23449    .await;
23450    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23451    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23452    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23453    let worktree = project.update(cx, |project, cx| {
23454        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23455        assert_eq!(worktrees.len(), 1);
23456        worktrees.pop().unwrap()
23457    });
23458    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23459
23460    let buffer_1 = project
23461        .update(cx, |project, cx| {
23462            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23463        })
23464        .await
23465        .unwrap();
23466
23467    let multi_buffer = cx.new(|cx| {
23468        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23469        multi_buffer.push_excerpts(
23470            buffer_1.clone(),
23471            [ExcerptRange::new(
23472                Point::new(0, 0)
23473                    ..Point::new(
23474                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23475                        0,
23476                    ),
23477            )],
23478            cx,
23479        );
23480        multi_buffer
23481    });
23482    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23483        Editor::new(
23484            EditorMode::full(),
23485            multi_buffer,
23486            Some(project.clone()),
23487            window,
23488            cx,
23489        )
23490    });
23491
23492    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23493    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23494        enum TestHighlight {}
23495        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23496        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23497        editor.highlight_text::<TestHighlight>(
23498            vec![highlight_range.clone()],
23499            HighlightStyle::color(Hsla::green()),
23500            cx,
23501        );
23502        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23503            s.select_ranges(Some(highlight_range))
23504        });
23505    });
23506
23507    let full_text = format!("\n\n{sample_text}");
23508    assert_eq!(
23509        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23510        full_text,
23511    );
23512}
23513
23514#[gpui::test]
23515async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23516    init_test(cx, |_| {});
23517    cx.update(|cx| {
23518        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23519            "keymaps/default-linux.json",
23520            cx,
23521        )
23522        .unwrap();
23523        cx.bind_keys(default_key_bindings);
23524    });
23525
23526    let (editor, cx) = cx.add_window_view(|window, cx| {
23527        let multi_buffer = MultiBuffer::build_multi(
23528            [
23529                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23530                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23531                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23532                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23533            ],
23534            cx,
23535        );
23536        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23537
23538        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23539        // fold all but the second buffer, so that we test navigating between two
23540        // adjacent folded buffers, as well as folded buffers at the start and
23541        // end the multibuffer
23542        editor.fold_buffer(buffer_ids[0], cx);
23543        editor.fold_buffer(buffer_ids[2], cx);
23544        editor.fold_buffer(buffer_ids[3], cx);
23545
23546        editor
23547    });
23548    cx.simulate_resize(size(px(1000.), px(1000.)));
23549
23550    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23551    cx.assert_excerpts_with_selections(indoc! {"
23552        [EXCERPT]
23553        ˇ[FOLDED]
23554        [EXCERPT]
23555        a1
23556        b1
23557        [EXCERPT]
23558        [FOLDED]
23559        [EXCERPT]
23560        [FOLDED]
23561        "
23562    });
23563    cx.simulate_keystroke("down");
23564    cx.assert_excerpts_with_selections(indoc! {"
23565        [EXCERPT]
23566        [FOLDED]
23567        [EXCERPT]
23568        ˇa1
23569        b1
23570        [EXCERPT]
23571        [FOLDED]
23572        [EXCERPT]
23573        [FOLDED]
23574        "
23575    });
23576    cx.simulate_keystroke("down");
23577    cx.assert_excerpts_with_selections(indoc! {"
23578        [EXCERPT]
23579        [FOLDED]
23580        [EXCERPT]
23581        a1
23582        ˇb1
23583        [EXCERPT]
23584        [FOLDED]
23585        [EXCERPT]
23586        [FOLDED]
23587        "
23588    });
23589    cx.simulate_keystroke("down");
23590    cx.assert_excerpts_with_selections(indoc! {"
23591        [EXCERPT]
23592        [FOLDED]
23593        [EXCERPT]
23594        a1
23595        b1
23596        ˇ[EXCERPT]
23597        [FOLDED]
23598        [EXCERPT]
23599        [FOLDED]
23600        "
23601    });
23602    cx.simulate_keystroke("down");
23603    cx.assert_excerpts_with_selections(indoc! {"
23604        [EXCERPT]
23605        [FOLDED]
23606        [EXCERPT]
23607        a1
23608        b1
23609        [EXCERPT]
23610        ˇ[FOLDED]
23611        [EXCERPT]
23612        [FOLDED]
23613        "
23614    });
23615    for _ in 0..5 {
23616        cx.simulate_keystroke("down");
23617        cx.assert_excerpts_with_selections(indoc! {"
23618            [EXCERPT]
23619            [FOLDED]
23620            [EXCERPT]
23621            a1
23622            b1
23623            [EXCERPT]
23624            [FOLDED]
23625            [EXCERPT]
23626            ˇ[FOLDED]
23627            "
23628        });
23629    }
23630
23631    cx.simulate_keystroke("up");
23632    cx.assert_excerpts_with_selections(indoc! {"
23633        [EXCERPT]
23634        [FOLDED]
23635        [EXCERPT]
23636        a1
23637        b1
23638        [EXCERPT]
23639        ˇ[FOLDED]
23640        [EXCERPT]
23641        [FOLDED]
23642        "
23643    });
23644    cx.simulate_keystroke("up");
23645    cx.assert_excerpts_with_selections(indoc! {"
23646        [EXCERPT]
23647        [FOLDED]
23648        [EXCERPT]
23649        a1
23650        b1
23651        ˇ[EXCERPT]
23652        [FOLDED]
23653        [EXCERPT]
23654        [FOLDED]
23655        "
23656    });
23657    cx.simulate_keystroke("up");
23658    cx.assert_excerpts_with_selections(indoc! {"
23659        [EXCERPT]
23660        [FOLDED]
23661        [EXCERPT]
23662        a1
23663        ˇb1
23664        [EXCERPT]
23665        [FOLDED]
23666        [EXCERPT]
23667        [FOLDED]
23668        "
23669    });
23670    cx.simulate_keystroke("up");
23671    cx.assert_excerpts_with_selections(indoc! {"
23672        [EXCERPT]
23673        [FOLDED]
23674        [EXCERPT]
23675        ˇa1
23676        b1
23677        [EXCERPT]
23678        [FOLDED]
23679        [EXCERPT]
23680        [FOLDED]
23681        "
23682    });
23683    for _ in 0..5 {
23684        cx.simulate_keystroke("up");
23685        cx.assert_excerpts_with_selections(indoc! {"
23686            [EXCERPT]
23687            ˇ[FOLDED]
23688            [EXCERPT]
23689            a1
23690            b1
23691            [EXCERPT]
23692            [FOLDED]
23693            [EXCERPT]
23694            [FOLDED]
23695            "
23696        });
23697    }
23698}
23699
23700#[gpui::test]
23701async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23702    init_test(cx, |_| {});
23703
23704    // Simple insertion
23705    assert_highlighted_edits(
23706        "Hello, world!",
23707        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23708        true,
23709        cx,
23710        |highlighted_edits, cx| {
23711            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23712            assert_eq!(highlighted_edits.highlights.len(), 1);
23713            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23714            assert_eq!(
23715                highlighted_edits.highlights[0].1.background_color,
23716                Some(cx.theme().status().created_background)
23717            );
23718        },
23719    )
23720    .await;
23721
23722    // Replacement
23723    assert_highlighted_edits(
23724        "This is a test.",
23725        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23726        false,
23727        cx,
23728        |highlighted_edits, cx| {
23729            assert_eq!(highlighted_edits.text, "That is a test.");
23730            assert_eq!(highlighted_edits.highlights.len(), 1);
23731            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23732            assert_eq!(
23733                highlighted_edits.highlights[0].1.background_color,
23734                Some(cx.theme().status().created_background)
23735            );
23736        },
23737    )
23738    .await;
23739
23740    // Multiple edits
23741    assert_highlighted_edits(
23742        "Hello, world!",
23743        vec![
23744            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23745            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23746        ],
23747        false,
23748        cx,
23749        |highlighted_edits, cx| {
23750            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23751            assert_eq!(highlighted_edits.highlights.len(), 2);
23752            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23753            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23754            assert_eq!(
23755                highlighted_edits.highlights[0].1.background_color,
23756                Some(cx.theme().status().created_background)
23757            );
23758            assert_eq!(
23759                highlighted_edits.highlights[1].1.background_color,
23760                Some(cx.theme().status().created_background)
23761            );
23762        },
23763    )
23764    .await;
23765
23766    // Multiple lines with edits
23767    assert_highlighted_edits(
23768        "First line\nSecond line\nThird line\nFourth line",
23769        vec![
23770            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23771            (
23772                Point::new(2, 0)..Point::new(2, 10),
23773                "New third line".to_string(),
23774            ),
23775            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23776        ],
23777        false,
23778        cx,
23779        |highlighted_edits, cx| {
23780            assert_eq!(
23781                highlighted_edits.text,
23782                "Second modified\nNew third line\nFourth updated line"
23783            );
23784            assert_eq!(highlighted_edits.highlights.len(), 3);
23785            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23786            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23787            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23788            for highlight in &highlighted_edits.highlights {
23789                assert_eq!(
23790                    highlight.1.background_color,
23791                    Some(cx.theme().status().created_background)
23792                );
23793            }
23794        },
23795    )
23796    .await;
23797}
23798
23799#[gpui::test]
23800async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23801    init_test(cx, |_| {});
23802
23803    // Deletion
23804    assert_highlighted_edits(
23805        "Hello, world!",
23806        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23807        true,
23808        cx,
23809        |highlighted_edits, cx| {
23810            assert_eq!(highlighted_edits.text, "Hello, world!");
23811            assert_eq!(highlighted_edits.highlights.len(), 1);
23812            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23813            assert_eq!(
23814                highlighted_edits.highlights[0].1.background_color,
23815                Some(cx.theme().status().deleted_background)
23816            );
23817        },
23818    )
23819    .await;
23820
23821    // Insertion
23822    assert_highlighted_edits(
23823        "Hello, world!",
23824        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23825        true,
23826        cx,
23827        |highlighted_edits, cx| {
23828            assert_eq!(highlighted_edits.highlights.len(), 1);
23829            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23830            assert_eq!(
23831                highlighted_edits.highlights[0].1.background_color,
23832                Some(cx.theme().status().created_background)
23833            );
23834        },
23835    )
23836    .await;
23837}
23838
23839async fn assert_highlighted_edits(
23840    text: &str,
23841    edits: Vec<(Range<Point>, String)>,
23842    include_deletions: bool,
23843    cx: &mut TestAppContext,
23844    assertion_fn: impl Fn(HighlightedText, &App),
23845) {
23846    let window = cx.add_window(|window, cx| {
23847        let buffer = MultiBuffer::build_simple(text, cx);
23848        Editor::new(EditorMode::full(), buffer, None, window, cx)
23849    });
23850    let cx = &mut VisualTestContext::from_window(*window, cx);
23851
23852    let (buffer, snapshot) = window
23853        .update(cx, |editor, _window, cx| {
23854            (
23855                editor.buffer().clone(),
23856                editor.buffer().read(cx).snapshot(cx),
23857            )
23858        })
23859        .unwrap();
23860
23861    let edits = edits
23862        .into_iter()
23863        .map(|(range, edit)| {
23864            (
23865                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23866                edit,
23867            )
23868        })
23869        .collect::<Vec<_>>();
23870
23871    let text_anchor_edits = edits
23872        .clone()
23873        .into_iter()
23874        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23875        .collect::<Vec<_>>();
23876
23877    let edit_preview = window
23878        .update(cx, |_, _window, cx| {
23879            buffer
23880                .read(cx)
23881                .as_singleton()
23882                .unwrap()
23883                .read(cx)
23884                .preview_edits(text_anchor_edits.into(), cx)
23885        })
23886        .unwrap()
23887        .await;
23888
23889    cx.update(|_window, cx| {
23890        let highlighted_edits = edit_prediction_edit_text(
23891            snapshot.as_singleton().unwrap().2,
23892            &edits,
23893            &edit_preview,
23894            include_deletions,
23895            cx,
23896        );
23897        assertion_fn(highlighted_edits, cx)
23898    });
23899}
23900
23901#[track_caller]
23902fn assert_breakpoint(
23903    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23904    path: &Arc<Path>,
23905    expected: Vec<(u32, Breakpoint)>,
23906) {
23907    if expected.is_empty() {
23908        assert!(!breakpoints.contains_key(path), "{}", path.display());
23909    } else {
23910        let mut breakpoint = breakpoints
23911            .get(path)
23912            .unwrap()
23913            .iter()
23914            .map(|breakpoint| {
23915                (
23916                    breakpoint.row,
23917                    Breakpoint {
23918                        message: breakpoint.message.clone(),
23919                        state: breakpoint.state,
23920                        condition: breakpoint.condition.clone(),
23921                        hit_condition: breakpoint.hit_condition.clone(),
23922                    },
23923                )
23924            })
23925            .collect::<Vec<_>>();
23926
23927        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23928
23929        assert_eq!(expected, breakpoint);
23930    }
23931}
23932
23933fn add_log_breakpoint_at_cursor(
23934    editor: &mut Editor,
23935    log_message: &str,
23936    window: &mut Window,
23937    cx: &mut Context<Editor>,
23938) {
23939    let (anchor, bp) = editor
23940        .breakpoints_at_cursors(window, cx)
23941        .first()
23942        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23943        .unwrap_or_else(|| {
23944            let snapshot = editor.snapshot(window, cx);
23945            let cursor_position: Point =
23946                editor.selections.newest(&snapshot.display_snapshot).head();
23947
23948            let breakpoint_position = snapshot
23949                .buffer_snapshot()
23950                .anchor_before(Point::new(cursor_position.row, 0));
23951
23952            (breakpoint_position, Breakpoint::new_log(log_message))
23953        });
23954
23955    editor.edit_breakpoint_at_anchor(
23956        anchor,
23957        bp,
23958        BreakpointEditAction::EditLogMessage(log_message.into()),
23959        cx,
23960    );
23961}
23962
23963#[gpui::test]
23964async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23965    init_test(cx, |_| {});
23966
23967    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23968    let fs = FakeFs::new(cx.executor());
23969    fs.insert_tree(
23970        path!("/a"),
23971        json!({
23972            "main.rs": sample_text,
23973        }),
23974    )
23975    .await;
23976    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23977    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23978    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23979
23980    let fs = FakeFs::new(cx.executor());
23981    fs.insert_tree(
23982        path!("/a"),
23983        json!({
23984            "main.rs": sample_text,
23985        }),
23986    )
23987    .await;
23988    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23989    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23990    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23991    let worktree_id = workspace
23992        .update(cx, |workspace, _window, cx| {
23993            workspace.project().update(cx, |project, cx| {
23994                project.worktrees(cx).next().unwrap().read(cx).id()
23995            })
23996        })
23997        .unwrap();
23998
23999    let buffer = project
24000        .update(cx, |project, cx| {
24001            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24002        })
24003        .await
24004        .unwrap();
24005
24006    let (editor, cx) = cx.add_window_view(|window, cx| {
24007        Editor::new(
24008            EditorMode::full(),
24009            MultiBuffer::build_from_buffer(buffer, cx),
24010            Some(project.clone()),
24011            window,
24012            cx,
24013        )
24014    });
24015
24016    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24017    let abs_path = project.read_with(cx, |project, cx| {
24018        project
24019            .absolute_path(&project_path, cx)
24020            .map(Arc::from)
24021            .unwrap()
24022    });
24023
24024    // assert we can add breakpoint on the first line
24025    editor.update_in(cx, |editor, window, cx| {
24026        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24027        editor.move_to_end(&MoveToEnd, window, cx);
24028        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24029    });
24030
24031    let breakpoints = editor.update(cx, |editor, cx| {
24032        editor
24033            .breakpoint_store()
24034            .as_ref()
24035            .unwrap()
24036            .read(cx)
24037            .all_source_breakpoints(cx)
24038    });
24039
24040    assert_eq!(1, breakpoints.len());
24041    assert_breakpoint(
24042        &breakpoints,
24043        &abs_path,
24044        vec![
24045            (0, Breakpoint::new_standard()),
24046            (3, Breakpoint::new_standard()),
24047        ],
24048    );
24049
24050    editor.update_in(cx, |editor, window, cx| {
24051        editor.move_to_beginning(&MoveToBeginning, window, cx);
24052        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24053    });
24054
24055    let breakpoints = editor.update(cx, |editor, cx| {
24056        editor
24057            .breakpoint_store()
24058            .as_ref()
24059            .unwrap()
24060            .read(cx)
24061            .all_source_breakpoints(cx)
24062    });
24063
24064    assert_eq!(1, breakpoints.len());
24065    assert_breakpoint(
24066        &breakpoints,
24067        &abs_path,
24068        vec![(3, Breakpoint::new_standard())],
24069    );
24070
24071    editor.update_in(cx, |editor, window, cx| {
24072        editor.move_to_end(&MoveToEnd, window, cx);
24073        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24074    });
24075
24076    let breakpoints = editor.update(cx, |editor, cx| {
24077        editor
24078            .breakpoint_store()
24079            .as_ref()
24080            .unwrap()
24081            .read(cx)
24082            .all_source_breakpoints(cx)
24083    });
24084
24085    assert_eq!(0, breakpoints.len());
24086    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24087}
24088
24089#[gpui::test]
24090async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24091    init_test(cx, |_| {});
24092
24093    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24094
24095    let fs = FakeFs::new(cx.executor());
24096    fs.insert_tree(
24097        path!("/a"),
24098        json!({
24099            "main.rs": sample_text,
24100        }),
24101    )
24102    .await;
24103    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24104    let (workspace, cx) =
24105        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24106
24107    let worktree_id = workspace.update(cx, |workspace, cx| {
24108        workspace.project().update(cx, |project, cx| {
24109            project.worktrees(cx).next().unwrap().read(cx).id()
24110        })
24111    });
24112
24113    let buffer = project
24114        .update(cx, |project, cx| {
24115            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24116        })
24117        .await
24118        .unwrap();
24119
24120    let (editor, cx) = cx.add_window_view(|window, cx| {
24121        Editor::new(
24122            EditorMode::full(),
24123            MultiBuffer::build_from_buffer(buffer, cx),
24124            Some(project.clone()),
24125            window,
24126            cx,
24127        )
24128    });
24129
24130    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24131    let abs_path = project.read_with(cx, |project, cx| {
24132        project
24133            .absolute_path(&project_path, cx)
24134            .map(Arc::from)
24135            .unwrap()
24136    });
24137
24138    editor.update_in(cx, |editor, window, cx| {
24139        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24140    });
24141
24142    let breakpoints = editor.update(cx, |editor, cx| {
24143        editor
24144            .breakpoint_store()
24145            .as_ref()
24146            .unwrap()
24147            .read(cx)
24148            .all_source_breakpoints(cx)
24149    });
24150
24151    assert_breakpoint(
24152        &breakpoints,
24153        &abs_path,
24154        vec![(0, Breakpoint::new_log("hello world"))],
24155    );
24156
24157    // Removing a log message from a log breakpoint should remove it
24158    editor.update_in(cx, |editor, window, cx| {
24159        add_log_breakpoint_at_cursor(editor, "", window, cx);
24160    });
24161
24162    let breakpoints = editor.update(cx, |editor, cx| {
24163        editor
24164            .breakpoint_store()
24165            .as_ref()
24166            .unwrap()
24167            .read(cx)
24168            .all_source_breakpoints(cx)
24169    });
24170
24171    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24172
24173    editor.update_in(cx, |editor, window, cx| {
24174        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24175        editor.move_to_end(&MoveToEnd, window, cx);
24176        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24177        // Not adding a log message to a standard breakpoint shouldn't remove it
24178        add_log_breakpoint_at_cursor(editor, "", window, cx);
24179    });
24180
24181    let breakpoints = editor.update(cx, |editor, cx| {
24182        editor
24183            .breakpoint_store()
24184            .as_ref()
24185            .unwrap()
24186            .read(cx)
24187            .all_source_breakpoints(cx)
24188    });
24189
24190    assert_breakpoint(
24191        &breakpoints,
24192        &abs_path,
24193        vec![
24194            (0, Breakpoint::new_standard()),
24195            (3, Breakpoint::new_standard()),
24196        ],
24197    );
24198
24199    editor.update_in(cx, |editor, window, cx| {
24200        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24201    });
24202
24203    let breakpoints = editor.update(cx, |editor, cx| {
24204        editor
24205            .breakpoint_store()
24206            .as_ref()
24207            .unwrap()
24208            .read(cx)
24209            .all_source_breakpoints(cx)
24210    });
24211
24212    assert_breakpoint(
24213        &breakpoints,
24214        &abs_path,
24215        vec![
24216            (0, Breakpoint::new_standard()),
24217            (3, Breakpoint::new_log("hello world")),
24218        ],
24219    );
24220
24221    editor.update_in(cx, |editor, window, cx| {
24222        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24223    });
24224
24225    let breakpoints = editor.update(cx, |editor, cx| {
24226        editor
24227            .breakpoint_store()
24228            .as_ref()
24229            .unwrap()
24230            .read(cx)
24231            .all_source_breakpoints(cx)
24232    });
24233
24234    assert_breakpoint(
24235        &breakpoints,
24236        &abs_path,
24237        vec![
24238            (0, Breakpoint::new_standard()),
24239            (3, Breakpoint::new_log("hello Earth!!")),
24240        ],
24241    );
24242}
24243
24244/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24245/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24246/// or when breakpoints were placed out of order. This tests for a regression too
24247#[gpui::test]
24248async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24249    init_test(cx, |_| {});
24250
24251    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24252    let fs = FakeFs::new(cx.executor());
24253    fs.insert_tree(
24254        path!("/a"),
24255        json!({
24256            "main.rs": sample_text,
24257        }),
24258    )
24259    .await;
24260    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24261    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24262    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24263
24264    let fs = FakeFs::new(cx.executor());
24265    fs.insert_tree(
24266        path!("/a"),
24267        json!({
24268            "main.rs": sample_text,
24269        }),
24270    )
24271    .await;
24272    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24273    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24274    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24275    let worktree_id = workspace
24276        .update(cx, |workspace, _window, cx| {
24277            workspace.project().update(cx, |project, cx| {
24278                project.worktrees(cx).next().unwrap().read(cx).id()
24279            })
24280        })
24281        .unwrap();
24282
24283    let buffer = project
24284        .update(cx, |project, cx| {
24285            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24286        })
24287        .await
24288        .unwrap();
24289
24290    let (editor, cx) = cx.add_window_view(|window, cx| {
24291        Editor::new(
24292            EditorMode::full(),
24293            MultiBuffer::build_from_buffer(buffer, cx),
24294            Some(project.clone()),
24295            window,
24296            cx,
24297        )
24298    });
24299
24300    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24301    let abs_path = project.read_with(cx, |project, cx| {
24302        project
24303            .absolute_path(&project_path, cx)
24304            .map(Arc::from)
24305            .unwrap()
24306    });
24307
24308    // assert we can add breakpoint on the first line
24309    editor.update_in(cx, |editor, window, cx| {
24310        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24311        editor.move_to_end(&MoveToEnd, window, cx);
24312        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24313        editor.move_up(&MoveUp, window, cx);
24314        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24315    });
24316
24317    let breakpoints = editor.update(cx, |editor, cx| {
24318        editor
24319            .breakpoint_store()
24320            .as_ref()
24321            .unwrap()
24322            .read(cx)
24323            .all_source_breakpoints(cx)
24324    });
24325
24326    assert_eq!(1, breakpoints.len());
24327    assert_breakpoint(
24328        &breakpoints,
24329        &abs_path,
24330        vec![
24331            (0, Breakpoint::new_standard()),
24332            (2, Breakpoint::new_standard()),
24333            (3, Breakpoint::new_standard()),
24334        ],
24335    );
24336
24337    editor.update_in(cx, |editor, window, cx| {
24338        editor.move_to_beginning(&MoveToBeginning, window, cx);
24339        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24340        editor.move_to_end(&MoveToEnd, window, cx);
24341        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24342        // Disabling a breakpoint that doesn't exist should do nothing
24343        editor.move_up(&MoveUp, window, cx);
24344        editor.move_up(&MoveUp, window, cx);
24345        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24346    });
24347
24348    let breakpoints = editor.update(cx, |editor, cx| {
24349        editor
24350            .breakpoint_store()
24351            .as_ref()
24352            .unwrap()
24353            .read(cx)
24354            .all_source_breakpoints(cx)
24355    });
24356
24357    let disable_breakpoint = {
24358        let mut bp = Breakpoint::new_standard();
24359        bp.state = BreakpointState::Disabled;
24360        bp
24361    };
24362
24363    assert_eq!(1, breakpoints.len());
24364    assert_breakpoint(
24365        &breakpoints,
24366        &abs_path,
24367        vec![
24368            (0, disable_breakpoint.clone()),
24369            (2, Breakpoint::new_standard()),
24370            (3, disable_breakpoint.clone()),
24371        ],
24372    );
24373
24374    editor.update_in(cx, |editor, window, cx| {
24375        editor.move_to_beginning(&MoveToBeginning, window, cx);
24376        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24377        editor.move_to_end(&MoveToEnd, window, cx);
24378        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24379        editor.move_up(&MoveUp, window, cx);
24380        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24381    });
24382
24383    let breakpoints = editor.update(cx, |editor, cx| {
24384        editor
24385            .breakpoint_store()
24386            .as_ref()
24387            .unwrap()
24388            .read(cx)
24389            .all_source_breakpoints(cx)
24390    });
24391
24392    assert_eq!(1, breakpoints.len());
24393    assert_breakpoint(
24394        &breakpoints,
24395        &abs_path,
24396        vec![
24397            (0, Breakpoint::new_standard()),
24398            (2, disable_breakpoint),
24399            (3, Breakpoint::new_standard()),
24400        ],
24401    );
24402}
24403
24404#[gpui::test]
24405async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24406    init_test(cx, |_| {});
24407    let capabilities = lsp::ServerCapabilities {
24408        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24409            prepare_provider: Some(true),
24410            work_done_progress_options: Default::default(),
24411        })),
24412        ..Default::default()
24413    };
24414    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24415
24416    cx.set_state(indoc! {"
24417        struct Fˇoo {}
24418    "});
24419
24420    cx.update_editor(|editor, _, cx| {
24421        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24422        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24423        editor.highlight_background::<DocumentHighlightRead>(
24424            &[highlight_range],
24425            |_, theme| theme.colors().editor_document_highlight_read_background,
24426            cx,
24427        );
24428    });
24429
24430    let mut prepare_rename_handler = cx
24431        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24432            move |_, _, _| async move {
24433                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24434                    start: lsp::Position {
24435                        line: 0,
24436                        character: 7,
24437                    },
24438                    end: lsp::Position {
24439                        line: 0,
24440                        character: 10,
24441                    },
24442                })))
24443            },
24444        );
24445    let prepare_rename_task = cx
24446        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24447        .expect("Prepare rename was not started");
24448    prepare_rename_handler.next().await.unwrap();
24449    prepare_rename_task.await.expect("Prepare rename failed");
24450
24451    let mut rename_handler =
24452        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24453            let edit = lsp::TextEdit {
24454                range: lsp::Range {
24455                    start: lsp::Position {
24456                        line: 0,
24457                        character: 7,
24458                    },
24459                    end: lsp::Position {
24460                        line: 0,
24461                        character: 10,
24462                    },
24463                },
24464                new_text: "FooRenamed".to_string(),
24465            };
24466            Ok(Some(lsp::WorkspaceEdit::new(
24467                // Specify the same edit twice
24468                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24469            )))
24470        });
24471    let rename_task = cx
24472        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24473        .expect("Confirm rename was not started");
24474    rename_handler.next().await.unwrap();
24475    rename_task.await.expect("Confirm rename failed");
24476    cx.run_until_parked();
24477
24478    // Despite two edits, only one is actually applied as those are identical
24479    cx.assert_editor_state(indoc! {"
24480        struct FooRenamedˇ {}
24481    "});
24482}
24483
24484#[gpui::test]
24485async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24486    init_test(cx, |_| {});
24487    // These capabilities indicate that the server does not support prepare rename.
24488    let capabilities = lsp::ServerCapabilities {
24489        rename_provider: Some(lsp::OneOf::Left(true)),
24490        ..Default::default()
24491    };
24492    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24493
24494    cx.set_state(indoc! {"
24495        struct Fˇoo {}
24496    "});
24497
24498    cx.update_editor(|editor, _window, cx| {
24499        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24500        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24501        editor.highlight_background::<DocumentHighlightRead>(
24502            &[highlight_range],
24503            |_, theme| theme.colors().editor_document_highlight_read_background,
24504            cx,
24505        );
24506    });
24507
24508    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24509        .expect("Prepare rename was not started")
24510        .await
24511        .expect("Prepare rename failed");
24512
24513    let mut rename_handler =
24514        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24515            let edit = lsp::TextEdit {
24516                range: lsp::Range {
24517                    start: lsp::Position {
24518                        line: 0,
24519                        character: 7,
24520                    },
24521                    end: lsp::Position {
24522                        line: 0,
24523                        character: 10,
24524                    },
24525                },
24526                new_text: "FooRenamed".to_string(),
24527            };
24528            Ok(Some(lsp::WorkspaceEdit::new(
24529                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24530            )))
24531        });
24532    let rename_task = cx
24533        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24534        .expect("Confirm rename was not started");
24535    rename_handler.next().await.unwrap();
24536    rename_task.await.expect("Confirm rename failed");
24537    cx.run_until_parked();
24538
24539    // Correct range is renamed, as `surrounding_word` is used to find it.
24540    cx.assert_editor_state(indoc! {"
24541        struct FooRenamedˇ {}
24542    "});
24543}
24544
24545#[gpui::test]
24546async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24547    init_test(cx, |_| {});
24548    let mut cx = EditorTestContext::new(cx).await;
24549
24550    let language = Arc::new(
24551        Language::new(
24552            LanguageConfig::default(),
24553            Some(tree_sitter_html::LANGUAGE.into()),
24554        )
24555        .with_brackets_query(
24556            r#"
24557            ("<" @open "/>" @close)
24558            ("</" @open ">" @close)
24559            ("<" @open ">" @close)
24560            ("\"" @open "\"" @close)
24561            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24562        "#,
24563        )
24564        .unwrap(),
24565    );
24566    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24567
24568    cx.set_state(indoc! {"
24569        <span>ˇ</span>
24570    "});
24571    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24572    cx.assert_editor_state(indoc! {"
24573        <span>
24574        ˇ
24575        </span>
24576    "});
24577
24578    cx.set_state(indoc! {"
24579        <span><span></span>ˇ</span>
24580    "});
24581    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24582    cx.assert_editor_state(indoc! {"
24583        <span><span></span>
24584        ˇ</span>
24585    "});
24586
24587    cx.set_state(indoc! {"
24588        <span>ˇ
24589        </span>
24590    "});
24591    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24592    cx.assert_editor_state(indoc! {"
24593        <span>
24594        ˇ
24595        </span>
24596    "});
24597}
24598
24599#[gpui::test(iterations = 10)]
24600async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24601    init_test(cx, |_| {});
24602
24603    let fs = FakeFs::new(cx.executor());
24604    fs.insert_tree(
24605        path!("/dir"),
24606        json!({
24607            "a.ts": "a",
24608        }),
24609    )
24610    .await;
24611
24612    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24613    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24614    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24615
24616    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24617    language_registry.add(Arc::new(Language::new(
24618        LanguageConfig {
24619            name: "TypeScript".into(),
24620            matcher: LanguageMatcher {
24621                path_suffixes: vec!["ts".to_string()],
24622                ..Default::default()
24623            },
24624            ..Default::default()
24625        },
24626        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24627    )));
24628    let mut fake_language_servers = language_registry.register_fake_lsp(
24629        "TypeScript",
24630        FakeLspAdapter {
24631            capabilities: lsp::ServerCapabilities {
24632                code_lens_provider: Some(lsp::CodeLensOptions {
24633                    resolve_provider: Some(true),
24634                }),
24635                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24636                    commands: vec!["_the/command".to_string()],
24637                    ..lsp::ExecuteCommandOptions::default()
24638                }),
24639                ..lsp::ServerCapabilities::default()
24640            },
24641            ..FakeLspAdapter::default()
24642        },
24643    );
24644
24645    let editor = workspace
24646        .update(cx, |workspace, window, cx| {
24647            workspace.open_abs_path(
24648                PathBuf::from(path!("/dir/a.ts")),
24649                OpenOptions::default(),
24650                window,
24651                cx,
24652            )
24653        })
24654        .unwrap()
24655        .await
24656        .unwrap()
24657        .downcast::<Editor>()
24658        .unwrap();
24659    cx.executor().run_until_parked();
24660
24661    let fake_server = fake_language_servers.next().await.unwrap();
24662
24663    let buffer = editor.update(cx, |editor, cx| {
24664        editor
24665            .buffer()
24666            .read(cx)
24667            .as_singleton()
24668            .expect("have opened a single file by path")
24669    });
24670
24671    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24672    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24673    drop(buffer_snapshot);
24674    let actions = cx
24675        .update_window(*workspace, |_, window, cx| {
24676            project.code_actions(&buffer, anchor..anchor, window, cx)
24677        })
24678        .unwrap();
24679
24680    fake_server
24681        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24682            Ok(Some(vec![
24683                lsp::CodeLens {
24684                    range: lsp::Range::default(),
24685                    command: Some(lsp::Command {
24686                        title: "Code lens command".to_owned(),
24687                        command: "_the/command".to_owned(),
24688                        arguments: None,
24689                    }),
24690                    data: None,
24691                },
24692                lsp::CodeLens {
24693                    range: lsp::Range::default(),
24694                    command: Some(lsp::Command {
24695                        title: "Command not in capabilities".to_owned(),
24696                        command: "not in capabilities".to_owned(),
24697                        arguments: None,
24698                    }),
24699                    data: None,
24700                },
24701                lsp::CodeLens {
24702                    range: lsp::Range {
24703                        start: lsp::Position {
24704                            line: 1,
24705                            character: 1,
24706                        },
24707                        end: lsp::Position {
24708                            line: 1,
24709                            character: 1,
24710                        },
24711                    },
24712                    command: Some(lsp::Command {
24713                        title: "Command not in range".to_owned(),
24714                        command: "_the/command".to_owned(),
24715                        arguments: None,
24716                    }),
24717                    data: None,
24718                },
24719            ]))
24720        })
24721        .next()
24722        .await;
24723
24724    let actions = actions.await.unwrap();
24725    assert_eq!(
24726        actions.len(),
24727        1,
24728        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24729    );
24730    let action = actions[0].clone();
24731    let apply = project.update(cx, |project, cx| {
24732        project.apply_code_action(buffer.clone(), action, true, cx)
24733    });
24734
24735    // Resolving the code action does not populate its edits. In absence of
24736    // edits, we must execute the given command.
24737    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24738        |mut lens, _| async move {
24739            let lens_command = lens.command.as_mut().expect("should have a command");
24740            assert_eq!(lens_command.title, "Code lens command");
24741            lens_command.arguments = Some(vec![json!("the-argument")]);
24742            Ok(lens)
24743        },
24744    );
24745
24746    // While executing the command, the language server sends the editor
24747    // a `workspaceEdit` request.
24748    fake_server
24749        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24750            let fake = fake_server.clone();
24751            move |params, _| {
24752                assert_eq!(params.command, "_the/command");
24753                let fake = fake.clone();
24754                async move {
24755                    fake.server
24756                        .request::<lsp::request::ApplyWorkspaceEdit>(
24757                            lsp::ApplyWorkspaceEditParams {
24758                                label: None,
24759                                edit: lsp::WorkspaceEdit {
24760                                    changes: Some(
24761                                        [(
24762                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24763                                            vec![lsp::TextEdit {
24764                                                range: lsp::Range::new(
24765                                                    lsp::Position::new(0, 0),
24766                                                    lsp::Position::new(0, 0),
24767                                                ),
24768                                                new_text: "X".into(),
24769                                            }],
24770                                        )]
24771                                        .into_iter()
24772                                        .collect(),
24773                                    ),
24774                                    ..lsp::WorkspaceEdit::default()
24775                                },
24776                            },
24777                        )
24778                        .await
24779                        .into_response()
24780                        .unwrap();
24781                    Ok(Some(json!(null)))
24782                }
24783            }
24784        })
24785        .next()
24786        .await;
24787
24788    // Applying the code lens command returns a project transaction containing the edits
24789    // sent by the language server in its `workspaceEdit` request.
24790    let transaction = apply.await.unwrap();
24791    assert!(transaction.0.contains_key(&buffer));
24792    buffer.update(cx, |buffer, cx| {
24793        assert_eq!(buffer.text(), "Xa");
24794        buffer.undo(cx);
24795        assert_eq!(buffer.text(), "a");
24796    });
24797
24798    let actions_after_edits = cx
24799        .update_window(*workspace, |_, window, cx| {
24800            project.code_actions(&buffer, anchor..anchor, window, cx)
24801        })
24802        .unwrap()
24803        .await
24804        .unwrap();
24805    assert_eq!(
24806        actions, actions_after_edits,
24807        "For the same selection, same code lens actions should be returned"
24808    );
24809
24810    let _responses =
24811        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24812            panic!("No more code lens requests are expected");
24813        });
24814    editor.update_in(cx, |editor, window, cx| {
24815        editor.select_all(&SelectAll, window, cx);
24816    });
24817    cx.executor().run_until_parked();
24818    let new_actions = cx
24819        .update_window(*workspace, |_, window, cx| {
24820            project.code_actions(&buffer, anchor..anchor, window, cx)
24821        })
24822        .unwrap()
24823        .await
24824        .unwrap();
24825    assert_eq!(
24826        actions, new_actions,
24827        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24828    );
24829}
24830
24831#[gpui::test]
24832async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24833    init_test(cx, |_| {});
24834
24835    let fs = FakeFs::new(cx.executor());
24836    let main_text = r#"fn main() {
24837println!("1");
24838println!("2");
24839println!("3");
24840println!("4");
24841println!("5");
24842}"#;
24843    let lib_text = "mod foo {}";
24844    fs.insert_tree(
24845        path!("/a"),
24846        json!({
24847            "lib.rs": lib_text,
24848            "main.rs": main_text,
24849        }),
24850    )
24851    .await;
24852
24853    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24854    let (workspace, cx) =
24855        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24856    let worktree_id = workspace.update(cx, |workspace, cx| {
24857        workspace.project().update(cx, |project, cx| {
24858            project.worktrees(cx).next().unwrap().read(cx).id()
24859        })
24860    });
24861
24862    let expected_ranges = vec![
24863        Point::new(0, 0)..Point::new(0, 0),
24864        Point::new(1, 0)..Point::new(1, 1),
24865        Point::new(2, 0)..Point::new(2, 2),
24866        Point::new(3, 0)..Point::new(3, 3),
24867    ];
24868
24869    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24870    let editor_1 = workspace
24871        .update_in(cx, |workspace, window, cx| {
24872            workspace.open_path(
24873                (worktree_id, rel_path("main.rs")),
24874                Some(pane_1.downgrade()),
24875                true,
24876                window,
24877                cx,
24878            )
24879        })
24880        .unwrap()
24881        .await
24882        .downcast::<Editor>()
24883        .unwrap();
24884    pane_1.update(cx, |pane, cx| {
24885        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24886        open_editor.update(cx, |editor, cx| {
24887            assert_eq!(
24888                editor.display_text(cx),
24889                main_text,
24890                "Original main.rs text on initial open",
24891            );
24892            assert_eq!(
24893                editor
24894                    .selections
24895                    .all::<Point>(&editor.display_snapshot(cx))
24896                    .into_iter()
24897                    .map(|s| s.range())
24898                    .collect::<Vec<_>>(),
24899                vec![Point::zero()..Point::zero()],
24900                "Default selections on initial open",
24901            );
24902        })
24903    });
24904    editor_1.update_in(cx, |editor, window, cx| {
24905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24906            s.select_ranges(expected_ranges.clone());
24907        });
24908    });
24909
24910    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24911        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24912    });
24913    let editor_2 = workspace
24914        .update_in(cx, |workspace, window, cx| {
24915            workspace.open_path(
24916                (worktree_id, rel_path("main.rs")),
24917                Some(pane_2.downgrade()),
24918                true,
24919                window,
24920                cx,
24921            )
24922        })
24923        .unwrap()
24924        .await
24925        .downcast::<Editor>()
24926        .unwrap();
24927    pane_2.update(cx, |pane, cx| {
24928        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24929        open_editor.update(cx, |editor, cx| {
24930            assert_eq!(
24931                editor.display_text(cx),
24932                main_text,
24933                "Original main.rs text on initial open in another panel",
24934            );
24935            assert_eq!(
24936                editor
24937                    .selections
24938                    .all::<Point>(&editor.display_snapshot(cx))
24939                    .into_iter()
24940                    .map(|s| s.range())
24941                    .collect::<Vec<_>>(),
24942                vec![Point::zero()..Point::zero()],
24943                "Default selections on initial open in another panel",
24944            );
24945        })
24946    });
24947
24948    editor_2.update_in(cx, |editor, window, cx| {
24949        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24950    });
24951
24952    let _other_editor_1 = workspace
24953        .update_in(cx, |workspace, window, cx| {
24954            workspace.open_path(
24955                (worktree_id, rel_path("lib.rs")),
24956                Some(pane_1.downgrade()),
24957                true,
24958                window,
24959                cx,
24960            )
24961        })
24962        .unwrap()
24963        .await
24964        .downcast::<Editor>()
24965        .unwrap();
24966    pane_1
24967        .update_in(cx, |pane, window, cx| {
24968            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24969        })
24970        .await
24971        .unwrap();
24972    drop(editor_1);
24973    pane_1.update(cx, |pane, cx| {
24974        pane.active_item()
24975            .unwrap()
24976            .downcast::<Editor>()
24977            .unwrap()
24978            .update(cx, |editor, cx| {
24979                assert_eq!(
24980                    editor.display_text(cx),
24981                    lib_text,
24982                    "Other file should be open and active",
24983                );
24984            });
24985        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24986    });
24987
24988    let _other_editor_2 = workspace
24989        .update_in(cx, |workspace, window, cx| {
24990            workspace.open_path(
24991                (worktree_id, rel_path("lib.rs")),
24992                Some(pane_2.downgrade()),
24993                true,
24994                window,
24995                cx,
24996            )
24997        })
24998        .unwrap()
24999        .await
25000        .downcast::<Editor>()
25001        .unwrap();
25002    pane_2
25003        .update_in(cx, |pane, window, cx| {
25004            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25005        })
25006        .await
25007        .unwrap();
25008    drop(editor_2);
25009    pane_2.update(cx, |pane, cx| {
25010        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25011        open_editor.update(cx, |editor, cx| {
25012            assert_eq!(
25013                editor.display_text(cx),
25014                lib_text,
25015                "Other file should be open and active in another panel too",
25016            );
25017        });
25018        assert_eq!(
25019            pane.items().count(),
25020            1,
25021            "No other editors should be open in another pane",
25022        );
25023    });
25024
25025    let _editor_1_reopened = workspace
25026        .update_in(cx, |workspace, window, cx| {
25027            workspace.open_path(
25028                (worktree_id, rel_path("main.rs")),
25029                Some(pane_1.downgrade()),
25030                true,
25031                window,
25032                cx,
25033            )
25034        })
25035        .unwrap()
25036        .await
25037        .downcast::<Editor>()
25038        .unwrap();
25039    let _editor_2_reopened = workspace
25040        .update_in(cx, |workspace, window, cx| {
25041            workspace.open_path(
25042                (worktree_id, rel_path("main.rs")),
25043                Some(pane_2.downgrade()),
25044                true,
25045                window,
25046                cx,
25047            )
25048        })
25049        .unwrap()
25050        .await
25051        .downcast::<Editor>()
25052        .unwrap();
25053    pane_1.update(cx, |pane, cx| {
25054        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25055        open_editor.update(cx, |editor, cx| {
25056            assert_eq!(
25057                editor.display_text(cx),
25058                main_text,
25059                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25060            );
25061            assert_eq!(
25062                editor
25063                    .selections
25064                    .all::<Point>(&editor.display_snapshot(cx))
25065                    .into_iter()
25066                    .map(|s| s.range())
25067                    .collect::<Vec<_>>(),
25068                expected_ranges,
25069                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25070            );
25071        })
25072    });
25073    pane_2.update(cx, |pane, cx| {
25074        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25075        open_editor.update(cx, |editor, cx| {
25076            assert_eq!(
25077                editor.display_text(cx),
25078                r#"fn main() {
25079⋯rintln!("1");
25080⋯intln!("2");
25081⋯ntln!("3");
25082println!("4");
25083println!("5");
25084}"#,
25085                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25086            );
25087            assert_eq!(
25088                editor
25089                    .selections
25090                    .all::<Point>(&editor.display_snapshot(cx))
25091                    .into_iter()
25092                    .map(|s| s.range())
25093                    .collect::<Vec<_>>(),
25094                vec![Point::zero()..Point::zero()],
25095                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25096            );
25097        })
25098    });
25099}
25100
25101#[gpui::test]
25102async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25103    init_test(cx, |_| {});
25104
25105    let fs = FakeFs::new(cx.executor());
25106    let main_text = r#"fn main() {
25107println!("1");
25108println!("2");
25109println!("3");
25110println!("4");
25111println!("5");
25112}"#;
25113    let lib_text = "mod foo {}";
25114    fs.insert_tree(
25115        path!("/a"),
25116        json!({
25117            "lib.rs": lib_text,
25118            "main.rs": main_text,
25119        }),
25120    )
25121    .await;
25122
25123    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25124    let (workspace, cx) =
25125        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25126    let worktree_id = workspace.update(cx, |workspace, cx| {
25127        workspace.project().update(cx, |project, cx| {
25128            project.worktrees(cx).next().unwrap().read(cx).id()
25129        })
25130    });
25131
25132    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25133    let editor = workspace
25134        .update_in(cx, |workspace, window, cx| {
25135            workspace.open_path(
25136                (worktree_id, rel_path("main.rs")),
25137                Some(pane.downgrade()),
25138                true,
25139                window,
25140                cx,
25141            )
25142        })
25143        .unwrap()
25144        .await
25145        .downcast::<Editor>()
25146        .unwrap();
25147    pane.update(cx, |pane, cx| {
25148        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25149        open_editor.update(cx, |editor, cx| {
25150            assert_eq!(
25151                editor.display_text(cx),
25152                main_text,
25153                "Original main.rs text on initial open",
25154            );
25155        })
25156    });
25157    editor.update_in(cx, |editor, window, cx| {
25158        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25159    });
25160
25161    cx.update_global(|store: &mut SettingsStore, cx| {
25162        store.update_user_settings(cx, |s| {
25163            s.workspace.restore_on_file_reopen = Some(false);
25164        });
25165    });
25166    editor.update_in(cx, |editor, window, cx| {
25167        editor.fold_ranges(
25168            vec![
25169                Point::new(1, 0)..Point::new(1, 1),
25170                Point::new(2, 0)..Point::new(2, 2),
25171                Point::new(3, 0)..Point::new(3, 3),
25172            ],
25173            false,
25174            window,
25175            cx,
25176        );
25177    });
25178    pane.update_in(cx, |pane, window, cx| {
25179        pane.close_all_items(&CloseAllItems::default(), window, cx)
25180    })
25181    .await
25182    .unwrap();
25183    pane.update(cx, |pane, _| {
25184        assert!(pane.active_item().is_none());
25185    });
25186    cx.update_global(|store: &mut SettingsStore, cx| {
25187        store.update_user_settings(cx, |s| {
25188            s.workspace.restore_on_file_reopen = Some(true);
25189        });
25190    });
25191
25192    let _editor_reopened = workspace
25193        .update_in(cx, |workspace, window, cx| {
25194            workspace.open_path(
25195                (worktree_id, rel_path("main.rs")),
25196                Some(pane.downgrade()),
25197                true,
25198                window,
25199                cx,
25200            )
25201        })
25202        .unwrap()
25203        .await
25204        .downcast::<Editor>()
25205        .unwrap();
25206    pane.update(cx, |pane, cx| {
25207        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25208        open_editor.update(cx, |editor, cx| {
25209            assert_eq!(
25210                editor.display_text(cx),
25211                main_text,
25212                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25213            );
25214        })
25215    });
25216}
25217
25218#[gpui::test]
25219async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25220    struct EmptyModalView {
25221        focus_handle: gpui::FocusHandle,
25222    }
25223    impl EventEmitter<DismissEvent> for EmptyModalView {}
25224    impl Render for EmptyModalView {
25225        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25226            div()
25227        }
25228    }
25229    impl Focusable for EmptyModalView {
25230        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25231            self.focus_handle.clone()
25232        }
25233    }
25234    impl workspace::ModalView for EmptyModalView {}
25235    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25236        EmptyModalView {
25237            focus_handle: cx.focus_handle(),
25238        }
25239    }
25240
25241    init_test(cx, |_| {});
25242
25243    let fs = FakeFs::new(cx.executor());
25244    let project = Project::test(fs, [], cx).await;
25245    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25246    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25247    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25248    let editor = cx.new_window_entity(|window, cx| {
25249        Editor::new(
25250            EditorMode::full(),
25251            buffer,
25252            Some(project.clone()),
25253            window,
25254            cx,
25255        )
25256    });
25257    workspace
25258        .update(cx, |workspace, window, cx| {
25259            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25260        })
25261        .unwrap();
25262    editor.update_in(cx, |editor, window, cx| {
25263        editor.open_context_menu(&OpenContextMenu, window, cx);
25264        assert!(editor.mouse_context_menu.is_some());
25265    });
25266    workspace
25267        .update(cx, |workspace, window, cx| {
25268            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25269        })
25270        .unwrap();
25271    cx.read(|cx| {
25272        assert!(editor.read(cx).mouse_context_menu.is_none());
25273    });
25274}
25275
25276fn set_linked_edit_ranges(
25277    opening: (Point, Point),
25278    closing: (Point, Point),
25279    editor: &mut Editor,
25280    cx: &mut Context<Editor>,
25281) {
25282    let Some((buffer, _)) = editor
25283        .buffer
25284        .read(cx)
25285        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25286    else {
25287        panic!("Failed to get buffer for selection position");
25288    };
25289    let buffer = buffer.read(cx);
25290    let buffer_id = buffer.remote_id();
25291    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25292    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25293    let mut linked_ranges = HashMap::default();
25294    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25295    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25296}
25297
25298#[gpui::test]
25299async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25300    init_test(cx, |_| {});
25301
25302    let fs = FakeFs::new(cx.executor());
25303    fs.insert_file(path!("/file.html"), Default::default())
25304        .await;
25305
25306    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25307
25308    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25309    let html_language = Arc::new(Language::new(
25310        LanguageConfig {
25311            name: "HTML".into(),
25312            matcher: LanguageMatcher {
25313                path_suffixes: vec!["html".to_string()],
25314                ..LanguageMatcher::default()
25315            },
25316            brackets: BracketPairConfig {
25317                pairs: vec![BracketPair {
25318                    start: "<".into(),
25319                    end: ">".into(),
25320                    close: true,
25321                    ..Default::default()
25322                }],
25323                ..Default::default()
25324            },
25325            ..Default::default()
25326        },
25327        Some(tree_sitter_html::LANGUAGE.into()),
25328    ));
25329    language_registry.add(html_language);
25330    let mut fake_servers = language_registry.register_fake_lsp(
25331        "HTML",
25332        FakeLspAdapter {
25333            capabilities: lsp::ServerCapabilities {
25334                completion_provider: Some(lsp::CompletionOptions {
25335                    resolve_provider: Some(true),
25336                    ..Default::default()
25337                }),
25338                ..Default::default()
25339            },
25340            ..Default::default()
25341        },
25342    );
25343
25344    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25345    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25346
25347    let worktree_id = workspace
25348        .update(cx, |workspace, _window, cx| {
25349            workspace.project().update(cx, |project, cx| {
25350                project.worktrees(cx).next().unwrap().read(cx).id()
25351            })
25352        })
25353        .unwrap();
25354    project
25355        .update(cx, |project, cx| {
25356            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25357        })
25358        .await
25359        .unwrap();
25360    let editor = workspace
25361        .update(cx, |workspace, window, cx| {
25362            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25363        })
25364        .unwrap()
25365        .await
25366        .unwrap()
25367        .downcast::<Editor>()
25368        .unwrap();
25369
25370    let fake_server = fake_servers.next().await.unwrap();
25371    editor.update_in(cx, |editor, window, cx| {
25372        editor.set_text("<ad></ad>", window, cx);
25373        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25374            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25375        });
25376        set_linked_edit_ranges(
25377            (Point::new(0, 1), Point::new(0, 3)),
25378            (Point::new(0, 6), Point::new(0, 8)),
25379            editor,
25380            cx,
25381        );
25382    });
25383    let mut completion_handle =
25384        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25385            Ok(Some(lsp::CompletionResponse::Array(vec![
25386                lsp::CompletionItem {
25387                    label: "head".to_string(),
25388                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25389                        lsp::InsertReplaceEdit {
25390                            new_text: "head".to_string(),
25391                            insert: lsp::Range::new(
25392                                lsp::Position::new(0, 1),
25393                                lsp::Position::new(0, 3),
25394                            ),
25395                            replace: lsp::Range::new(
25396                                lsp::Position::new(0, 1),
25397                                lsp::Position::new(0, 3),
25398                            ),
25399                        },
25400                    )),
25401                    ..Default::default()
25402                },
25403            ])))
25404        });
25405    editor.update_in(cx, |editor, window, cx| {
25406        editor.show_completions(&ShowCompletions, window, cx);
25407    });
25408    cx.run_until_parked();
25409    completion_handle.next().await.unwrap();
25410    editor.update(cx, |editor, _| {
25411        assert!(
25412            editor.context_menu_visible(),
25413            "Completion menu should be visible"
25414        );
25415    });
25416    editor.update_in(cx, |editor, window, cx| {
25417        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25418    });
25419    cx.executor().run_until_parked();
25420    editor.update(cx, |editor, cx| {
25421        assert_eq!(editor.text(cx), "<head></head>");
25422    });
25423}
25424
25425#[gpui::test]
25426async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25427    init_test(cx, |_| {});
25428
25429    let mut cx = EditorTestContext::new(cx).await;
25430    let language = Arc::new(Language::new(
25431        LanguageConfig {
25432            name: "TSX".into(),
25433            matcher: LanguageMatcher {
25434                path_suffixes: vec!["tsx".to_string()],
25435                ..LanguageMatcher::default()
25436            },
25437            brackets: BracketPairConfig {
25438                pairs: vec![BracketPair {
25439                    start: "<".into(),
25440                    end: ">".into(),
25441                    close: true,
25442                    ..Default::default()
25443                }],
25444                ..Default::default()
25445            },
25446            linked_edit_characters: HashSet::from_iter(['.']),
25447            ..Default::default()
25448        },
25449        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25450    ));
25451    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25452
25453    // Test typing > does not extend linked pair
25454    cx.set_state("<divˇ<div></div>");
25455    cx.update_editor(|editor, _, cx| {
25456        set_linked_edit_ranges(
25457            (Point::new(0, 1), Point::new(0, 4)),
25458            (Point::new(0, 11), Point::new(0, 14)),
25459            editor,
25460            cx,
25461        );
25462    });
25463    cx.update_editor(|editor, window, cx| {
25464        editor.handle_input(">", window, cx);
25465    });
25466    cx.assert_editor_state("<div>ˇ<div></div>");
25467
25468    // Test typing . do extend linked pair
25469    cx.set_state("<Animatedˇ></Animated>");
25470    cx.update_editor(|editor, _, cx| {
25471        set_linked_edit_ranges(
25472            (Point::new(0, 1), Point::new(0, 9)),
25473            (Point::new(0, 12), Point::new(0, 20)),
25474            editor,
25475            cx,
25476        );
25477    });
25478    cx.update_editor(|editor, window, cx| {
25479        editor.handle_input(".", window, cx);
25480    });
25481    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25482    cx.update_editor(|editor, _, cx| {
25483        set_linked_edit_ranges(
25484            (Point::new(0, 1), Point::new(0, 10)),
25485            (Point::new(0, 13), Point::new(0, 21)),
25486            editor,
25487            cx,
25488        );
25489    });
25490    cx.update_editor(|editor, window, cx| {
25491        editor.handle_input("V", window, cx);
25492    });
25493    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25494}
25495
25496#[gpui::test]
25497async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25498    init_test(cx, |_| {});
25499
25500    let fs = FakeFs::new(cx.executor());
25501    fs.insert_tree(
25502        path!("/root"),
25503        json!({
25504            "a": {
25505                "main.rs": "fn main() {}",
25506            },
25507            "foo": {
25508                "bar": {
25509                    "external_file.rs": "pub mod external {}",
25510                }
25511            }
25512        }),
25513    )
25514    .await;
25515
25516    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25517    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25518    language_registry.add(rust_lang());
25519    let _fake_servers = language_registry.register_fake_lsp(
25520        "Rust",
25521        FakeLspAdapter {
25522            ..FakeLspAdapter::default()
25523        },
25524    );
25525    let (workspace, cx) =
25526        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25527    let worktree_id = workspace.update(cx, |workspace, cx| {
25528        workspace.project().update(cx, |project, cx| {
25529            project.worktrees(cx).next().unwrap().read(cx).id()
25530        })
25531    });
25532
25533    let assert_language_servers_count =
25534        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25535            project.update(cx, |project, cx| {
25536                let current = project
25537                    .lsp_store()
25538                    .read(cx)
25539                    .as_local()
25540                    .unwrap()
25541                    .language_servers
25542                    .len();
25543                assert_eq!(expected, current, "{context}");
25544            });
25545        };
25546
25547    assert_language_servers_count(
25548        0,
25549        "No servers should be running before any file is open",
25550        cx,
25551    );
25552    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25553    let main_editor = workspace
25554        .update_in(cx, |workspace, window, cx| {
25555            workspace.open_path(
25556                (worktree_id, rel_path("main.rs")),
25557                Some(pane.downgrade()),
25558                true,
25559                window,
25560                cx,
25561            )
25562        })
25563        .unwrap()
25564        .await
25565        .downcast::<Editor>()
25566        .unwrap();
25567    pane.update(cx, |pane, cx| {
25568        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25569        open_editor.update(cx, |editor, cx| {
25570            assert_eq!(
25571                editor.display_text(cx),
25572                "fn main() {}",
25573                "Original main.rs text on initial open",
25574            );
25575        });
25576        assert_eq!(open_editor, main_editor);
25577    });
25578    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25579
25580    let external_editor = workspace
25581        .update_in(cx, |workspace, window, cx| {
25582            workspace.open_abs_path(
25583                PathBuf::from("/root/foo/bar/external_file.rs"),
25584                OpenOptions::default(),
25585                window,
25586                cx,
25587            )
25588        })
25589        .await
25590        .expect("opening external file")
25591        .downcast::<Editor>()
25592        .expect("downcasted external file's open element to editor");
25593    pane.update(cx, |pane, cx| {
25594        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25595        open_editor.update(cx, |editor, cx| {
25596            assert_eq!(
25597                editor.display_text(cx),
25598                "pub mod external {}",
25599                "External file is open now",
25600            );
25601        });
25602        assert_eq!(open_editor, external_editor);
25603    });
25604    assert_language_servers_count(
25605        1,
25606        "Second, external, *.rs file should join the existing server",
25607        cx,
25608    );
25609
25610    pane.update_in(cx, |pane, window, cx| {
25611        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25612    })
25613    .await
25614    .unwrap();
25615    pane.update_in(cx, |pane, window, cx| {
25616        pane.navigate_backward(&Default::default(), window, cx);
25617    });
25618    cx.run_until_parked();
25619    pane.update(cx, |pane, cx| {
25620        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25621        open_editor.update(cx, |editor, cx| {
25622            assert_eq!(
25623                editor.display_text(cx),
25624                "pub mod external {}",
25625                "External file is open now",
25626            );
25627        });
25628    });
25629    assert_language_servers_count(
25630        1,
25631        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25632        cx,
25633    );
25634
25635    cx.update(|_, cx| {
25636        workspace::reload(cx);
25637    });
25638    assert_language_servers_count(
25639        1,
25640        "After reloading the worktree with local and external files opened, only one project should be started",
25641        cx,
25642    );
25643}
25644
25645#[gpui::test]
25646async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25647    init_test(cx, |_| {});
25648
25649    let mut cx = EditorTestContext::new(cx).await;
25650    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25651    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25652
25653    // test cursor move to start of each line on tab
25654    // for `if`, `elif`, `else`, `while`, `with` and `for`
25655    cx.set_state(indoc! {"
25656        def main():
25657        ˇ    for item in items:
25658        ˇ        while item.active:
25659        ˇ            if item.value > 10:
25660        ˇ                continue
25661        ˇ            elif item.value < 0:
25662        ˇ                break
25663        ˇ            else:
25664        ˇ                with item.context() as ctx:
25665        ˇ                    yield count
25666        ˇ        else:
25667        ˇ            log('while else')
25668        ˇ    else:
25669        ˇ        log('for else')
25670    "});
25671    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25672    cx.wait_for_autoindent_applied().await;
25673    cx.assert_editor_state(indoc! {"
25674        def main():
25675            ˇfor item in items:
25676                ˇwhile item.active:
25677                    ˇif item.value > 10:
25678                        ˇcontinue
25679                    ˇelif item.value < 0:
25680                        ˇbreak
25681                    ˇelse:
25682                        ˇwith item.context() as ctx:
25683                            ˇyield count
25684                ˇelse:
25685                    ˇlog('while else')
25686            ˇelse:
25687                ˇlog('for else')
25688    "});
25689    // test relative indent is preserved when tab
25690    // for `if`, `elif`, `else`, `while`, `with` and `for`
25691    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25692    cx.wait_for_autoindent_applied().await;
25693    cx.assert_editor_state(indoc! {"
25694        def main():
25695                ˇfor item in items:
25696                    ˇwhile item.active:
25697                        ˇif item.value > 10:
25698                            ˇcontinue
25699                        ˇelif item.value < 0:
25700                            ˇbreak
25701                        ˇelse:
25702                            ˇwith item.context() as ctx:
25703                                ˇyield count
25704                    ˇelse:
25705                        ˇlog('while else')
25706                ˇelse:
25707                    ˇlog('for else')
25708    "});
25709
25710    // test cursor move to start of each line on tab
25711    // for `try`, `except`, `else`, `finally`, `match` and `def`
25712    cx.set_state(indoc! {"
25713        def main():
25714        ˇ    try:
25715        ˇ        fetch()
25716        ˇ    except ValueError:
25717        ˇ        handle_error()
25718        ˇ    else:
25719        ˇ        match value:
25720        ˇ            case _:
25721        ˇ    finally:
25722        ˇ        def status():
25723        ˇ            return 0
25724    "});
25725    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25726    cx.wait_for_autoindent_applied().await;
25727    cx.assert_editor_state(indoc! {"
25728        def main():
25729            ˇtry:
25730                ˇfetch()
25731            ˇexcept ValueError:
25732                ˇhandle_error()
25733            ˇelse:
25734                ˇmatch value:
25735                    ˇcase _:
25736            ˇfinally:
25737                ˇdef status():
25738                    ˇreturn 0
25739    "});
25740    // test relative indent is preserved when tab
25741    // for `try`, `except`, `else`, `finally`, `match` and `def`
25742    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25743    cx.wait_for_autoindent_applied().await;
25744    cx.assert_editor_state(indoc! {"
25745        def main():
25746                ˇtry:
25747                    ˇfetch()
25748                ˇexcept ValueError:
25749                    ˇhandle_error()
25750                ˇelse:
25751                    ˇmatch value:
25752                        ˇcase _:
25753                ˇfinally:
25754                    ˇdef status():
25755                        ˇreturn 0
25756    "});
25757}
25758
25759#[gpui::test]
25760async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25761    init_test(cx, |_| {});
25762
25763    let mut cx = EditorTestContext::new(cx).await;
25764    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25765    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25766
25767    // test `else` auto outdents when typed inside `if` block
25768    cx.set_state(indoc! {"
25769        def main():
25770            if i == 2:
25771                return
25772                ˇ
25773    "});
25774    cx.update_editor(|editor, window, cx| {
25775        editor.handle_input("else:", window, cx);
25776    });
25777    cx.wait_for_autoindent_applied().await;
25778    cx.assert_editor_state(indoc! {"
25779        def main():
25780            if i == 2:
25781                return
25782            else:ˇ
25783    "});
25784
25785    // test `except` auto outdents when typed inside `try` block
25786    cx.set_state(indoc! {"
25787        def main():
25788            try:
25789                i = 2
25790                ˇ
25791    "});
25792    cx.update_editor(|editor, window, cx| {
25793        editor.handle_input("except:", window, cx);
25794    });
25795    cx.wait_for_autoindent_applied().await;
25796    cx.assert_editor_state(indoc! {"
25797        def main():
25798            try:
25799                i = 2
25800            except:ˇ
25801    "});
25802
25803    // test `else` auto outdents when typed inside `except` block
25804    cx.set_state(indoc! {"
25805        def main():
25806            try:
25807                i = 2
25808            except:
25809                j = 2
25810                ˇ
25811    "});
25812    cx.update_editor(|editor, window, cx| {
25813        editor.handle_input("else:", window, cx);
25814    });
25815    cx.wait_for_autoindent_applied().await;
25816    cx.assert_editor_state(indoc! {"
25817        def main():
25818            try:
25819                i = 2
25820            except:
25821                j = 2
25822            else:ˇ
25823    "});
25824
25825    // test `finally` auto outdents when typed inside `else` block
25826    cx.set_state(indoc! {"
25827        def main():
25828            try:
25829                i = 2
25830            except:
25831                j = 2
25832            else:
25833                k = 2
25834                ˇ
25835    "});
25836    cx.update_editor(|editor, window, cx| {
25837        editor.handle_input("finally:", window, cx);
25838    });
25839    cx.wait_for_autoindent_applied().await;
25840    cx.assert_editor_state(indoc! {"
25841        def main():
25842            try:
25843                i = 2
25844            except:
25845                j = 2
25846            else:
25847                k = 2
25848            finally:ˇ
25849    "});
25850
25851    // test `else` does not outdents when typed inside `except` block right after for block
25852    cx.set_state(indoc! {"
25853        def main():
25854            try:
25855                i = 2
25856            except:
25857                for i in range(n):
25858                    pass
25859                ˇ
25860    "});
25861    cx.update_editor(|editor, window, cx| {
25862        editor.handle_input("else:", window, cx);
25863    });
25864    cx.wait_for_autoindent_applied().await;
25865    cx.assert_editor_state(indoc! {"
25866        def main():
25867            try:
25868                i = 2
25869            except:
25870                for i in range(n):
25871                    pass
25872                else:ˇ
25873    "});
25874
25875    // test `finally` auto outdents when typed inside `else` block right after for block
25876    cx.set_state(indoc! {"
25877        def main():
25878            try:
25879                i = 2
25880            except:
25881                j = 2
25882            else:
25883                for i in range(n):
25884                    pass
25885                ˇ
25886    "});
25887    cx.update_editor(|editor, window, cx| {
25888        editor.handle_input("finally:", window, cx);
25889    });
25890    cx.wait_for_autoindent_applied().await;
25891    cx.assert_editor_state(indoc! {"
25892        def main():
25893            try:
25894                i = 2
25895            except:
25896                j = 2
25897            else:
25898                for i in range(n):
25899                    pass
25900            finally:ˇ
25901    "});
25902
25903    // test `except` outdents to inner "try" block
25904    cx.set_state(indoc! {"
25905        def main():
25906            try:
25907                i = 2
25908                if i == 2:
25909                    try:
25910                        i = 3
25911                        ˇ
25912    "});
25913    cx.update_editor(|editor, window, cx| {
25914        editor.handle_input("except:", window, cx);
25915    });
25916    cx.wait_for_autoindent_applied().await;
25917    cx.assert_editor_state(indoc! {"
25918        def main():
25919            try:
25920                i = 2
25921                if i == 2:
25922                    try:
25923                        i = 3
25924                    except:ˇ
25925    "});
25926
25927    // test `except` outdents to outer "try" block
25928    cx.set_state(indoc! {"
25929        def main():
25930            try:
25931                i = 2
25932                if i == 2:
25933                    try:
25934                        i = 3
25935                ˇ
25936    "});
25937    cx.update_editor(|editor, window, cx| {
25938        editor.handle_input("except:", window, cx);
25939    });
25940    cx.wait_for_autoindent_applied().await;
25941    cx.assert_editor_state(indoc! {"
25942        def main():
25943            try:
25944                i = 2
25945                if i == 2:
25946                    try:
25947                        i = 3
25948            except:ˇ
25949    "});
25950
25951    // test `else` stays at correct indent when typed after `for` block
25952    cx.set_state(indoc! {"
25953        def main():
25954            for i in range(10):
25955                if i == 3:
25956                    break
25957            ˇ
25958    "});
25959    cx.update_editor(|editor, window, cx| {
25960        editor.handle_input("else:", window, cx);
25961    });
25962    cx.wait_for_autoindent_applied().await;
25963    cx.assert_editor_state(indoc! {"
25964        def main():
25965            for i in range(10):
25966                if i == 3:
25967                    break
25968            else:ˇ
25969    "});
25970
25971    // test does not outdent on typing after line with square brackets
25972    cx.set_state(indoc! {"
25973        def f() -> list[str]:
25974            ˇ
25975    "});
25976    cx.update_editor(|editor, window, cx| {
25977        editor.handle_input("a", window, cx);
25978    });
25979    cx.wait_for_autoindent_applied().await;
25980    cx.assert_editor_state(indoc! {"
25981        def f() -> list[str]:
2598225983    "});
25984
25985    // test does not outdent on typing : after case keyword
25986    cx.set_state(indoc! {"
25987        match 1:
25988            caseˇ
25989    "});
25990    cx.update_editor(|editor, window, cx| {
25991        editor.handle_input(":", window, cx);
25992    });
25993    cx.wait_for_autoindent_applied().await;
25994    cx.assert_editor_state(indoc! {"
25995        match 1:
25996            case:ˇ
25997    "});
25998}
25999
26000#[gpui::test]
26001async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26002    init_test(cx, |_| {});
26003    update_test_language_settings(cx, |settings| {
26004        settings.defaults.extend_comment_on_newline = Some(false);
26005    });
26006    let mut cx = EditorTestContext::new(cx).await;
26007    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26008    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26009
26010    // test correct indent after newline on comment
26011    cx.set_state(indoc! {"
26012        # COMMENT:ˇ
26013    "});
26014    cx.update_editor(|editor, window, cx| {
26015        editor.newline(&Newline, window, cx);
26016    });
26017    cx.wait_for_autoindent_applied().await;
26018    cx.assert_editor_state(indoc! {"
26019        # COMMENT:
26020        ˇ
26021    "});
26022
26023    // test correct indent after newline in brackets
26024    cx.set_state(indoc! {"
26025        {ˇ}
26026    "});
26027    cx.update_editor(|editor, window, cx| {
26028        editor.newline(&Newline, window, cx);
26029    });
26030    cx.wait_for_autoindent_applied().await;
26031    cx.assert_editor_state(indoc! {"
26032        {
26033            ˇ
26034        }
26035    "});
26036
26037    cx.set_state(indoc! {"
26038        (ˇ)
26039    "});
26040    cx.update_editor(|editor, window, cx| {
26041        editor.newline(&Newline, window, cx);
26042    });
26043    cx.run_until_parked();
26044    cx.assert_editor_state(indoc! {"
26045        (
26046            ˇ
26047        )
26048    "});
26049
26050    // do not indent after empty lists or dictionaries
26051    cx.set_state(indoc! {"
26052        a = []ˇ
26053    "});
26054    cx.update_editor(|editor, window, cx| {
26055        editor.newline(&Newline, window, cx);
26056    });
26057    cx.run_until_parked();
26058    cx.assert_editor_state(indoc! {"
26059        a = []
26060        ˇ
26061    "});
26062}
26063
26064#[gpui::test]
26065async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26066    init_test(cx, |_| {});
26067
26068    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26069    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26070    language_registry.add(markdown_lang());
26071    language_registry.add(python_lang);
26072
26073    let mut cx = EditorTestContext::new(cx).await;
26074    cx.update_buffer(|buffer, cx| {
26075        buffer.set_language_registry(language_registry);
26076        buffer.set_language(Some(markdown_lang()), cx);
26077    });
26078
26079    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26080    cx.set_state(indoc! {"
26081        # Heading
26082
26083        ```python
26084        def main():
26085            if condition:
26086                pass
26087                ˇ
26088        ```
26089    "});
26090    cx.update_editor(|editor, window, cx| {
26091        editor.handle_input("else:", window, cx);
26092    });
26093    cx.run_until_parked();
26094    cx.assert_editor_state(indoc! {"
26095        # Heading
26096
26097        ```python
26098        def main():
26099            if condition:
26100                pass
26101            else:ˇ
26102        ```
26103    "});
26104}
26105
26106#[gpui::test]
26107async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26108    init_test(cx, |_| {});
26109
26110    let mut cx = EditorTestContext::new(cx).await;
26111    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26112    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26113
26114    // test cursor move to start of each line on tab
26115    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26116    cx.set_state(indoc! {"
26117        function main() {
26118        ˇ    for item in $items; do
26119        ˇ        while [ -n \"$item\" ]; do
26120        ˇ            if [ \"$value\" -gt 10 ]; then
26121        ˇ                continue
26122        ˇ            elif [ \"$value\" -lt 0 ]; then
26123        ˇ                break
26124        ˇ            else
26125        ˇ                echo \"$item\"
26126        ˇ            fi
26127        ˇ        done
26128        ˇ    done
26129        ˇ}
26130    "});
26131    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26132    cx.wait_for_autoindent_applied().await;
26133    cx.assert_editor_state(indoc! {"
26134        function main() {
26135            ˇfor item in $items; do
26136                ˇwhile [ -n \"$item\" ]; do
26137                    ˇif [ \"$value\" -gt 10 ]; then
26138                        ˇcontinue
26139                    ˇelif [ \"$value\" -lt 0 ]; then
26140                        ˇbreak
26141                    ˇelse
26142                        ˇecho \"$item\"
26143                    ˇfi
26144                ˇdone
26145            ˇdone
26146        ˇ}
26147    "});
26148    // test relative indent is preserved when tab
26149    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26150    cx.wait_for_autoindent_applied().await;
26151    cx.assert_editor_state(indoc! {"
26152        function main() {
26153                ˇfor item in $items; do
26154                    ˇwhile [ -n \"$item\" ]; do
26155                        ˇif [ \"$value\" -gt 10 ]; then
26156                            ˇcontinue
26157                        ˇelif [ \"$value\" -lt 0 ]; then
26158                            ˇbreak
26159                        ˇelse
26160                            ˇecho \"$item\"
26161                        ˇfi
26162                    ˇdone
26163                ˇdone
26164            ˇ}
26165    "});
26166
26167    // test cursor move to start of each line on tab
26168    // for `case` statement with patterns
26169    cx.set_state(indoc! {"
26170        function handle() {
26171        ˇ    case \"$1\" in
26172        ˇ        start)
26173        ˇ            echo \"a\"
26174        ˇ            ;;
26175        ˇ        stop)
26176        ˇ            echo \"b\"
26177        ˇ            ;;
26178        ˇ        *)
26179        ˇ            echo \"c\"
26180        ˇ            ;;
26181        ˇ    esac
26182        ˇ}
26183    "});
26184    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26185    cx.wait_for_autoindent_applied().await;
26186    cx.assert_editor_state(indoc! {"
26187        function handle() {
26188            ˇcase \"$1\" in
26189                ˇstart)
26190                    ˇecho \"a\"
26191                    ˇ;;
26192                ˇstop)
26193                    ˇecho \"b\"
26194                    ˇ;;
26195                ˇ*)
26196                    ˇecho \"c\"
26197                    ˇ;;
26198            ˇesac
26199        ˇ}
26200    "});
26201}
26202
26203#[gpui::test]
26204async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26205    init_test(cx, |_| {});
26206
26207    let mut cx = EditorTestContext::new(cx).await;
26208    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26209    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26210
26211    // test indents on comment insert
26212    cx.set_state(indoc! {"
26213        function main() {
26214        ˇ    for item in $items; do
26215        ˇ        while [ -n \"$item\" ]; do
26216        ˇ            if [ \"$value\" -gt 10 ]; then
26217        ˇ                continue
26218        ˇ            elif [ \"$value\" -lt 0 ]; then
26219        ˇ                break
26220        ˇ            else
26221        ˇ                echo \"$item\"
26222        ˇ            fi
26223        ˇ        done
26224        ˇ    done
26225        ˇ}
26226    "});
26227    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26228    cx.wait_for_autoindent_applied().await;
26229    cx.assert_editor_state(indoc! {"
26230        function main() {
26231        #ˇ    for item in $items; do
26232        #ˇ        while [ -n \"$item\" ]; do
26233        #ˇ            if [ \"$value\" -gt 10 ]; then
26234        #ˇ                continue
26235        #ˇ            elif [ \"$value\" -lt 0 ]; then
26236        #ˇ                break
26237        #ˇ            else
26238        #ˇ                echo \"$item\"
26239        #ˇ            fi
26240        #ˇ        done
26241        #ˇ    done
26242        #ˇ}
26243    "});
26244}
26245
26246#[gpui::test]
26247async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26248    init_test(cx, |_| {});
26249
26250    let mut cx = EditorTestContext::new(cx).await;
26251    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26252    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26253
26254    // test `else` auto outdents when typed inside `if` block
26255    cx.set_state(indoc! {"
26256        if [ \"$1\" = \"test\" ]; then
26257            echo \"foo bar\"
26258            ˇ
26259    "});
26260    cx.update_editor(|editor, window, cx| {
26261        editor.handle_input("else", window, cx);
26262    });
26263    cx.wait_for_autoindent_applied().await;
26264    cx.assert_editor_state(indoc! {"
26265        if [ \"$1\" = \"test\" ]; then
26266            echo \"foo bar\"
26267        elseˇ
26268    "});
26269
26270    // test `elif` auto outdents when typed inside `if` block
26271    cx.set_state(indoc! {"
26272        if [ \"$1\" = \"test\" ]; then
26273            echo \"foo bar\"
26274            ˇ
26275    "});
26276    cx.update_editor(|editor, window, cx| {
26277        editor.handle_input("elif", window, cx);
26278    });
26279    cx.wait_for_autoindent_applied().await;
26280    cx.assert_editor_state(indoc! {"
26281        if [ \"$1\" = \"test\" ]; then
26282            echo \"foo bar\"
26283        elifˇ
26284    "});
26285
26286    // test `fi` auto outdents when typed inside `else` block
26287    cx.set_state(indoc! {"
26288        if [ \"$1\" = \"test\" ]; then
26289            echo \"foo bar\"
26290        else
26291            echo \"bar baz\"
26292            ˇ
26293    "});
26294    cx.update_editor(|editor, window, cx| {
26295        editor.handle_input("fi", window, cx);
26296    });
26297    cx.wait_for_autoindent_applied().await;
26298    cx.assert_editor_state(indoc! {"
26299        if [ \"$1\" = \"test\" ]; then
26300            echo \"foo bar\"
26301        else
26302            echo \"bar baz\"
26303        fiˇ
26304    "});
26305
26306    // test `done` auto outdents when typed inside `while` block
26307    cx.set_state(indoc! {"
26308        while read line; do
26309            echo \"$line\"
26310            ˇ
26311    "});
26312    cx.update_editor(|editor, window, cx| {
26313        editor.handle_input("done", window, cx);
26314    });
26315    cx.wait_for_autoindent_applied().await;
26316    cx.assert_editor_state(indoc! {"
26317        while read line; do
26318            echo \"$line\"
26319        doneˇ
26320    "});
26321
26322    // test `done` auto outdents when typed inside `for` block
26323    cx.set_state(indoc! {"
26324        for file in *.txt; do
26325            cat \"$file\"
26326            ˇ
26327    "});
26328    cx.update_editor(|editor, window, cx| {
26329        editor.handle_input("done", window, cx);
26330    });
26331    cx.wait_for_autoindent_applied().await;
26332    cx.assert_editor_state(indoc! {"
26333        for file in *.txt; do
26334            cat \"$file\"
26335        doneˇ
26336    "});
26337
26338    // test `esac` auto outdents when typed inside `case` block
26339    cx.set_state(indoc! {"
26340        case \"$1\" in
26341            start)
26342                echo \"foo bar\"
26343                ;;
26344            stop)
26345                echo \"bar baz\"
26346                ;;
26347            ˇ
26348    "});
26349    cx.update_editor(|editor, window, cx| {
26350        editor.handle_input("esac", window, cx);
26351    });
26352    cx.wait_for_autoindent_applied().await;
26353    cx.assert_editor_state(indoc! {"
26354        case \"$1\" in
26355            start)
26356                echo \"foo bar\"
26357                ;;
26358            stop)
26359                echo \"bar baz\"
26360                ;;
26361        esacˇ
26362    "});
26363
26364    // test `*)` auto outdents when typed inside `case` block
26365    cx.set_state(indoc! {"
26366        case \"$1\" in
26367            start)
26368                echo \"foo bar\"
26369                ;;
26370                ˇ
26371    "});
26372    cx.update_editor(|editor, window, cx| {
26373        editor.handle_input("*)", window, cx);
26374    });
26375    cx.wait_for_autoindent_applied().await;
26376    cx.assert_editor_state(indoc! {"
26377        case \"$1\" in
26378            start)
26379                echo \"foo bar\"
26380                ;;
26381            *)ˇ
26382    "});
26383
26384    // test `fi` outdents to correct level with nested if blocks
26385    cx.set_state(indoc! {"
26386        if [ \"$1\" = \"test\" ]; then
26387            echo \"outer if\"
26388            if [ \"$2\" = \"debug\" ]; then
26389                echo \"inner if\"
26390                ˇ
26391    "});
26392    cx.update_editor(|editor, window, cx| {
26393        editor.handle_input("fi", window, cx);
26394    });
26395    cx.wait_for_autoindent_applied().await;
26396    cx.assert_editor_state(indoc! {"
26397        if [ \"$1\" = \"test\" ]; then
26398            echo \"outer if\"
26399            if [ \"$2\" = \"debug\" ]; then
26400                echo \"inner if\"
26401            fiˇ
26402    "});
26403}
26404
26405#[gpui::test]
26406async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26407    init_test(cx, |_| {});
26408    update_test_language_settings(cx, |settings| {
26409        settings.defaults.extend_comment_on_newline = Some(false);
26410    });
26411    let mut cx = EditorTestContext::new(cx).await;
26412    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26413    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26414
26415    // test correct indent after newline on comment
26416    cx.set_state(indoc! {"
26417        # COMMENT:ˇ
26418    "});
26419    cx.update_editor(|editor, window, cx| {
26420        editor.newline(&Newline, window, cx);
26421    });
26422    cx.wait_for_autoindent_applied().await;
26423    cx.assert_editor_state(indoc! {"
26424        # COMMENT:
26425        ˇ
26426    "});
26427
26428    // test correct indent after newline after `then`
26429    cx.set_state(indoc! {"
26430
26431        if [ \"$1\" = \"test\" ]; thenˇ
26432    "});
26433    cx.update_editor(|editor, window, cx| {
26434        editor.newline(&Newline, window, cx);
26435    });
26436    cx.wait_for_autoindent_applied().await;
26437    cx.assert_editor_state(indoc! {"
26438
26439        if [ \"$1\" = \"test\" ]; then
26440            ˇ
26441    "});
26442
26443    // test correct indent after newline after `else`
26444    cx.set_state(indoc! {"
26445        if [ \"$1\" = \"test\" ]; then
26446        elseˇ
26447    "});
26448    cx.update_editor(|editor, window, cx| {
26449        editor.newline(&Newline, window, cx);
26450    });
26451    cx.wait_for_autoindent_applied().await;
26452    cx.assert_editor_state(indoc! {"
26453        if [ \"$1\" = \"test\" ]; then
26454        else
26455            ˇ
26456    "});
26457
26458    // test correct indent after newline after `elif`
26459    cx.set_state(indoc! {"
26460        if [ \"$1\" = \"test\" ]; then
26461        elifˇ
26462    "});
26463    cx.update_editor(|editor, window, cx| {
26464        editor.newline(&Newline, window, cx);
26465    });
26466    cx.wait_for_autoindent_applied().await;
26467    cx.assert_editor_state(indoc! {"
26468        if [ \"$1\" = \"test\" ]; then
26469        elif
26470            ˇ
26471    "});
26472
26473    // test correct indent after newline after `do`
26474    cx.set_state(indoc! {"
26475        for file in *.txt; doˇ
26476    "});
26477    cx.update_editor(|editor, window, cx| {
26478        editor.newline(&Newline, window, cx);
26479    });
26480    cx.wait_for_autoindent_applied().await;
26481    cx.assert_editor_state(indoc! {"
26482        for file in *.txt; do
26483            ˇ
26484    "});
26485
26486    // test correct indent after newline after case pattern
26487    cx.set_state(indoc! {"
26488        case \"$1\" in
26489            start)ˇ
26490    "});
26491    cx.update_editor(|editor, window, cx| {
26492        editor.newline(&Newline, window, cx);
26493    });
26494    cx.wait_for_autoindent_applied().await;
26495    cx.assert_editor_state(indoc! {"
26496        case \"$1\" in
26497            start)
26498                ˇ
26499    "});
26500
26501    // test correct indent after newline after case pattern
26502    cx.set_state(indoc! {"
26503        case \"$1\" in
26504            start)
26505                ;;
26506            *)ˇ
26507    "});
26508    cx.update_editor(|editor, window, cx| {
26509        editor.newline(&Newline, window, cx);
26510    });
26511    cx.wait_for_autoindent_applied().await;
26512    cx.assert_editor_state(indoc! {"
26513        case \"$1\" in
26514            start)
26515                ;;
26516            *)
26517                ˇ
26518    "});
26519
26520    // test correct indent after newline after function opening brace
26521    cx.set_state(indoc! {"
26522        function test() {ˇ}
26523    "});
26524    cx.update_editor(|editor, window, cx| {
26525        editor.newline(&Newline, window, cx);
26526    });
26527    cx.wait_for_autoindent_applied().await;
26528    cx.assert_editor_state(indoc! {"
26529        function test() {
26530            ˇ
26531        }
26532    "});
26533
26534    // test no extra indent after semicolon on same line
26535    cx.set_state(indoc! {"
26536        echo \"test\"26537    "});
26538    cx.update_editor(|editor, window, cx| {
26539        editor.newline(&Newline, window, cx);
26540    });
26541    cx.wait_for_autoindent_applied().await;
26542    cx.assert_editor_state(indoc! {"
26543        echo \"test\";
26544        ˇ
26545    "});
26546}
26547
26548fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26549    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26550    point..point
26551}
26552
26553#[track_caller]
26554fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26555    let (text, ranges) = marked_text_ranges(marked_text, true);
26556    assert_eq!(editor.text(cx), text);
26557    assert_eq!(
26558        editor.selections.ranges(&editor.display_snapshot(cx)),
26559        ranges
26560            .iter()
26561            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26562            .collect::<Vec<_>>(),
26563        "Assert selections are {}",
26564        marked_text
26565    );
26566}
26567
26568pub fn handle_signature_help_request(
26569    cx: &mut EditorLspTestContext,
26570    mocked_response: lsp::SignatureHelp,
26571) -> impl Future<Output = ()> + use<> {
26572    let mut request =
26573        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26574            let mocked_response = mocked_response.clone();
26575            async move { Ok(Some(mocked_response)) }
26576        });
26577
26578    async move {
26579        request.next().await;
26580    }
26581}
26582
26583#[track_caller]
26584pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26585    cx.update_editor(|editor, _, _| {
26586        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26587            let entries = menu.entries.borrow();
26588            let entries = entries
26589                .iter()
26590                .map(|entry| entry.string.as_str())
26591                .collect::<Vec<_>>();
26592            assert_eq!(entries, expected);
26593        } else {
26594            panic!("Expected completions menu");
26595        }
26596    });
26597}
26598
26599#[gpui::test]
26600async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26601    init_test(cx, |_| {});
26602    let mut cx = EditorLspTestContext::new_rust(
26603        lsp::ServerCapabilities {
26604            completion_provider: Some(lsp::CompletionOptions {
26605                ..Default::default()
26606            }),
26607            ..Default::default()
26608        },
26609        cx,
26610    )
26611    .await;
26612    cx.lsp
26613        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26614            Ok(Some(lsp::CompletionResponse::Array(vec![
26615                lsp::CompletionItem {
26616                    label: "unsafe".into(),
26617                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26618                        range: lsp::Range {
26619                            start: lsp::Position {
26620                                line: 0,
26621                                character: 9,
26622                            },
26623                            end: lsp::Position {
26624                                line: 0,
26625                                character: 11,
26626                            },
26627                        },
26628                        new_text: "unsafe".to_string(),
26629                    })),
26630                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26631                    ..Default::default()
26632                },
26633            ])))
26634        });
26635
26636    cx.update_editor(|editor, _, cx| {
26637        editor.project().unwrap().update(cx, |project, cx| {
26638            project.snippets().update(cx, |snippets, _cx| {
26639                snippets.add_snippet_for_test(
26640                    None,
26641                    PathBuf::from("test_snippets.json"),
26642                    vec![
26643                        Arc::new(project::snippet_provider::Snippet {
26644                            prefix: vec![
26645                                "unlimited word count".to_string(),
26646                                "unlimit word count".to_string(),
26647                                "unlimited unknown".to_string(),
26648                            ],
26649                            body: "this is many words".to_string(),
26650                            description: Some("description".to_string()),
26651                            name: "multi-word snippet test".to_string(),
26652                        }),
26653                        Arc::new(project::snippet_provider::Snippet {
26654                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26655                            body: "fewer words".to_string(),
26656                            description: Some("alt description".to_string()),
26657                            name: "other name".to_string(),
26658                        }),
26659                        Arc::new(project::snippet_provider::Snippet {
26660                            prefix: vec!["ab aa".to_string()],
26661                            body: "abcd".to_string(),
26662                            description: None,
26663                            name: "alphabet".to_string(),
26664                        }),
26665                    ],
26666                );
26667            });
26668        })
26669    });
26670
26671    let get_completions = |cx: &mut EditorLspTestContext| {
26672        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26673            Some(CodeContextMenu::Completions(context_menu)) => {
26674                let entries = context_menu.entries.borrow();
26675                entries
26676                    .iter()
26677                    .map(|entry| entry.string.clone())
26678                    .collect_vec()
26679            }
26680            _ => vec![],
26681        })
26682    };
26683
26684    // snippets:
26685    //  @foo
26686    //  foo bar
26687    //
26688    // when typing:
26689    //
26690    // when typing:
26691    //  - if I type a symbol "open the completions with snippets only"
26692    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26693    //
26694    // stuff we need:
26695    //  - filtering logic change?
26696    //  - remember how far back the completion started.
26697
26698    let test_cases: &[(&str, &[&str])] = &[
26699        (
26700            "un",
26701            &[
26702                "unsafe",
26703                "unlimit word count",
26704                "unlimited unknown",
26705                "unlimited word count",
26706                "unsnip",
26707            ],
26708        ),
26709        (
26710            "u ",
26711            &[
26712                "unlimit word count",
26713                "unlimited unknown",
26714                "unlimited word count",
26715            ],
26716        ),
26717        ("u a", &["ab aa", "unsafe"]), // unsAfe
26718        (
26719            "u u",
26720            &[
26721                "unsafe",
26722                "unlimit word count",
26723                "unlimited unknown", // ranked highest among snippets
26724                "unlimited word count",
26725                "unsnip",
26726            ],
26727        ),
26728        ("uw c", &["unlimit word count", "unlimited word count"]),
26729        (
26730            "u w",
26731            &[
26732                "unlimit word count",
26733                "unlimited word count",
26734                "unlimited unknown",
26735            ],
26736        ),
26737        ("u w ", &["unlimit word count", "unlimited word count"]),
26738        (
26739            "u ",
26740            &[
26741                "unlimit word count",
26742                "unlimited unknown",
26743                "unlimited word count",
26744            ],
26745        ),
26746        ("wor", &[]),
26747        ("uf", &["unsafe"]),
26748        ("af", &["unsafe"]),
26749        ("afu", &[]),
26750        (
26751            "ue",
26752            &["unsafe", "unlimited unknown", "unlimited word count"],
26753        ),
26754        ("@", &["@few"]),
26755        ("@few", &["@few"]),
26756        ("@ ", &[]),
26757        ("a@", &["@few"]),
26758        ("a@f", &["@few", "unsafe"]),
26759        ("a@fw", &["@few"]),
26760        ("a", &["ab aa", "unsafe"]),
26761        ("aa", &["ab aa"]),
26762        ("aaa", &["ab aa"]),
26763        ("ab", &["ab aa"]),
26764        ("ab ", &["ab aa"]),
26765        ("ab a", &["ab aa", "unsafe"]),
26766        ("ab ab", &["ab aa"]),
26767        ("ab ab aa", &["ab aa"]),
26768    ];
26769
26770    for &(input_to_simulate, expected_completions) in test_cases {
26771        cx.set_state("fn a() { ˇ }\n");
26772        for c in input_to_simulate.split("") {
26773            cx.simulate_input(c);
26774            cx.run_until_parked();
26775        }
26776        let expected_completions = expected_completions
26777            .iter()
26778            .map(|s| s.to_string())
26779            .collect_vec();
26780        assert_eq!(
26781            get_completions(&mut cx),
26782            expected_completions,
26783            "< actual / expected >, input = {input_to_simulate:?}",
26784        );
26785    }
26786}
26787
26788/// Handle completion request passing a marked string specifying where the completion
26789/// should be triggered from using '|' character, what range should be replaced, and what completions
26790/// should be returned using '<' and '>' to delimit the range.
26791///
26792/// Also see `handle_completion_request_with_insert_and_replace`.
26793#[track_caller]
26794pub fn handle_completion_request(
26795    marked_string: &str,
26796    completions: Vec<&'static str>,
26797    is_incomplete: bool,
26798    counter: Arc<AtomicUsize>,
26799    cx: &mut EditorLspTestContext,
26800) -> impl Future<Output = ()> {
26801    let complete_from_marker: TextRangeMarker = '|'.into();
26802    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26803    let (_, mut marked_ranges) = marked_text_ranges_by(
26804        marked_string,
26805        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26806    );
26807
26808    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26809        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26810    ));
26811    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26812    let replace_range =
26813        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26814
26815    let mut request =
26816        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26817            let completions = completions.clone();
26818            counter.fetch_add(1, atomic::Ordering::Release);
26819            async move {
26820                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26821                assert_eq!(
26822                    params.text_document_position.position,
26823                    complete_from_position
26824                );
26825                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26826                    is_incomplete,
26827                    item_defaults: None,
26828                    items: completions
26829                        .iter()
26830                        .map(|completion_text| lsp::CompletionItem {
26831                            label: completion_text.to_string(),
26832                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26833                                range: replace_range,
26834                                new_text: completion_text.to_string(),
26835                            })),
26836                            ..Default::default()
26837                        })
26838                        .collect(),
26839                })))
26840            }
26841        });
26842
26843    async move {
26844        request.next().await;
26845    }
26846}
26847
26848/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26849/// given instead, which also contains an `insert` range.
26850///
26851/// This function uses markers to define ranges:
26852/// - `|` marks the cursor position
26853/// - `<>` marks the replace range
26854/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26855pub fn handle_completion_request_with_insert_and_replace(
26856    cx: &mut EditorLspTestContext,
26857    marked_string: &str,
26858    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26859    counter: Arc<AtomicUsize>,
26860) -> impl Future<Output = ()> {
26861    let complete_from_marker: TextRangeMarker = '|'.into();
26862    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26863    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26864
26865    let (_, mut marked_ranges) = marked_text_ranges_by(
26866        marked_string,
26867        vec![
26868            complete_from_marker.clone(),
26869            replace_range_marker.clone(),
26870            insert_range_marker.clone(),
26871        ],
26872    );
26873
26874    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26875        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26876    ));
26877    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26878    let replace_range =
26879        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26880
26881    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26882        Some(ranges) if !ranges.is_empty() => {
26883            let range1 = ranges[0].clone();
26884            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26885        }
26886        _ => lsp::Range {
26887            start: replace_range.start,
26888            end: complete_from_position,
26889        },
26890    };
26891
26892    let mut request =
26893        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26894            let completions = completions.clone();
26895            counter.fetch_add(1, atomic::Ordering::Release);
26896            async move {
26897                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26898                assert_eq!(
26899                    params.text_document_position.position, complete_from_position,
26900                    "marker `|` position doesn't match",
26901                );
26902                Ok(Some(lsp::CompletionResponse::Array(
26903                    completions
26904                        .iter()
26905                        .map(|(label, new_text)| lsp::CompletionItem {
26906                            label: label.to_string(),
26907                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26908                                lsp::InsertReplaceEdit {
26909                                    insert: insert_range,
26910                                    replace: replace_range,
26911                                    new_text: new_text.to_string(),
26912                                },
26913                            )),
26914                            ..Default::default()
26915                        })
26916                        .collect(),
26917                )))
26918            }
26919        });
26920
26921    async move {
26922        request.next().await;
26923    }
26924}
26925
26926fn handle_resolve_completion_request(
26927    cx: &mut EditorLspTestContext,
26928    edits: Option<Vec<(&'static str, &'static str)>>,
26929) -> impl Future<Output = ()> {
26930    let edits = edits.map(|edits| {
26931        edits
26932            .iter()
26933            .map(|(marked_string, new_text)| {
26934                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26935                let replace_range = cx.to_lsp_range(
26936                    MultiBufferOffset(marked_ranges[0].start)
26937                        ..MultiBufferOffset(marked_ranges[0].end),
26938                );
26939                lsp::TextEdit::new(replace_range, new_text.to_string())
26940            })
26941            .collect::<Vec<_>>()
26942    });
26943
26944    let mut request =
26945        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26946            let edits = edits.clone();
26947            async move {
26948                Ok(lsp::CompletionItem {
26949                    additional_text_edits: edits,
26950                    ..Default::default()
26951                })
26952            }
26953        });
26954
26955    async move {
26956        request.next().await;
26957    }
26958}
26959
26960pub(crate) fn update_test_language_settings(
26961    cx: &mut TestAppContext,
26962    f: impl Fn(&mut AllLanguageSettingsContent),
26963) {
26964    cx.update(|cx| {
26965        SettingsStore::update_global(cx, |store, cx| {
26966            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26967        });
26968    });
26969}
26970
26971pub(crate) fn update_test_project_settings(
26972    cx: &mut TestAppContext,
26973    f: impl Fn(&mut ProjectSettingsContent),
26974) {
26975    cx.update(|cx| {
26976        SettingsStore::update_global(cx, |store, cx| {
26977            store.update_user_settings(cx, |settings| f(&mut settings.project));
26978        });
26979    });
26980}
26981
26982pub(crate) fn update_test_editor_settings(
26983    cx: &mut TestAppContext,
26984    f: impl Fn(&mut EditorSettingsContent),
26985) {
26986    cx.update(|cx| {
26987        SettingsStore::update_global(cx, |store, cx| {
26988            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26989        })
26990    })
26991}
26992
26993pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26994    cx.update(|cx| {
26995        assets::Assets.load_test_fonts(cx);
26996        let store = SettingsStore::test(cx);
26997        cx.set_global(store);
26998        theme::init(theme::LoadThemes::JustBase, cx);
26999        release_channel::init(semver::Version::new(0, 0, 0), cx);
27000        crate::init(cx);
27001    });
27002    zlog::init_test();
27003    update_test_language_settings(cx, f);
27004}
27005
27006#[track_caller]
27007fn assert_hunk_revert(
27008    not_reverted_text_with_selections: &str,
27009    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27010    expected_reverted_text_with_selections: &str,
27011    base_text: &str,
27012    cx: &mut EditorLspTestContext,
27013) {
27014    cx.set_state(not_reverted_text_with_selections);
27015    cx.set_head_text(base_text);
27016    cx.executor().run_until_parked();
27017
27018    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27019        let snapshot = editor.snapshot(window, cx);
27020        let reverted_hunk_statuses = snapshot
27021            .buffer_snapshot()
27022            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27023            .map(|hunk| hunk.status().kind)
27024            .collect::<Vec<_>>();
27025
27026        editor.git_restore(&Default::default(), window, cx);
27027        reverted_hunk_statuses
27028    });
27029    cx.executor().run_until_parked();
27030    cx.assert_editor_state(expected_reverted_text_with_selections);
27031    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27032}
27033
27034#[gpui::test(iterations = 10)]
27035async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27036    init_test(cx, |_| {});
27037
27038    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27039    let counter = diagnostic_requests.clone();
27040
27041    let fs = FakeFs::new(cx.executor());
27042    fs.insert_tree(
27043        path!("/a"),
27044        json!({
27045            "first.rs": "fn main() { let a = 5; }",
27046            "second.rs": "// Test file",
27047        }),
27048    )
27049    .await;
27050
27051    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27052    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27053    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27054
27055    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27056    language_registry.add(rust_lang());
27057    let mut fake_servers = language_registry.register_fake_lsp(
27058        "Rust",
27059        FakeLspAdapter {
27060            capabilities: lsp::ServerCapabilities {
27061                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27062                    lsp::DiagnosticOptions {
27063                        identifier: None,
27064                        inter_file_dependencies: true,
27065                        workspace_diagnostics: true,
27066                        work_done_progress_options: Default::default(),
27067                    },
27068                )),
27069                ..Default::default()
27070            },
27071            ..Default::default()
27072        },
27073    );
27074
27075    let editor = workspace
27076        .update(cx, |workspace, window, cx| {
27077            workspace.open_abs_path(
27078                PathBuf::from(path!("/a/first.rs")),
27079                OpenOptions::default(),
27080                window,
27081                cx,
27082            )
27083        })
27084        .unwrap()
27085        .await
27086        .unwrap()
27087        .downcast::<Editor>()
27088        .unwrap();
27089    let fake_server = fake_servers.next().await.unwrap();
27090    let server_id = fake_server.server.server_id();
27091    let mut first_request = fake_server
27092        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27093            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27094            let result_id = Some(new_result_id.to_string());
27095            assert_eq!(
27096                params.text_document.uri,
27097                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27098            );
27099            async move {
27100                Ok(lsp::DocumentDiagnosticReportResult::Report(
27101                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27102                        related_documents: None,
27103                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27104                            items: Vec::new(),
27105                            result_id,
27106                        },
27107                    }),
27108                ))
27109            }
27110        });
27111
27112    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27113        project.update(cx, |project, cx| {
27114            let buffer_id = editor
27115                .read(cx)
27116                .buffer()
27117                .read(cx)
27118                .as_singleton()
27119                .expect("created a singleton buffer")
27120                .read(cx)
27121                .remote_id();
27122            let buffer_result_id = project
27123                .lsp_store()
27124                .read(cx)
27125                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27126            assert_eq!(expected, buffer_result_id);
27127        });
27128    };
27129
27130    ensure_result_id(None, cx);
27131    cx.executor().advance_clock(Duration::from_millis(60));
27132    cx.executor().run_until_parked();
27133    assert_eq!(
27134        diagnostic_requests.load(atomic::Ordering::Acquire),
27135        1,
27136        "Opening file should trigger diagnostic request"
27137    );
27138    first_request
27139        .next()
27140        .await
27141        .expect("should have sent the first diagnostics pull request");
27142    ensure_result_id(Some(SharedString::new("1")), cx);
27143
27144    // Editing should trigger diagnostics
27145    editor.update_in(cx, |editor, window, cx| {
27146        editor.handle_input("2", window, cx)
27147    });
27148    cx.executor().advance_clock(Duration::from_millis(60));
27149    cx.executor().run_until_parked();
27150    assert_eq!(
27151        diagnostic_requests.load(atomic::Ordering::Acquire),
27152        2,
27153        "Editing should trigger diagnostic request"
27154    );
27155    ensure_result_id(Some(SharedString::new("2")), cx);
27156
27157    // Moving cursor should not trigger diagnostic request
27158    editor.update_in(cx, |editor, window, cx| {
27159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27160            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27161        });
27162    });
27163    cx.executor().advance_clock(Duration::from_millis(60));
27164    cx.executor().run_until_parked();
27165    assert_eq!(
27166        diagnostic_requests.load(atomic::Ordering::Acquire),
27167        2,
27168        "Cursor movement should not trigger diagnostic request"
27169    );
27170    ensure_result_id(Some(SharedString::new("2")), cx);
27171    // Multiple rapid edits should be debounced
27172    for _ in 0..5 {
27173        editor.update_in(cx, |editor, window, cx| {
27174            editor.handle_input("x", window, cx)
27175        });
27176    }
27177    cx.executor().advance_clock(Duration::from_millis(60));
27178    cx.executor().run_until_parked();
27179
27180    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27181    assert!(
27182        final_requests <= 4,
27183        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27184    );
27185    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27186}
27187
27188#[gpui::test]
27189async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27190    // Regression test for issue #11671
27191    // Previously, adding a cursor after moving multiple cursors would reset
27192    // the cursor count instead of adding to the existing cursors.
27193    init_test(cx, |_| {});
27194    let mut cx = EditorTestContext::new(cx).await;
27195
27196    // Create a simple buffer with cursor at start
27197    cx.set_state(indoc! {"
27198        ˇaaaa
27199        bbbb
27200        cccc
27201        dddd
27202        eeee
27203        ffff
27204        gggg
27205        hhhh"});
27206
27207    // Add 2 cursors below (so we have 3 total)
27208    cx.update_editor(|editor, window, cx| {
27209        editor.add_selection_below(&Default::default(), window, cx);
27210        editor.add_selection_below(&Default::default(), window, cx);
27211    });
27212
27213    // Verify we have 3 cursors
27214    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27215    assert_eq!(
27216        initial_count, 3,
27217        "Should have 3 cursors after adding 2 below"
27218    );
27219
27220    // Move down one line
27221    cx.update_editor(|editor, window, cx| {
27222        editor.move_down(&MoveDown, window, cx);
27223    });
27224
27225    // Add another cursor below
27226    cx.update_editor(|editor, window, cx| {
27227        editor.add_selection_below(&Default::default(), window, cx);
27228    });
27229
27230    // Should now have 4 cursors (3 original + 1 new)
27231    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27232    assert_eq!(
27233        final_count, 4,
27234        "Should have 4 cursors after moving and adding another"
27235    );
27236}
27237
27238#[gpui::test]
27239async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27240    init_test(cx, |_| {});
27241
27242    let mut cx = EditorTestContext::new(cx).await;
27243
27244    cx.set_state(indoc!(
27245        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27246           Second line here"#
27247    ));
27248
27249    cx.update_editor(|editor, window, cx| {
27250        // Enable soft wrapping with a narrow width to force soft wrapping and
27251        // confirm that more than 2 rows are being displayed.
27252        editor.set_wrap_width(Some(100.0.into()), cx);
27253        assert!(editor.display_text(cx).lines().count() > 2);
27254
27255        editor.add_selection_below(
27256            &AddSelectionBelow {
27257                skip_soft_wrap: true,
27258            },
27259            window,
27260            cx,
27261        );
27262
27263        assert_eq!(
27264            display_ranges(editor, cx),
27265            &[
27266                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27267                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27268            ]
27269        );
27270
27271        editor.add_selection_above(
27272            &AddSelectionAbove {
27273                skip_soft_wrap: true,
27274            },
27275            window,
27276            cx,
27277        );
27278
27279        assert_eq!(
27280            display_ranges(editor, cx),
27281            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27282        );
27283
27284        editor.add_selection_below(
27285            &AddSelectionBelow {
27286                skip_soft_wrap: false,
27287            },
27288            window,
27289            cx,
27290        );
27291
27292        assert_eq!(
27293            display_ranges(editor, cx),
27294            &[
27295                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27296                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27297            ]
27298        );
27299
27300        editor.add_selection_above(
27301            &AddSelectionAbove {
27302                skip_soft_wrap: false,
27303            },
27304            window,
27305            cx,
27306        );
27307
27308        assert_eq!(
27309            display_ranges(editor, cx),
27310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27311        );
27312    });
27313}
27314
27315#[gpui::test]
27316async fn test_insert_snippet(cx: &mut TestAppContext) {
27317    init_test(cx, |_| {});
27318    let mut cx = EditorTestContext::new(cx).await;
27319
27320    cx.update_editor(|editor, _, cx| {
27321        editor.project().unwrap().update(cx, |project, cx| {
27322            project.snippets().update(cx, |snippets, _cx| {
27323                let snippet = project::snippet_provider::Snippet {
27324                    prefix: vec![], // no prefix needed!
27325                    body: "an Unspecified".to_string(),
27326                    description: Some("shhhh it's a secret".to_string()),
27327                    name: "super secret snippet".to_string(),
27328                };
27329                snippets.add_snippet_for_test(
27330                    None,
27331                    PathBuf::from("test_snippets.json"),
27332                    vec![Arc::new(snippet)],
27333                );
27334
27335                let snippet = project::snippet_provider::Snippet {
27336                    prefix: vec![], // no prefix needed!
27337                    body: " Location".to_string(),
27338                    description: Some("the word 'location'".to_string()),
27339                    name: "location word".to_string(),
27340                };
27341                snippets.add_snippet_for_test(
27342                    Some("Markdown".to_string()),
27343                    PathBuf::from("test_snippets.json"),
27344                    vec![Arc::new(snippet)],
27345                );
27346            });
27347        })
27348    });
27349
27350    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27351
27352    cx.update_editor(|editor, window, cx| {
27353        editor.insert_snippet_at_selections(
27354            &InsertSnippet {
27355                language: None,
27356                name: Some("super secret snippet".to_string()),
27357                snippet: None,
27358            },
27359            window,
27360            cx,
27361        );
27362
27363        // Language is specified in the action,
27364        // so the buffer language does not need to match
27365        editor.insert_snippet_at_selections(
27366            &InsertSnippet {
27367                language: Some("Markdown".to_string()),
27368                name: Some("location word".to_string()),
27369                snippet: None,
27370            },
27371            window,
27372            cx,
27373        );
27374
27375        editor.insert_snippet_at_selections(
27376            &InsertSnippet {
27377                language: None,
27378                name: None,
27379                snippet: Some("$0 after".to_string()),
27380            },
27381            window,
27382            cx,
27383        );
27384    });
27385
27386    cx.assert_editor_state(
27387        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27388    );
27389}
27390
27391#[gpui::test(iterations = 10)]
27392async fn test_document_colors(cx: &mut TestAppContext) {
27393    let expected_color = Rgba {
27394        r: 0.33,
27395        g: 0.33,
27396        b: 0.33,
27397        a: 0.33,
27398    };
27399
27400    init_test(cx, |_| {});
27401
27402    let fs = FakeFs::new(cx.executor());
27403    fs.insert_tree(
27404        path!("/a"),
27405        json!({
27406            "first.rs": "fn main() { let a = 5; }",
27407        }),
27408    )
27409    .await;
27410
27411    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27412    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27413    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27414
27415    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27416    language_registry.add(rust_lang());
27417    let mut fake_servers = language_registry.register_fake_lsp(
27418        "Rust",
27419        FakeLspAdapter {
27420            capabilities: lsp::ServerCapabilities {
27421                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27422                ..lsp::ServerCapabilities::default()
27423            },
27424            name: "rust-analyzer",
27425            ..FakeLspAdapter::default()
27426        },
27427    );
27428    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27429        "Rust",
27430        FakeLspAdapter {
27431            capabilities: lsp::ServerCapabilities {
27432                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27433                ..lsp::ServerCapabilities::default()
27434            },
27435            name: "not-rust-analyzer",
27436            ..FakeLspAdapter::default()
27437        },
27438    );
27439
27440    let editor = workspace
27441        .update(cx, |workspace, window, cx| {
27442            workspace.open_abs_path(
27443                PathBuf::from(path!("/a/first.rs")),
27444                OpenOptions::default(),
27445                window,
27446                cx,
27447            )
27448        })
27449        .unwrap()
27450        .await
27451        .unwrap()
27452        .downcast::<Editor>()
27453        .unwrap();
27454    let fake_language_server = fake_servers.next().await.unwrap();
27455    let fake_language_server_without_capabilities =
27456        fake_servers_without_capabilities.next().await.unwrap();
27457    let requests_made = Arc::new(AtomicUsize::new(0));
27458    let closure_requests_made = Arc::clone(&requests_made);
27459    let mut color_request_handle = fake_language_server
27460        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27461            let requests_made = Arc::clone(&closure_requests_made);
27462            async move {
27463                assert_eq!(
27464                    params.text_document.uri,
27465                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27466                );
27467                requests_made.fetch_add(1, atomic::Ordering::Release);
27468                Ok(vec![
27469                    lsp::ColorInformation {
27470                        range: lsp::Range {
27471                            start: lsp::Position {
27472                                line: 0,
27473                                character: 0,
27474                            },
27475                            end: lsp::Position {
27476                                line: 0,
27477                                character: 1,
27478                            },
27479                        },
27480                        color: lsp::Color {
27481                            red: 0.33,
27482                            green: 0.33,
27483                            blue: 0.33,
27484                            alpha: 0.33,
27485                        },
27486                    },
27487                    lsp::ColorInformation {
27488                        range: lsp::Range {
27489                            start: lsp::Position {
27490                                line: 0,
27491                                character: 0,
27492                            },
27493                            end: lsp::Position {
27494                                line: 0,
27495                                character: 1,
27496                            },
27497                        },
27498                        color: lsp::Color {
27499                            red: 0.33,
27500                            green: 0.33,
27501                            blue: 0.33,
27502                            alpha: 0.33,
27503                        },
27504                    },
27505                ])
27506            }
27507        });
27508
27509    let _handle = fake_language_server_without_capabilities
27510        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27511            panic!("Should not be called");
27512        });
27513    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27514    color_request_handle.next().await.unwrap();
27515    cx.run_until_parked();
27516    assert_eq!(
27517        1,
27518        requests_made.load(atomic::Ordering::Acquire),
27519        "Should query for colors once per editor open"
27520    );
27521    editor.update_in(cx, |editor, _, cx| {
27522        assert_eq!(
27523            vec![expected_color],
27524            extract_color_inlays(editor, cx),
27525            "Should have an initial inlay"
27526        );
27527    });
27528
27529    // opening another file in a split should not influence the LSP query counter
27530    workspace
27531        .update(cx, |workspace, window, cx| {
27532            assert_eq!(
27533                workspace.panes().len(),
27534                1,
27535                "Should have one pane with one editor"
27536            );
27537            workspace.move_item_to_pane_in_direction(
27538                &MoveItemToPaneInDirection {
27539                    direction: SplitDirection::Right,
27540                    focus: false,
27541                    clone: true,
27542                },
27543                window,
27544                cx,
27545            );
27546        })
27547        .unwrap();
27548    cx.run_until_parked();
27549    workspace
27550        .update(cx, |workspace, _, cx| {
27551            let panes = workspace.panes();
27552            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27553            for pane in panes {
27554                let editor = pane
27555                    .read(cx)
27556                    .active_item()
27557                    .and_then(|item| item.downcast::<Editor>())
27558                    .expect("Should have opened an editor in each split");
27559                let editor_file = editor
27560                    .read(cx)
27561                    .buffer()
27562                    .read(cx)
27563                    .as_singleton()
27564                    .expect("test deals with singleton buffers")
27565                    .read(cx)
27566                    .file()
27567                    .expect("test buffese should have a file")
27568                    .path();
27569                assert_eq!(
27570                    editor_file.as_ref(),
27571                    rel_path("first.rs"),
27572                    "Both editors should be opened for the same file"
27573                )
27574            }
27575        })
27576        .unwrap();
27577
27578    cx.executor().advance_clock(Duration::from_millis(500));
27579    let save = editor.update_in(cx, |editor, window, cx| {
27580        editor.move_to_end(&MoveToEnd, window, cx);
27581        editor.handle_input("dirty", window, cx);
27582        editor.save(
27583            SaveOptions {
27584                format: true,
27585                autosave: true,
27586            },
27587            project.clone(),
27588            window,
27589            cx,
27590        )
27591    });
27592    save.await.unwrap();
27593
27594    color_request_handle.next().await.unwrap();
27595    cx.run_until_parked();
27596    assert_eq!(
27597        2,
27598        requests_made.load(atomic::Ordering::Acquire),
27599        "Should query for colors once per save (deduplicated) and once per formatting after save"
27600    );
27601
27602    drop(editor);
27603    let close = workspace
27604        .update(cx, |workspace, window, cx| {
27605            workspace.active_pane().update(cx, |pane, cx| {
27606                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27607            })
27608        })
27609        .unwrap();
27610    close.await.unwrap();
27611    let close = workspace
27612        .update(cx, |workspace, window, cx| {
27613            workspace.active_pane().update(cx, |pane, cx| {
27614                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27615            })
27616        })
27617        .unwrap();
27618    close.await.unwrap();
27619    assert_eq!(
27620        2,
27621        requests_made.load(atomic::Ordering::Acquire),
27622        "After saving and closing all editors, no extra requests should be made"
27623    );
27624    workspace
27625        .update(cx, |workspace, _, cx| {
27626            assert!(
27627                workspace.active_item(cx).is_none(),
27628                "Should close all editors"
27629            )
27630        })
27631        .unwrap();
27632
27633    workspace
27634        .update(cx, |workspace, window, cx| {
27635            workspace.active_pane().update(cx, |pane, cx| {
27636                pane.navigate_backward(&workspace::GoBack, window, cx);
27637            })
27638        })
27639        .unwrap();
27640    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27641    cx.run_until_parked();
27642    let editor = workspace
27643        .update(cx, |workspace, _, cx| {
27644            workspace
27645                .active_item(cx)
27646                .expect("Should have reopened the editor again after navigating back")
27647                .downcast::<Editor>()
27648                .expect("Should be an editor")
27649        })
27650        .unwrap();
27651
27652    assert_eq!(
27653        2,
27654        requests_made.load(atomic::Ordering::Acquire),
27655        "Cache should be reused on buffer close and reopen"
27656    );
27657    editor.update(cx, |editor, cx| {
27658        assert_eq!(
27659            vec![expected_color],
27660            extract_color_inlays(editor, cx),
27661            "Should have an initial inlay"
27662        );
27663    });
27664
27665    drop(color_request_handle);
27666    let closure_requests_made = Arc::clone(&requests_made);
27667    let mut empty_color_request_handle = fake_language_server
27668        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27669            let requests_made = Arc::clone(&closure_requests_made);
27670            async move {
27671                assert_eq!(
27672                    params.text_document.uri,
27673                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27674                );
27675                requests_made.fetch_add(1, atomic::Ordering::Release);
27676                Ok(Vec::new())
27677            }
27678        });
27679    let save = editor.update_in(cx, |editor, window, cx| {
27680        editor.move_to_end(&MoveToEnd, window, cx);
27681        editor.handle_input("dirty_again", window, cx);
27682        editor.save(
27683            SaveOptions {
27684                format: false,
27685                autosave: true,
27686            },
27687            project.clone(),
27688            window,
27689            cx,
27690        )
27691    });
27692    save.await.unwrap();
27693
27694    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27695    empty_color_request_handle.next().await.unwrap();
27696    cx.run_until_parked();
27697    assert_eq!(
27698        3,
27699        requests_made.load(atomic::Ordering::Acquire),
27700        "Should query for colors once per save only, as formatting was not requested"
27701    );
27702    editor.update(cx, |editor, cx| {
27703        assert_eq!(
27704            Vec::<Rgba>::new(),
27705            extract_color_inlays(editor, cx),
27706            "Should clear all colors when the server returns an empty response"
27707        );
27708    });
27709}
27710
27711#[gpui::test]
27712async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27713    init_test(cx, |_| {});
27714    let (editor, cx) = cx.add_window_view(Editor::single_line);
27715    editor.update_in(cx, |editor, window, cx| {
27716        editor.set_text("oops\n\nwow\n", window, cx)
27717    });
27718    cx.run_until_parked();
27719    editor.update(cx, |editor, cx| {
27720        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27721    });
27722    editor.update(cx, |editor, cx| {
27723        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27724    });
27725    cx.run_until_parked();
27726    editor.update(cx, |editor, cx| {
27727        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27728    });
27729}
27730
27731#[gpui::test]
27732async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27733    init_test(cx, |_| {});
27734
27735    cx.update(|cx| {
27736        register_project_item::<Editor>(cx);
27737    });
27738
27739    let fs = FakeFs::new(cx.executor());
27740    fs.insert_tree("/root1", json!({})).await;
27741    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27742        .await;
27743
27744    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27745    let (workspace, cx) =
27746        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27747
27748    let worktree_id = project.update(cx, |project, cx| {
27749        project.worktrees(cx).next().unwrap().read(cx).id()
27750    });
27751
27752    let handle = workspace
27753        .update_in(cx, |workspace, window, cx| {
27754            let project_path = (worktree_id, rel_path("one.pdf"));
27755            workspace.open_path(project_path, None, true, window, cx)
27756        })
27757        .await
27758        .unwrap();
27759    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
27760    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
27761    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
27762    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
27763}
27764
27765#[gpui::test]
27766async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27767    init_test(cx, |_| {});
27768
27769    let language = Arc::new(Language::new(
27770        LanguageConfig::default(),
27771        Some(tree_sitter_rust::LANGUAGE.into()),
27772    ));
27773
27774    // Test hierarchical sibling navigation
27775    let text = r#"
27776        fn outer() {
27777            if condition {
27778                let a = 1;
27779            }
27780            let b = 2;
27781        }
27782
27783        fn another() {
27784            let c = 3;
27785        }
27786    "#;
27787
27788    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27789    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27790    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27791
27792    // Wait for parsing to complete
27793    editor
27794        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27795        .await;
27796
27797    editor.update_in(cx, |editor, window, cx| {
27798        // Start by selecting "let a = 1;" inside the if block
27799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27800            s.select_display_ranges([
27801                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27802            ]);
27803        });
27804
27805        let initial_selection = editor
27806            .selections
27807            .display_ranges(&editor.display_snapshot(cx));
27808        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27809
27810        // Test select next sibling - should move up levels to find the next sibling
27811        // Since "let a = 1;" has no siblings in the if block, it should move up
27812        // to find "let b = 2;" which is a sibling of the if block
27813        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27814        let next_selection = editor
27815            .selections
27816            .display_ranges(&editor.display_snapshot(cx));
27817
27818        // Should have a selection and it should be different from the initial
27819        assert_eq!(
27820            next_selection.len(),
27821            1,
27822            "Should have one selection after next"
27823        );
27824        assert_ne!(
27825            next_selection[0], initial_selection[0],
27826            "Next sibling selection should be different"
27827        );
27828
27829        // Test hierarchical navigation by going to the end of the current function
27830        // and trying to navigate to the next function
27831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27832            s.select_display_ranges([
27833                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27834            ]);
27835        });
27836
27837        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27838        let function_next_selection = editor
27839            .selections
27840            .display_ranges(&editor.display_snapshot(cx));
27841
27842        // Should move to the next function
27843        assert_eq!(
27844            function_next_selection.len(),
27845            1,
27846            "Should have one selection after function next"
27847        );
27848
27849        // Test select previous sibling navigation
27850        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27851        let prev_selection = editor
27852            .selections
27853            .display_ranges(&editor.display_snapshot(cx));
27854
27855        // Should have a selection and it should be different
27856        assert_eq!(
27857            prev_selection.len(),
27858            1,
27859            "Should have one selection after prev"
27860        );
27861        assert_ne!(
27862            prev_selection[0], function_next_selection[0],
27863            "Previous sibling selection should be different from next"
27864        );
27865    });
27866}
27867
27868#[gpui::test]
27869async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27870    init_test(cx, |_| {});
27871
27872    let mut cx = EditorTestContext::new(cx).await;
27873    cx.set_state(
27874        "let ˇvariable = 42;
27875let another = variable + 1;
27876let result = variable * 2;",
27877    );
27878
27879    // Set up document highlights manually (simulating LSP response)
27880    cx.update_editor(|editor, _window, cx| {
27881        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27882
27883        // Create highlights for "variable" occurrences
27884        let highlight_ranges = [
27885            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27886            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27887            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27888        ];
27889
27890        let anchor_ranges: Vec<_> = highlight_ranges
27891            .iter()
27892            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27893            .collect();
27894
27895        editor.highlight_background::<DocumentHighlightRead>(
27896            &anchor_ranges,
27897            |_, theme| theme.colors().editor_document_highlight_read_background,
27898            cx,
27899        );
27900    });
27901
27902    // Go to next highlight - should move to second "variable"
27903    cx.update_editor(|editor, window, cx| {
27904        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27905    });
27906    cx.assert_editor_state(
27907        "let variable = 42;
27908let another = ˇvariable + 1;
27909let result = variable * 2;",
27910    );
27911
27912    // Go to next highlight - should move to third "variable"
27913    cx.update_editor(|editor, window, cx| {
27914        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27915    });
27916    cx.assert_editor_state(
27917        "let variable = 42;
27918let another = variable + 1;
27919let result = ˇvariable * 2;",
27920    );
27921
27922    // Go to next highlight - should stay at third "variable" (no wrap-around)
27923    cx.update_editor(|editor, window, cx| {
27924        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27925    });
27926    cx.assert_editor_state(
27927        "let variable = 42;
27928let another = variable + 1;
27929let result = ˇvariable * 2;",
27930    );
27931
27932    // Now test going backwards from third position
27933    cx.update_editor(|editor, window, cx| {
27934        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27935    });
27936    cx.assert_editor_state(
27937        "let variable = 42;
27938let another = ˇvariable + 1;
27939let result = variable * 2;",
27940    );
27941
27942    // Go to previous highlight - should move to first "variable"
27943    cx.update_editor(|editor, window, cx| {
27944        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27945    });
27946    cx.assert_editor_state(
27947        "let ˇvariable = 42;
27948let another = variable + 1;
27949let result = variable * 2;",
27950    );
27951
27952    // Go to previous highlight - should stay on first "variable"
27953    cx.update_editor(|editor, window, cx| {
27954        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27955    });
27956    cx.assert_editor_state(
27957        "let ˇvariable = 42;
27958let another = variable + 1;
27959let result = variable * 2;",
27960    );
27961}
27962
27963#[gpui::test]
27964async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27965    cx: &mut gpui::TestAppContext,
27966) {
27967    init_test(cx, |_| {});
27968
27969    let url = "https://zed.dev";
27970
27971    let markdown_language = Arc::new(Language::new(
27972        LanguageConfig {
27973            name: "Markdown".into(),
27974            ..LanguageConfig::default()
27975        },
27976        None,
27977    ));
27978
27979    let mut cx = EditorTestContext::new(cx).await;
27980    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27981    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27982
27983    cx.update_editor(|editor, window, cx| {
27984        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27985        editor.paste(&Paste, window, cx);
27986    });
27987
27988    cx.assert_editor_state(&format!(
27989        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27990    ));
27991}
27992
27993#[gpui::test]
27994async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27995    init_test(cx, |_| {});
27996
27997    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27998    let mut cx = EditorTestContext::new(cx).await;
27999
28000    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28001
28002    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28003    cx.set_state(&indoc! {"
28004        - [ ] Item 1
28005            - [ ] Item 1.a
28006        - [ˇ] Item 2
28007            - [ˇ] Item 2.a
28008            - [ˇ] Item 2.b
28009        "
28010    });
28011    cx.update_editor(|editor, window, cx| {
28012        editor.handle_input("x", window, cx);
28013    });
28014    cx.run_until_parked();
28015    cx.assert_editor_state(indoc! {"
28016        - [ ] Item 1
28017            - [ ] Item 1.a
28018        - [xˇ] Item 2
28019            - [xˇ] Item 2.a
28020            - [xˇ] Item 2.b
28021        "
28022    });
28023
28024    // Case 2: Test adding new line after nested list preserves indent of previous line
28025    cx.set_state(&indoc! {"
28026        - [ ] Item 1
28027            - [ ] Item 1.a
28028        - [x] Item 2
28029            - [x] Item 2.a
28030            - [x] Item 2.bˇ"
28031    });
28032    cx.update_editor(|editor, window, cx| {
28033        editor.newline(&Newline, window, cx);
28034    });
28035    cx.assert_editor_state(indoc! {"
28036        - [ ] Item 1
28037            - [ ] Item 1.a
28038        - [x] Item 2
28039            - [x] Item 2.a
28040            - [x] Item 2.b
28041            ˇ"
28042    });
28043
28044    // Case 3: Test adding a new nested list item preserves indent
28045    cx.set_state(&indoc! {"
28046        - [ ] Item 1
28047            - [ ] Item 1.a
28048        - [x] Item 2
28049            - [x] Item 2.a
28050            - [x] Item 2.b
28051            ˇ"
28052    });
28053    cx.update_editor(|editor, window, cx| {
28054        editor.handle_input("-", window, cx);
28055    });
28056    cx.run_until_parked();
28057    cx.assert_editor_state(indoc! {"
28058        - [ ] Item 1
28059            - [ ] Item 1.a
28060        - [x] Item 2
28061            - [x] Item 2.a
28062            - [x] Item 2.b
28063"
28064    });
28065    cx.update_editor(|editor, window, cx| {
28066        editor.handle_input(" [x] Item 2.c", window, cx);
28067    });
28068    cx.run_until_parked();
28069    cx.assert_editor_state(indoc! {"
28070        - [ ] Item 1
28071            - [ ] Item 1.a
28072        - [x] Item 2
28073            - [x] Item 2.a
28074            - [x] Item 2.b
28075            - [x] Item 2.cˇ"
28076    });
28077
28078    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
28079    cx.set_state(indoc! {"
28080        1. Item 1
28081            1. Item 1.a
28082        2. Item 2
28083            1. Item 2.a
28084            2. Item 2.bˇ"
28085    });
28086    cx.update_editor(|editor, window, cx| {
28087        editor.newline(&Newline, window, cx);
28088    });
28089    cx.assert_editor_state(indoc! {"
28090        1. Item 1
28091            1. Item 1.a
28092        2. Item 2
28093            1. Item 2.a
28094            2. Item 2.b
28095            ˇ"
28096    });
28097
28098    // Case 5: Adding new ordered list item preserves indent
28099    cx.set_state(indoc! {"
28100        1. Item 1
28101            1. Item 1.a
28102        2. Item 2
28103            1. Item 2.a
28104            2. Item 2.b
28105            ˇ"
28106    });
28107    cx.update_editor(|editor, window, cx| {
28108        editor.handle_input("3", window, cx);
28109    });
28110    cx.run_until_parked();
28111    cx.assert_editor_state(indoc! {"
28112        1. Item 1
28113            1. Item 1.a
28114        2. Item 2
28115            1. Item 2.a
28116            2. Item 2.b
28117"
28118    });
28119    cx.update_editor(|editor, window, cx| {
28120        editor.handle_input(".", window, cx);
28121    });
28122    cx.run_until_parked();
28123    cx.assert_editor_state(indoc! {"
28124        1. Item 1
28125            1. Item 1.a
28126        2. Item 2
28127            1. Item 2.a
28128            2. Item 2.b
28129            3.ˇ"
28130    });
28131    cx.update_editor(|editor, window, cx| {
28132        editor.handle_input(" Item 2.c", window, cx);
28133    });
28134    cx.run_until_parked();
28135    cx.assert_editor_state(indoc! {"
28136        1. Item 1
28137            1. Item 1.a
28138        2. Item 2
28139            1. Item 2.a
28140            2. Item 2.b
28141            3. Item 2.cˇ"
28142    });
28143
28144    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28145    cx.set_state(indoc! {"
28146        - Item 1
28147            - Item 1.a
28148            - Item 1.a
28149        ˇ"});
28150    cx.update_editor(|editor, window, cx| {
28151        editor.handle_input("-", window, cx);
28152    });
28153    cx.run_until_parked();
28154    cx.assert_editor_state(indoc! {"
28155        - Item 1
28156            - Item 1.a
28157            - Item 1.a
28158"});
28159
28160    // Case 7: Test blockquote newline preserves something
28161    cx.set_state(indoc! {"
28162        > Item 1ˇ"
28163    });
28164    cx.update_editor(|editor, window, cx| {
28165        editor.newline(&Newline, window, cx);
28166    });
28167    cx.assert_editor_state(indoc! {"
28168        > Item 1
28169        ˇ"
28170    });
28171}
28172
28173#[gpui::test]
28174async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28175    cx: &mut gpui::TestAppContext,
28176) {
28177    init_test(cx, |_| {});
28178
28179    let url = "https://zed.dev";
28180
28181    let markdown_language = Arc::new(Language::new(
28182        LanguageConfig {
28183            name: "Markdown".into(),
28184            ..LanguageConfig::default()
28185        },
28186        None,
28187    ));
28188
28189    let mut cx = EditorTestContext::new(cx).await;
28190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28191    cx.set_state(&format!(
28192        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28193    ));
28194
28195    cx.update_editor(|editor, window, cx| {
28196        editor.copy(&Copy, window, cx);
28197    });
28198
28199    cx.set_state(&format!(
28200        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28201    ));
28202
28203    cx.update_editor(|editor, window, cx| {
28204        editor.paste(&Paste, window, cx);
28205    });
28206
28207    cx.assert_editor_state(&format!(
28208        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28209    ));
28210}
28211
28212#[gpui::test]
28213async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28214    cx: &mut gpui::TestAppContext,
28215) {
28216    init_test(cx, |_| {});
28217
28218    let url = "https://zed.dev";
28219
28220    let markdown_language = Arc::new(Language::new(
28221        LanguageConfig {
28222            name: "Markdown".into(),
28223            ..LanguageConfig::default()
28224        },
28225        None,
28226    ));
28227
28228    let mut cx = EditorTestContext::new(cx).await;
28229    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28230    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28231
28232    cx.update_editor(|editor, window, cx| {
28233        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28234        editor.paste(&Paste, window, cx);
28235    });
28236
28237    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28238}
28239
28240#[gpui::test]
28241async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28242    cx: &mut gpui::TestAppContext,
28243) {
28244    init_test(cx, |_| {});
28245
28246    let text = "Awesome";
28247
28248    let markdown_language = Arc::new(Language::new(
28249        LanguageConfig {
28250            name: "Markdown".into(),
28251            ..LanguageConfig::default()
28252        },
28253        None,
28254    ));
28255
28256    let mut cx = EditorTestContext::new(cx).await;
28257    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28258    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28259
28260    cx.update_editor(|editor, window, cx| {
28261        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28262        editor.paste(&Paste, window, cx);
28263    });
28264
28265    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28266}
28267
28268#[gpui::test]
28269async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28270    cx: &mut gpui::TestAppContext,
28271) {
28272    init_test(cx, |_| {});
28273
28274    let url = "https://zed.dev";
28275
28276    let markdown_language = Arc::new(Language::new(
28277        LanguageConfig {
28278            name: "Rust".into(),
28279            ..LanguageConfig::default()
28280        },
28281        None,
28282    ));
28283
28284    let mut cx = EditorTestContext::new(cx).await;
28285    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28286    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28287
28288    cx.update_editor(|editor, window, cx| {
28289        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28290        editor.paste(&Paste, window, cx);
28291    });
28292
28293    cx.assert_editor_state(&format!(
28294        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28295    ));
28296}
28297
28298#[gpui::test]
28299async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28300    cx: &mut TestAppContext,
28301) {
28302    init_test(cx, |_| {});
28303
28304    let url = "https://zed.dev";
28305
28306    let markdown_language = Arc::new(Language::new(
28307        LanguageConfig {
28308            name: "Markdown".into(),
28309            ..LanguageConfig::default()
28310        },
28311        None,
28312    ));
28313
28314    let (editor, cx) = cx.add_window_view(|window, cx| {
28315        let multi_buffer = MultiBuffer::build_multi(
28316            [
28317                ("this will embed -> link", vec![Point::row_range(0..1)]),
28318                ("this will replace -> link", vec![Point::row_range(0..1)]),
28319            ],
28320            cx,
28321        );
28322        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28323        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28324            s.select_ranges(vec![
28325                Point::new(0, 19)..Point::new(0, 23),
28326                Point::new(1, 21)..Point::new(1, 25),
28327            ])
28328        });
28329        let first_buffer_id = multi_buffer
28330            .read(cx)
28331            .excerpt_buffer_ids()
28332            .into_iter()
28333            .next()
28334            .unwrap();
28335        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28336        first_buffer.update(cx, |buffer, cx| {
28337            buffer.set_language(Some(markdown_language.clone()), cx);
28338        });
28339
28340        editor
28341    });
28342    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28343
28344    cx.update_editor(|editor, window, cx| {
28345        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28346        editor.paste(&Paste, window, cx);
28347    });
28348
28349    cx.assert_editor_state(&format!(
28350        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28351    ));
28352}
28353
28354#[gpui::test]
28355async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28356    init_test(cx, |_| {});
28357
28358    let fs = FakeFs::new(cx.executor());
28359    fs.insert_tree(
28360        path!("/project"),
28361        json!({
28362            "first.rs": "# First Document\nSome content here.",
28363            "second.rs": "Plain text content for second file.",
28364        }),
28365    )
28366    .await;
28367
28368    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28369    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28370    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28371
28372    let language = rust_lang();
28373    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28374    language_registry.add(language.clone());
28375    let mut fake_servers = language_registry.register_fake_lsp(
28376        "Rust",
28377        FakeLspAdapter {
28378            ..FakeLspAdapter::default()
28379        },
28380    );
28381
28382    let buffer1 = project
28383        .update(cx, |project, cx| {
28384            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28385        })
28386        .await
28387        .unwrap();
28388    let buffer2 = project
28389        .update(cx, |project, cx| {
28390            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28391        })
28392        .await
28393        .unwrap();
28394
28395    let multi_buffer = cx.new(|cx| {
28396        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28397        multi_buffer.set_excerpts_for_path(
28398            PathKey::for_buffer(&buffer1, cx),
28399            buffer1.clone(),
28400            [Point::zero()..buffer1.read(cx).max_point()],
28401            3,
28402            cx,
28403        );
28404        multi_buffer.set_excerpts_for_path(
28405            PathKey::for_buffer(&buffer2, cx),
28406            buffer2.clone(),
28407            [Point::zero()..buffer1.read(cx).max_point()],
28408            3,
28409            cx,
28410        );
28411        multi_buffer
28412    });
28413
28414    let (editor, cx) = cx.add_window_view(|window, cx| {
28415        Editor::new(
28416            EditorMode::full(),
28417            multi_buffer,
28418            Some(project.clone()),
28419            window,
28420            cx,
28421        )
28422    });
28423
28424    let fake_language_server = fake_servers.next().await.unwrap();
28425
28426    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28427
28428    let save = editor.update_in(cx, |editor, window, cx| {
28429        assert!(editor.is_dirty(cx));
28430
28431        editor.save(
28432            SaveOptions {
28433                format: true,
28434                autosave: true,
28435            },
28436            project,
28437            window,
28438            cx,
28439        )
28440    });
28441    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28442    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28443    let mut done_edit_rx = Some(done_edit_rx);
28444    let mut start_edit_tx = Some(start_edit_tx);
28445
28446    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28447        start_edit_tx.take().unwrap().send(()).unwrap();
28448        let done_edit_rx = done_edit_rx.take().unwrap();
28449        async move {
28450            done_edit_rx.await.unwrap();
28451            Ok(None)
28452        }
28453    });
28454
28455    start_edit_rx.await.unwrap();
28456    buffer2
28457        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28458        .unwrap();
28459
28460    done_edit_tx.send(()).unwrap();
28461
28462    save.await.unwrap();
28463    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28464}
28465
28466#[track_caller]
28467fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28468    editor
28469        .all_inlays(cx)
28470        .into_iter()
28471        .filter_map(|inlay| inlay.get_color())
28472        .map(Rgba::from)
28473        .collect()
28474}
28475
28476#[gpui::test]
28477fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28478    init_test(cx, |_| {});
28479
28480    let editor = cx.add_window(|window, cx| {
28481        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28482        build_editor(buffer, window, cx)
28483    });
28484
28485    editor
28486        .update(cx, |editor, window, cx| {
28487            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28488                s.select_display_ranges([
28489                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28490                ])
28491            });
28492
28493            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28494
28495            assert_eq!(
28496                editor.display_text(cx),
28497                "line1\nline2\nline2",
28498                "Duplicating last line upward should create duplicate above, not on same line"
28499            );
28500
28501            assert_eq!(
28502                editor
28503                    .selections
28504                    .display_ranges(&editor.display_snapshot(cx)),
28505                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28506                "Selection should move to the duplicated line"
28507            );
28508        })
28509        .unwrap();
28510}
28511
28512#[gpui::test]
28513async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28514    init_test(cx, |_| {});
28515
28516    let mut cx = EditorTestContext::new(cx).await;
28517
28518    cx.set_state("line1\nline2ˇ");
28519
28520    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28521
28522    let clipboard_text = cx
28523        .read_from_clipboard()
28524        .and_then(|item| item.text().as_deref().map(str::to_string));
28525
28526    assert_eq!(
28527        clipboard_text,
28528        Some("line2\n".to_string()),
28529        "Copying a line without trailing newline should include a newline"
28530    );
28531
28532    cx.set_state("line1\nˇ");
28533
28534    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28535
28536    cx.assert_editor_state("line1\nline2\nˇ");
28537}
28538
28539#[gpui::test]
28540async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28541    init_test(cx, |_| {});
28542
28543    let mut cx = EditorTestContext::new(cx).await;
28544
28545    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28546
28547    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28548
28549    let clipboard_text = cx
28550        .read_from_clipboard()
28551        .and_then(|item| item.text().as_deref().map(str::to_string));
28552
28553    assert_eq!(
28554        clipboard_text,
28555        Some("line1\nline2\nline3\n".to_string()),
28556        "Copying multiple lines should include a single newline between lines"
28557    );
28558
28559    cx.set_state("lineA\nˇ");
28560
28561    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28562
28563    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28564}
28565
28566#[gpui::test]
28567async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28568    init_test(cx, |_| {});
28569
28570    let mut cx = EditorTestContext::new(cx).await;
28571
28572    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28573
28574    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28575
28576    let clipboard_text = cx
28577        .read_from_clipboard()
28578        .and_then(|item| item.text().as_deref().map(str::to_string));
28579
28580    assert_eq!(
28581        clipboard_text,
28582        Some("line1\nline2\nline3\n".to_string()),
28583        "Copying multiple lines should include a single newline between lines"
28584    );
28585
28586    cx.set_state("lineA\nˇ");
28587
28588    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28589
28590    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28591}
28592
28593#[gpui::test]
28594async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28595    init_test(cx, |_| {});
28596
28597    let mut cx = EditorTestContext::new(cx).await;
28598
28599    cx.set_state("line1\nline2ˇ");
28600    cx.update_editor(|e, window, cx| {
28601        e.set_mode(EditorMode::SingleLine);
28602        assert!(e.key_context(window, cx).contains("end_of_input"));
28603    });
28604    cx.set_state("ˇline1\nline2");
28605    cx.update_editor(|e, window, cx| {
28606        assert!(!e.key_context(window, cx).contains("end_of_input"));
28607    });
28608    cx.set_state("line1ˇ\nline2");
28609    cx.update_editor(|e, window, cx| {
28610        assert!(!e.key_context(window, cx).contains("end_of_input"));
28611    });
28612}
28613
28614#[gpui::test]
28615async fn test_sticky_scroll(cx: &mut TestAppContext) {
28616    init_test(cx, |_| {});
28617    let mut cx = EditorTestContext::new(cx).await;
28618
28619    let buffer = indoc! {"
28620            ˇfn foo() {
28621                let abc = 123;
28622            }
28623            struct Bar;
28624            impl Bar {
28625                fn new() -> Self {
28626                    Self
28627                }
28628            }
28629            fn baz() {
28630            }
28631        "};
28632    cx.set_state(&buffer);
28633
28634    cx.update_editor(|e, _, cx| {
28635        e.buffer()
28636            .read(cx)
28637            .as_singleton()
28638            .unwrap()
28639            .update(cx, |buffer, cx| {
28640                buffer.set_language(Some(rust_lang()), cx);
28641            })
28642    });
28643
28644    let mut sticky_headers = |offset: ScrollOffset| {
28645        cx.update_editor(|e, window, cx| {
28646            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28647            let style = e.style(cx).clone();
28648            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28649                .into_iter()
28650                .map(
28651                    |StickyHeader {
28652                         start_point,
28653                         offset,
28654                         ..
28655                     }| { (start_point, offset) },
28656                )
28657                .collect::<Vec<_>>()
28658        })
28659    };
28660
28661    let fn_foo = Point { row: 0, column: 0 };
28662    let impl_bar = Point { row: 4, column: 0 };
28663    let fn_new = Point { row: 5, column: 4 };
28664
28665    assert_eq!(sticky_headers(0.0), vec![]);
28666    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28667    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28668    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28669    assert_eq!(sticky_headers(2.0), vec![]);
28670    assert_eq!(sticky_headers(2.5), vec![]);
28671    assert_eq!(sticky_headers(3.0), vec![]);
28672    assert_eq!(sticky_headers(3.5), vec![]);
28673    assert_eq!(sticky_headers(4.0), vec![]);
28674    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28675    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28676    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28677    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28678    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28679    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28680    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28681    assert_eq!(sticky_headers(8.0), vec![]);
28682    assert_eq!(sticky_headers(8.5), vec![]);
28683    assert_eq!(sticky_headers(9.0), vec![]);
28684    assert_eq!(sticky_headers(9.5), vec![]);
28685    assert_eq!(sticky_headers(10.0), vec![]);
28686}
28687
28688#[gpui::test]
28689async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28690    init_test(cx, |_| {});
28691    cx.update(|cx| {
28692        SettingsStore::update_global(cx, |store, cx| {
28693            store.update_user_settings(cx, |settings| {
28694                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28695                    enabled: Some(true),
28696                })
28697            });
28698        });
28699    });
28700    let mut cx = EditorTestContext::new(cx).await;
28701
28702    let line_height = cx.update_editor(|editor, window, cx| {
28703        editor
28704            .style(cx)
28705            .text
28706            .line_height_in_pixels(window.rem_size())
28707    });
28708
28709    let buffer = indoc! {"
28710            ˇfn foo() {
28711                let abc = 123;
28712            }
28713            struct Bar;
28714            impl Bar {
28715                fn new() -> Self {
28716                    Self
28717                }
28718            }
28719            fn baz() {
28720            }
28721        "};
28722    cx.set_state(&buffer);
28723
28724    cx.update_editor(|e, _, cx| {
28725        e.buffer()
28726            .read(cx)
28727            .as_singleton()
28728            .unwrap()
28729            .update(cx, |buffer, cx| {
28730                buffer.set_language(Some(rust_lang()), cx);
28731            })
28732    });
28733
28734    let fn_foo = || empty_range(0, 0);
28735    let impl_bar = || empty_range(4, 0);
28736    let fn_new = || empty_range(5, 4);
28737
28738    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28739        cx.update_editor(|e, window, cx| {
28740            e.scroll(
28741                gpui::Point {
28742                    x: 0.,
28743                    y: scroll_offset,
28744                },
28745                None,
28746                window,
28747                cx,
28748            );
28749        });
28750        cx.simulate_click(
28751            gpui::Point {
28752                x: px(0.),
28753                y: click_offset as f32 * line_height,
28754            },
28755            Modifiers::none(),
28756        );
28757        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28758    };
28759
28760    assert_eq!(
28761        scroll_and_click(
28762            4.5, // impl Bar is halfway off the screen
28763            0.0  // click top of screen
28764        ),
28765        // scrolled to impl Bar
28766        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28767    );
28768
28769    assert_eq!(
28770        scroll_and_click(
28771            4.5,  // impl Bar is halfway off the screen
28772            0.25  // click middle of impl Bar
28773        ),
28774        // scrolled to impl Bar
28775        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28776    );
28777
28778    assert_eq!(
28779        scroll_and_click(
28780            4.5, // impl Bar is halfway off the screen
28781            1.5  // click below impl Bar (e.g. fn new())
28782        ),
28783        // scrolled to fn new() - this is below the impl Bar header which has persisted
28784        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28785    );
28786
28787    assert_eq!(
28788        scroll_and_click(
28789            5.5,  // fn new is halfway underneath impl Bar
28790            0.75  // click on the overlap of impl Bar and fn new()
28791        ),
28792        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28793    );
28794
28795    assert_eq!(
28796        scroll_and_click(
28797            5.5,  // fn new is halfway underneath impl Bar
28798            1.25  // click on the visible part of fn new()
28799        ),
28800        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28801    );
28802
28803    assert_eq!(
28804        scroll_and_click(
28805            1.5, // fn foo is halfway off the screen
28806            0.0  // click top of screen
28807        ),
28808        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28809    );
28810
28811    assert_eq!(
28812        scroll_and_click(
28813            1.5,  // fn foo is halfway off the screen
28814            0.75  // click visible part of let abc...
28815        )
28816        .0,
28817        // no change in scroll
28818        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28819        (gpui::Point { x: 0., y: 1.5 })
28820    );
28821}
28822
28823#[gpui::test]
28824async fn test_next_prev_reference(cx: &mut TestAppContext) {
28825    const CYCLE_POSITIONS: &[&'static str] = &[
28826        indoc! {"
28827            fn foo() {
28828                let ˇabc = 123;
28829                let x = abc + 1;
28830                let y = abc + 2;
28831                let z = abc + 2;
28832            }
28833        "},
28834        indoc! {"
28835            fn foo() {
28836                let abc = 123;
28837                let x = ˇabc + 1;
28838                let y = abc + 2;
28839                let z = abc + 2;
28840            }
28841        "},
28842        indoc! {"
28843            fn foo() {
28844                let abc = 123;
28845                let x = abc + 1;
28846                let y = ˇabc + 2;
28847                let z = abc + 2;
28848            }
28849        "},
28850        indoc! {"
28851            fn foo() {
28852                let abc = 123;
28853                let x = abc + 1;
28854                let y = abc + 2;
28855                let z = ˇabc + 2;
28856            }
28857        "},
28858    ];
28859
28860    init_test(cx, |_| {});
28861
28862    let mut cx = EditorLspTestContext::new_rust(
28863        lsp::ServerCapabilities {
28864            references_provider: Some(lsp::OneOf::Left(true)),
28865            ..Default::default()
28866        },
28867        cx,
28868    )
28869    .await;
28870
28871    // importantly, the cursor is in the middle
28872    cx.set_state(indoc! {"
28873        fn foo() {
28874            let aˇbc = 123;
28875            let x = abc + 1;
28876            let y = abc + 2;
28877            let z = abc + 2;
28878        }
28879    "});
28880
28881    let reference_ranges = [
28882        lsp::Position::new(1, 8),
28883        lsp::Position::new(2, 12),
28884        lsp::Position::new(3, 12),
28885        lsp::Position::new(4, 12),
28886    ]
28887    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28888
28889    cx.lsp
28890        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28891            Ok(Some(
28892                reference_ranges
28893                    .map(|range| lsp::Location {
28894                        uri: params.text_document_position.text_document.uri.clone(),
28895                        range,
28896                    })
28897                    .to_vec(),
28898            ))
28899        });
28900
28901    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28902        cx.update_editor(|editor, window, cx| {
28903            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28904        })
28905        .unwrap()
28906        .await
28907        .unwrap()
28908    };
28909
28910    _move(Direction::Next, 1, &mut cx).await;
28911    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28912
28913    _move(Direction::Next, 1, &mut cx).await;
28914    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28915
28916    _move(Direction::Next, 1, &mut cx).await;
28917    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28918
28919    // loops back to the start
28920    _move(Direction::Next, 1, &mut cx).await;
28921    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28922
28923    // loops back to the end
28924    _move(Direction::Prev, 1, &mut cx).await;
28925    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28926
28927    _move(Direction::Prev, 1, &mut cx).await;
28928    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28929
28930    _move(Direction::Prev, 1, &mut cx).await;
28931    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28932
28933    _move(Direction::Prev, 1, &mut cx).await;
28934    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28935
28936    _move(Direction::Next, 3, &mut cx).await;
28937    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28938
28939    _move(Direction::Prev, 2, &mut cx).await;
28940    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28941}
28942
28943#[gpui::test]
28944async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28945    init_test(cx, |_| {});
28946
28947    let (editor, cx) = cx.add_window_view(|window, cx| {
28948        let multi_buffer = MultiBuffer::build_multi(
28949            [
28950                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28951                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28952            ],
28953            cx,
28954        );
28955        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28956    });
28957
28958    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28959    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28960
28961    cx.assert_excerpts_with_selections(indoc! {"
28962        [EXCERPT]
28963        ˇ1
28964        2
28965        3
28966        [EXCERPT]
28967        1
28968        2
28969        3
28970        "});
28971
28972    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28973    cx.update_editor(|editor, window, cx| {
28974        editor.change_selections(None.into(), window, cx, |s| {
28975            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28976        });
28977    });
28978    cx.assert_excerpts_with_selections(indoc! {"
28979        [EXCERPT]
28980        1
2898128982        3
28983        [EXCERPT]
28984        1
28985        2
28986        3
28987        "});
28988
28989    cx.update_editor(|editor, window, cx| {
28990        editor
28991            .select_all_matches(&SelectAllMatches, window, cx)
28992            .unwrap();
28993    });
28994    cx.assert_excerpts_with_selections(indoc! {"
28995        [EXCERPT]
28996        1
2899728998        3
28999        [EXCERPT]
29000        1
2900129002        3
29003        "});
29004
29005    cx.update_editor(|editor, window, cx| {
29006        editor.handle_input("X", window, cx);
29007    });
29008    cx.assert_excerpts_with_selections(indoc! {"
29009        [EXCERPT]
29010        1
2901129012        3
29013        [EXCERPT]
29014        1
2901529016        3
29017        "});
29018
29019    // Scenario 2: Select "2", then fold second buffer before insertion
29020    cx.update_multibuffer(|mb, cx| {
29021        for buffer_id in buffer_ids.iter() {
29022            let buffer = mb.buffer(*buffer_id).unwrap();
29023            buffer.update(cx, |buffer, cx| {
29024                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29025            });
29026        }
29027    });
29028
29029    // Select "2" and select all matches
29030    cx.update_editor(|editor, window, cx| {
29031        editor.change_selections(None.into(), window, cx, |s| {
29032            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29033        });
29034        editor
29035            .select_all_matches(&SelectAllMatches, window, cx)
29036            .unwrap();
29037    });
29038
29039    // Fold second buffer - should remove selections from folded buffer
29040    cx.update_editor(|editor, _, cx| {
29041        editor.fold_buffer(buffer_ids[1], cx);
29042    });
29043    cx.assert_excerpts_with_selections(indoc! {"
29044        [EXCERPT]
29045        1
2904629047        3
29048        [EXCERPT]
29049        [FOLDED]
29050        "});
29051
29052    // Insert text - should only affect first buffer
29053    cx.update_editor(|editor, window, cx| {
29054        editor.handle_input("Y", window, cx);
29055    });
29056    cx.update_editor(|editor, _, cx| {
29057        editor.unfold_buffer(buffer_ids[1], cx);
29058    });
29059    cx.assert_excerpts_with_selections(indoc! {"
29060        [EXCERPT]
29061        1
2906229063        3
29064        [EXCERPT]
29065        1
29066        2
29067        3
29068        "});
29069
29070    // Scenario 3: Select "2", then fold first buffer before insertion
29071    cx.update_multibuffer(|mb, cx| {
29072        for buffer_id in buffer_ids.iter() {
29073            let buffer = mb.buffer(*buffer_id).unwrap();
29074            buffer.update(cx, |buffer, cx| {
29075                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29076            });
29077        }
29078    });
29079
29080    // Select "2" and select all matches
29081    cx.update_editor(|editor, window, cx| {
29082        editor.change_selections(None.into(), window, cx, |s| {
29083            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29084        });
29085        editor
29086            .select_all_matches(&SelectAllMatches, window, cx)
29087            .unwrap();
29088    });
29089
29090    // Fold first buffer - should remove selections from folded buffer
29091    cx.update_editor(|editor, _, cx| {
29092        editor.fold_buffer(buffer_ids[0], cx);
29093    });
29094    cx.assert_excerpts_with_selections(indoc! {"
29095        [EXCERPT]
29096        [FOLDED]
29097        [EXCERPT]
29098        1
2909929100        3
29101        "});
29102
29103    // Insert text - should only affect second buffer
29104    cx.update_editor(|editor, window, cx| {
29105        editor.handle_input("Z", window, cx);
29106    });
29107    cx.update_editor(|editor, _, cx| {
29108        editor.unfold_buffer(buffer_ids[0], cx);
29109    });
29110    cx.assert_excerpts_with_selections(indoc! {"
29111        [EXCERPT]
29112        1
29113        2
29114        3
29115        [EXCERPT]
29116        1
2911729118        3
29119        "});
29120
29121    // Test correct folded header is selected upon fold
29122    cx.update_editor(|editor, _, cx| {
29123        editor.fold_buffer(buffer_ids[0], cx);
29124        editor.fold_buffer(buffer_ids[1], cx);
29125    });
29126    cx.assert_excerpts_with_selections(indoc! {"
29127        [EXCERPT]
29128        [FOLDED]
29129        [EXCERPT]
29130        ˇ[FOLDED]
29131        "});
29132
29133    // Test selection inside folded buffer unfolds it on type
29134    cx.update_editor(|editor, window, cx| {
29135        editor.handle_input("W", window, cx);
29136    });
29137    cx.update_editor(|editor, _, cx| {
29138        editor.unfold_buffer(buffer_ids[0], cx);
29139    });
29140    cx.assert_excerpts_with_selections(indoc! {"
29141        [EXCERPT]
29142        1
29143        2
29144        3
29145        [EXCERPT]
29146        Wˇ1
29147        Z
29148        3
29149        "});
29150}
29151
29152#[gpui::test]
29153async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
29154    init_test(cx, |_| {});
29155    let mut leader_cx = EditorTestContext::new(cx).await;
29156
29157    let diff_base = indoc!(
29158        r#"
29159        one
29160        two
29161        three
29162        four
29163        five
29164        six
29165        "#
29166    );
29167
29168    let initial_state = indoc!(
29169        r#"
29170        ˇone
29171        two
29172        THREE
29173        four
29174        five
29175        six
29176        "#
29177    );
29178
29179    leader_cx.set_state(initial_state);
29180
29181    leader_cx.set_head_text(&diff_base);
29182    leader_cx.run_until_parked();
29183
29184    let follower = leader_cx.update_multibuffer(|leader, cx| {
29185        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29186        leader.set_all_diff_hunks_expanded(cx);
29187        leader.get_or_create_follower(cx)
29188    });
29189    follower.update(cx, |follower, cx| {
29190        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29191        follower.set_all_diff_hunks_expanded(cx);
29192    });
29193
29194    let follower_editor =
29195        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
29196    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
29197
29198    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
29199    cx.run_until_parked();
29200
29201    leader_cx.assert_editor_state(initial_state);
29202    follower_cx.assert_editor_state(indoc! {
29203        r#"
29204        ˇone
29205        two
29206        three
29207        four
29208        five
29209        six
29210        "#
29211    });
29212
29213    follower_cx.editor(|editor, _window, cx| {
29214        assert!(editor.read_only(cx));
29215    });
29216
29217    leader_cx.update_editor(|editor, _window, cx| {
29218        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
29219    });
29220    cx.run_until_parked();
29221
29222    leader_cx.assert_editor_state(indoc! {
29223        r#"
29224        ˇone
29225        two
29226        THREE
29227        four
29228        FIVE
29229        six
29230        "#
29231    });
29232
29233    follower_cx.assert_editor_state(indoc! {
29234        r#"
29235        ˇone
29236        two
29237        three
29238        four
29239        five
29240        six
29241        "#
29242    });
29243
29244    leader_cx.update_editor(|editor, _window, cx| {
29245        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
29246    });
29247    cx.run_until_parked();
29248
29249    leader_cx.assert_editor_state(indoc! {
29250        r#"
29251        ˇone
29252        two
29253        THREE
29254        four
29255        FIVE
29256        six
29257        SEVEN"#
29258    });
29259
29260    follower_cx.assert_editor_state(indoc! {
29261        r#"
29262        ˇone
29263        two
29264        three
29265        four
29266        five
29267        six
29268        "#
29269    });
29270
29271    leader_cx.update_editor(|editor, window, cx| {
29272        editor.move_down(&MoveDown, window, cx);
29273        editor.refresh_selected_text_highlights(true, window, cx);
29274    });
29275    leader_cx.run_until_parked();
29276}
29277
29278#[gpui::test]
29279async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
29280    init_test(cx, |_| {});
29281    let base_text = "base\n";
29282    let buffer_text = "buffer\n";
29283
29284    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
29285    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
29286
29287    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
29288    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
29289    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
29290    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
29291
29292    let leader = cx.new(|cx| {
29293        let mut leader = MultiBuffer::new(Capability::ReadWrite);
29294        leader.set_all_diff_hunks_expanded(cx);
29295        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29296        leader
29297    });
29298    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
29299    follower.update(cx, |follower, _| {
29300        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29301    });
29302
29303    leader.update(cx, |leader, cx| {
29304        leader.insert_excerpts_after(
29305            ExcerptId::min(),
29306            extra_buffer_2.clone(),
29307            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29308            cx,
29309        );
29310        leader.add_diff(extra_diff_2.clone(), cx);
29311
29312        leader.insert_excerpts_after(
29313            ExcerptId::min(),
29314            extra_buffer_1.clone(),
29315            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29316            cx,
29317        );
29318        leader.add_diff(extra_diff_1.clone(), cx);
29319
29320        leader.insert_excerpts_after(
29321            ExcerptId::min(),
29322            buffer1.clone(),
29323            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29324            cx,
29325        );
29326        leader.add_diff(diff1.clone(), cx);
29327    });
29328
29329    cx.run_until_parked();
29330    let mut cx = cx.add_empty_window();
29331
29332    let leader_editor = cx
29333        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29334    let follower_editor = cx.new_window_entity(|window, cx| {
29335        Editor::for_multibuffer(follower.clone(), None, window, cx)
29336    });
29337
29338    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29339    leader_cx.assert_editor_state(indoc! {"
29340       ˇbuffer
29341
29342       dummy text 1
29343
29344       dummy text 2
29345    "});
29346    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29347    follower_cx.assert_editor_state(indoc! {"
29348        ˇbase
29349
29350
29351    "});
29352}
29353
29354#[gpui::test]
29355async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29356    init_test(cx, |_| {});
29357
29358    let (editor, cx) = cx.add_window_view(|window, cx| {
29359        let multi_buffer = MultiBuffer::build_multi(
29360            [
29361                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29362                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29363            ],
29364            cx,
29365        );
29366        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29367    });
29368
29369    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29370
29371    cx.assert_excerpts_with_selections(indoc! {"
29372        [EXCERPT]
29373        ˇ1
29374        2
29375        3
29376        [EXCERPT]
29377        1
29378        2
29379        3
29380        4
29381        5
29382        6
29383        7
29384        8
29385        9
29386        "});
29387
29388    cx.update_editor(|editor, window, cx| {
29389        editor.change_selections(None.into(), window, cx, |s| {
29390            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29391        });
29392    });
29393
29394    cx.assert_excerpts_with_selections(indoc! {"
29395        [EXCERPT]
29396        1
29397        2
29398        3
29399        [EXCERPT]
29400        1
29401        2
29402        3
29403        4
29404        5
29405        6
29406        ˇ7
29407        8
29408        9
29409        "});
29410
29411    cx.update_editor(|editor, _window, cx| {
29412        editor.set_vertical_scroll_margin(0, cx);
29413    });
29414
29415    cx.update_editor(|editor, window, cx| {
29416        assert_eq!(editor.vertical_scroll_margin(), 0);
29417        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29418        assert_eq!(
29419            editor.snapshot(window, cx).scroll_position(),
29420            gpui::Point::new(0., 12.0)
29421        );
29422    });
29423
29424    cx.update_editor(|editor, _window, cx| {
29425        editor.set_vertical_scroll_margin(3, cx);
29426    });
29427
29428    cx.update_editor(|editor, window, cx| {
29429        assert_eq!(editor.vertical_scroll_margin(), 3);
29430        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29431        assert_eq!(
29432            editor.snapshot(window, cx).scroll_position(),
29433            gpui::Point::new(0., 9.0)
29434        );
29435    });
29436}
29437
29438#[gpui::test]
29439async fn test_find_references_single_case(cx: &mut TestAppContext) {
29440    init_test(cx, |_| {});
29441    let mut cx = EditorLspTestContext::new_rust(
29442        lsp::ServerCapabilities {
29443            references_provider: Some(lsp::OneOf::Left(true)),
29444            ..lsp::ServerCapabilities::default()
29445        },
29446        cx,
29447    )
29448    .await;
29449
29450    let before = indoc!(
29451        r#"
29452        fn main() {
29453            let aˇbc = 123;
29454            let xyz = abc;
29455        }
29456        "#
29457    );
29458    let after = indoc!(
29459        r#"
29460        fn main() {
29461            let abc = 123;
29462            let xyz = ˇabc;
29463        }
29464        "#
29465    );
29466
29467    cx.lsp
29468        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29469            Ok(Some(vec![
29470                lsp::Location {
29471                    uri: params.text_document_position.text_document.uri.clone(),
29472                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29473                },
29474                lsp::Location {
29475                    uri: params.text_document_position.text_document.uri,
29476                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29477                },
29478            ]))
29479        });
29480
29481    cx.set_state(before);
29482
29483    let action = FindAllReferences {
29484        always_open_multibuffer: false,
29485    };
29486
29487    let navigated = cx
29488        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29489        .expect("should have spawned a task")
29490        .await
29491        .unwrap();
29492
29493    assert_eq!(navigated, Navigated::No);
29494
29495    cx.run_until_parked();
29496
29497    cx.assert_editor_state(after);
29498}
29499
29500#[gpui::test]
29501async fn test_local_worktree_trust(cx: &mut TestAppContext) {
29502    init_test(cx, |_| {});
29503    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
29504
29505    cx.update(|cx| {
29506        SettingsStore::update_global(cx, |store, cx| {
29507            store.update_user_settings(cx, |settings| {
29508                settings.project.all_languages.defaults.inlay_hints =
29509                    Some(InlayHintSettingsContent {
29510                        enabled: Some(true),
29511                        ..InlayHintSettingsContent::default()
29512                    });
29513            });
29514        });
29515    });
29516
29517    let fs = FakeFs::new(cx.executor());
29518    fs.insert_tree(
29519        path!("/project"),
29520        json!({
29521            ".zed": {
29522                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
29523            },
29524            "main.rs": "fn main() {}"
29525        }),
29526    )
29527    .await;
29528
29529    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
29530    let server_name = "override-rust-analyzer";
29531    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
29532
29533    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29534    language_registry.add(rust_lang());
29535
29536    let capabilities = lsp::ServerCapabilities {
29537        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29538        ..lsp::ServerCapabilities::default()
29539    };
29540    let mut fake_language_servers = language_registry.register_fake_lsp(
29541        "Rust",
29542        FakeLspAdapter {
29543            name: server_name,
29544            capabilities,
29545            initializer: Some(Box::new({
29546                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29547                move |fake_server| {
29548                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
29549                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29550                        move |_params, _| {
29551                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
29552                            async move {
29553                                Ok(Some(vec![lsp::InlayHint {
29554                                    position: lsp::Position::new(0, 0),
29555                                    label: lsp::InlayHintLabel::String("hint".to_string()),
29556                                    kind: None,
29557                                    text_edits: None,
29558                                    tooltip: None,
29559                                    padding_left: None,
29560                                    padding_right: None,
29561                                    data: None,
29562                                }]))
29563                            }
29564                        },
29565                    );
29566                }
29567            })),
29568            ..FakeLspAdapter::default()
29569        },
29570    );
29571
29572    cx.run_until_parked();
29573
29574    let worktree_id = project.read_with(cx, |project, cx| {
29575        project
29576            .worktrees(cx)
29577            .next()
29578            .map(|wt| wt.read(cx).id())
29579            .expect("should have a worktree")
29580    });
29581
29582    let trusted_worktrees =
29583        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
29584
29585    let can_trust = trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29586    assert!(!can_trust, "worktree should be restricted initially");
29587
29588    let buffer_before_approval = project
29589        .update(cx, |project, cx| {
29590            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
29591        })
29592        .await
29593        .unwrap();
29594
29595    let (editor, cx) = cx.add_window_view(|window, cx| {
29596        Editor::new(
29597            EditorMode::full(),
29598            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
29599            Some(project.clone()),
29600            window,
29601            cx,
29602        )
29603    });
29604    cx.run_until_parked();
29605    let fake_language_server = fake_language_servers.next();
29606
29607    cx.read(|cx| {
29608        let file = buffer_before_approval.read(cx).file();
29609        assert_eq!(
29610            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29611                .language_servers,
29612            ["...".to_string()],
29613            "local .zed/settings.json must not apply before trust approval"
29614        )
29615    });
29616
29617    editor.update_in(cx, |editor, window, cx| {
29618        editor.handle_input("1", window, cx);
29619    });
29620    cx.run_until_parked();
29621    cx.executor()
29622        .advance_clock(std::time::Duration::from_secs(1));
29623    assert_eq!(
29624        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
29625        0,
29626        "inlay hints must not be queried before trust approval"
29627    );
29628
29629    trusted_worktrees.update(cx, |store, cx| {
29630        store.trust(
29631            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
29632            None,
29633            cx,
29634        );
29635    });
29636    cx.run_until_parked();
29637
29638    cx.read(|cx| {
29639        let file = buffer_before_approval.read(cx).file();
29640        assert_eq!(
29641            language::language_settings::language_settings(Some("Rust".into()), file, cx)
29642                .language_servers,
29643            ["override-rust-analyzer".to_string()],
29644            "local .zed/settings.json should apply after trust approval"
29645        )
29646    });
29647    let _fake_language_server = fake_language_server.await.unwrap();
29648    editor.update_in(cx, |editor, window, cx| {
29649        editor.handle_input("1", window, cx);
29650    });
29651    cx.run_until_parked();
29652    cx.executor()
29653        .advance_clock(std::time::Duration::from_secs(1));
29654    assert!(
29655        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
29656        "inlay hints should be queried after trust approval"
29657    );
29658
29659    let can_trust_after =
29660        trusted_worktrees.update(cx, |store, cx| store.can_trust(worktree_id, cx));
29661    assert!(can_trust_after, "worktree should be trusted after trust()");
29662}
29663
29664#[gpui::test]
29665fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
29666    // This test reproduces a bug where drawing an editor at a position above the viewport
29667    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
29668    // causes an infinite loop in blocks_in_range.
29669    //
29670    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
29671    // the content mask intersection produces visible_bounds with origin at the viewport top.
29672    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
29673    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
29674    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
29675    init_test(cx, |_| {});
29676
29677    let window = cx.add_window(|_, _| gpui::Empty);
29678    let mut cx = VisualTestContext::from_window(*window, cx);
29679
29680    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
29681    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
29682
29683    // Simulate a small viewport (500x500 pixels at origin 0,0)
29684    cx.simulate_resize(gpui::size(px(500.), px(500.)));
29685
29686    // Draw the editor at a very negative Y position, simulating an editor that's been
29687    // scrolled way above the visible viewport (like in a List that has scrolled past it).
29688    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
29689    // This should NOT hang - it should just render nothing.
29690    cx.draw(
29691        gpui::point(px(0.), px(-10000.)),
29692        gpui::size(px(500.), px(3000.)),
29693        |_, _| editor.clone(),
29694    );
29695
29696    // If we get here without hanging, the test passes
29697}