editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs, Project,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47    trusted_worktrees::{PathTrust, TrustedWorktrees},
   48};
   49use serde_json::{self, json};
   50use settings::{
   51    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   52    IndentGuideColoring, InlayHintSettingsContent, ProjectSettingsContent, SearchSettingsContent,
   53    SettingsStore,
   54};
   55use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   56use std::{
   57    iter,
   58    sync::atomic::{self, AtomicUsize},
   59};
   60use test::build_editor_with_project;
   61use text::ToPoint as _;
   62use unindent::Unindent;
   63use util::{
   64    assert_set_eq, path,
   65    rel_path::rel_path,
   66    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   67    uri,
   68};
   69use workspace::{
   70    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   71    OpenOptions, ViewId,
   72    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   73    register_project_item,
   74};
   75
   76fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   77    editor
   78        .selections
   79        .display_ranges(&editor.display_snapshot(cx))
   80}
   81
   82#[gpui::test]
   83fn test_edit_events(cx: &mut TestAppContext) {
   84    init_test(cx, |_| {});
   85
   86    let buffer = cx.new(|cx| {
   87        let mut buffer = language::Buffer::local("123456", cx);
   88        buffer.set_group_interval(Duration::from_secs(1));
   89        buffer
   90    });
   91
   92    let events = Rc::new(RefCell::new(Vec::new()));
   93    let editor1 = cx.add_window({
   94        let events = events.clone();
   95        |window, cx| {
   96            let entity = cx.entity();
   97            cx.subscribe_in(
   98                &entity,
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor1", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    let editor2 = cx.add_window({
  114        let events = events.clone();
  115        |window, cx| {
  116            cx.subscribe_in(
  117                &cx.entity(),
  118                window,
  119                move |_, _, event: &EditorEvent, _, _| match event {
  120                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  121                    EditorEvent::BufferEdited => {
  122                        events.borrow_mut().push(("editor2", "buffer edited"))
  123                    }
  124                    _ => {}
  125                },
  126            )
  127            .detach();
  128            Editor::for_buffer(buffer.clone(), None, window, cx)
  129        }
  130    });
  131
  132    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  133
  134    // Mutating editor 1 will emit an `Edited` event only for that editor.
  135    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  136    assert_eq!(
  137        mem::take(&mut *events.borrow_mut()),
  138        [
  139            ("editor1", "edited"),
  140            ("editor1", "buffer edited"),
  141            ("editor2", "buffer edited"),
  142        ]
  143    );
  144
  145    // Mutating editor 2 will emit an `Edited` event only for that editor.
  146    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  147    assert_eq!(
  148        mem::take(&mut *events.borrow_mut()),
  149        [
  150            ("editor2", "edited"),
  151            ("editor1", "buffer edited"),
  152            ("editor2", "buffer edited"),
  153        ]
  154    );
  155
  156    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  157    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  158    assert_eq!(
  159        mem::take(&mut *events.borrow_mut()),
  160        [
  161            ("editor1", "edited"),
  162            ("editor1", "buffer edited"),
  163            ("editor2", "buffer edited"),
  164        ]
  165    );
  166
  167    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  168    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  169    assert_eq!(
  170        mem::take(&mut *events.borrow_mut()),
  171        [
  172            ("editor1", "edited"),
  173            ("editor1", "buffer edited"),
  174            ("editor2", "buffer edited"),
  175        ]
  176    );
  177
  178    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  179    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  180    assert_eq!(
  181        mem::take(&mut *events.borrow_mut()),
  182        [
  183            ("editor2", "edited"),
  184            ("editor1", "buffer edited"),
  185            ("editor2", "buffer edited"),
  186        ]
  187    );
  188
  189    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  190    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  191    assert_eq!(
  192        mem::take(&mut *events.borrow_mut()),
  193        [
  194            ("editor2", "edited"),
  195            ("editor1", "buffer edited"),
  196            ("editor2", "buffer edited"),
  197        ]
  198    );
  199
  200    // No event is emitted when the mutation is a no-op.
  201    _ = editor2.update(cx, |editor, window, cx| {
  202        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  203            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  204        });
  205
  206        editor.backspace(&Backspace, window, cx);
  207    });
  208    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  209}
  210
  211#[gpui::test]
  212fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  213    init_test(cx, |_| {});
  214
  215    let mut now = Instant::now();
  216    let group_interval = Duration::from_millis(1);
  217    let buffer = cx.new(|cx| {
  218        let mut buf = language::Buffer::local("123456", cx);
  219        buf.set_group_interval(group_interval);
  220        buf
  221    });
  222    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  223    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  224
  225    _ = editor.update(cx, |editor, window, cx| {
  226        editor.start_transaction_at(now, window, cx);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  229        });
  230
  231        editor.insert("cd", window, cx);
  232        editor.end_transaction_at(now, cx);
  233        assert_eq!(editor.text(cx), "12cd56");
  234        assert_eq!(
  235            editor.selections.ranges(&editor.display_snapshot(cx)),
  236            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  237        );
  238
  239        editor.start_transaction_at(now, window, cx);
  240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  241            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  242        });
  243        editor.insert("e", window, cx);
  244        editor.end_transaction_at(now, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(
  247            editor.selections.ranges(&editor.display_snapshot(cx)),
  248            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  249        );
  250
  251        now += group_interval + Duration::from_millis(1);
  252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  253            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  254        });
  255
  256        // Simulate an edit in another editor
  257        buffer.update(cx, |buffer, cx| {
  258            buffer.start_transaction_at(now, cx);
  259            buffer.edit(
  260                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  261                None,
  262                cx,
  263            );
  264            buffer.edit(
  265                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  266                None,
  267                cx,
  268            );
  269            buffer.end_transaction_at(now, cx);
  270        });
  271
  272        assert_eq!(editor.text(cx), "ab2cde6");
  273        assert_eq!(
  274            editor.selections.ranges(&editor.display_snapshot(cx)),
  275            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  276        );
  277
  278        // Last transaction happened past the group interval in a different editor.
  279        // Undo it individually and don't restore selections.
  280        editor.undo(&Undo, window, cx);
  281        assert_eq!(editor.text(cx), "12cde6");
  282        assert_eq!(
  283            editor.selections.ranges(&editor.display_snapshot(cx)),
  284            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  285        );
  286
  287        // First two transactions happened within the group interval in this editor.
  288        // Undo them together and restore selections.
  289        editor.undo(&Undo, window, cx);
  290        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  291        assert_eq!(editor.text(cx), "123456");
  292        assert_eq!(
  293            editor.selections.ranges(&editor.display_snapshot(cx)),
  294            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  295        );
  296
  297        // Redo the first two transactions together.
  298        editor.redo(&Redo, window, cx);
  299        assert_eq!(editor.text(cx), "12cde6");
  300        assert_eq!(
  301            editor.selections.ranges(&editor.display_snapshot(cx)),
  302            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  303        );
  304
  305        // Redo the last transaction on its own.
  306        editor.redo(&Redo, window, cx);
  307        assert_eq!(editor.text(cx), "ab2cde6");
  308        assert_eq!(
  309            editor.selections.ranges(&editor.display_snapshot(cx)),
  310            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  311        );
  312
  313        // Test empty transactions.
  314        editor.start_transaction_at(now, window, cx);
  315        editor.end_transaction_at(now, cx);
  316        editor.undo(&Undo, window, cx);
  317        assert_eq!(editor.text(cx), "12cde6");
  318    });
  319}
  320
  321#[gpui::test]
  322fn test_ime_composition(cx: &mut TestAppContext) {
  323    init_test(cx, |_| {});
  324
  325    let buffer = cx.new(|cx| {
  326        let mut buffer = language::Buffer::local("abcde", cx);
  327        // Ensure automatic grouping doesn't occur.
  328        buffer.set_group_interval(Duration::ZERO);
  329        buffer
  330    });
  331
  332    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  333    cx.add_window(|window, cx| {
  334        let mut editor = build_editor(buffer.clone(), window, cx);
  335
  336        // Start a new IME composition.
  337        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  339        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  340        assert_eq!(editor.text(cx), "äbcde");
  341        assert_eq!(
  342            editor.marked_text_ranges(cx),
  343            Some(vec![
  344                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  345            ])
  346        );
  347
  348        // Finalize IME composition.
  349        editor.replace_text_in_range(None, "ā", window, cx);
  350        assert_eq!(editor.text(cx), "ābcde");
  351        assert_eq!(editor.marked_text_ranges(cx), None);
  352
  353        // IME composition edits are grouped and are undone/redone at once.
  354        editor.undo(&Default::default(), window, cx);
  355        assert_eq!(editor.text(cx), "abcde");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357        editor.redo(&Default::default(), window, cx);
  358        assert_eq!(editor.text(cx), "ābcde");
  359        assert_eq!(editor.marked_text_ranges(cx), None);
  360
  361        // Start a new IME composition.
  362        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  363        assert_eq!(
  364            editor.marked_text_ranges(cx),
  365            Some(vec![
  366                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  367            ])
  368        );
  369
  370        // Undoing during an IME composition cancels it.
  371        editor.undo(&Default::default(), window, cx);
  372        assert_eq!(editor.text(cx), "ābcde");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  376        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  377        assert_eq!(editor.text(cx), "ābcdè");
  378        assert_eq!(
  379            editor.marked_text_ranges(cx),
  380            Some(vec![
  381                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  382            ])
  383        );
  384
  385        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  386        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  387        assert_eq!(editor.text(cx), "ābcdę");
  388        assert_eq!(editor.marked_text_ranges(cx), None);
  389
  390        // Start a new IME composition with multiple cursors.
  391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  392            s.select_ranges([
  393                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  396            ])
  397        });
  398        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  399        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  400        assert_eq!(
  401            editor.marked_text_ranges(cx),
  402            Some(vec![
  403                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  406            ])
  407        );
  408
  409        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  410        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  411        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  412        assert_eq!(
  413            editor.marked_text_ranges(cx),
  414            Some(vec![
  415                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  418            ])
  419        );
  420
  421        // Finalize IME composition with multiple cursors.
  422        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  423        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  424        assert_eq!(editor.marked_text_ranges(cx), None);
  425
  426        editor
  427    });
  428}
  429
  430#[gpui::test]
  431fn test_selection_with_mouse(cx: &mut TestAppContext) {
  432    init_test(cx, |_| {});
  433
  434    let editor = cx.add_window(|window, cx| {
  435        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  436        build_editor(buffer, window, cx)
  437    });
  438
  439    _ = editor.update(cx, |editor, window, cx| {
  440        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  441    });
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.update_selection(
  451            DisplayPoint::new(DisplayRow(3), 3),
  452            0,
  453            gpui::Point::<f32>::default(),
  454            window,
  455            cx,
  456        );
  457    });
  458
  459    assert_eq!(
  460        editor
  461            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  462            .unwrap(),
  463        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  464    );
  465
  466    _ = editor.update(cx, |editor, window, cx| {
  467        editor.update_selection(
  468            DisplayPoint::new(DisplayRow(1), 1),
  469            0,
  470            gpui::Point::<f32>::default(),
  471            window,
  472            cx,
  473        );
  474    });
  475
  476    assert_eq!(
  477        editor
  478            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  479            .unwrap(),
  480        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  481    );
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.end_selection(window, cx);
  485        editor.update_selection(
  486            DisplayPoint::new(DisplayRow(3), 3),
  487            0,
  488            gpui::Point::<f32>::default(),
  489            window,
  490            cx,
  491        );
  492    });
  493
  494    assert_eq!(
  495        editor
  496            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  497            .unwrap(),
  498        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  503        editor.update_selection(
  504            DisplayPoint::new(DisplayRow(0), 0),
  505            0,
  506            gpui::Point::<f32>::default(),
  507            window,
  508            cx,
  509        );
  510    });
  511
  512    assert_eq!(
  513        editor
  514            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  515            .unwrap(),
  516        [
  517            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  518            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  519        ]
  520    );
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  545    });
  546
  547    _ = editor.update(cx, |editor, window, cx| {
  548        editor.end_selection(window, cx);
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  553    });
  554
  555    _ = editor.update(cx, |editor, window, cx| {
  556        editor.end_selection(window, cx);
  557    });
  558
  559    assert_eq!(
  560        editor
  561            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  562            .unwrap(),
  563        [
  564            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  565            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  566        ]
  567    );
  568
  569    _ = editor.update(cx, |editor, window, cx| {
  570        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.end_selection(window, cx);
  575    });
  576
  577    assert_eq!(
  578        editor
  579            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  580            .unwrap(),
  581        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  582    );
  583}
  584
  585#[gpui::test]
  586fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  587    init_test(cx, |_| {});
  588
  589    let editor = cx.add_window(|window, cx| {
  590        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  591        build_editor(buffer, window, cx)
  592    });
  593
  594    _ = editor.update(cx, |editor, window, cx| {
  595        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  596        assert_eq!(
  597            display_ranges(editor, cx),
  598            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  599        );
  600    });
  601
  602    _ = editor.update(cx, |editor, window, cx| {
  603        editor.update_selection(
  604            DisplayPoint::new(DisplayRow(3), 3),
  605            0,
  606            gpui::Point::<f32>::default(),
  607            window,
  608            cx,
  609        );
  610        assert_eq!(
  611            display_ranges(editor, cx),
  612            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  613        );
  614    });
  615
  616    _ = editor.update(cx, |editor, window, cx| {
  617        editor.cancel(&Cancel, window, cx);
  618        editor.update_selection(
  619            DisplayPoint::new(DisplayRow(1), 1),
  620            0,
  621            gpui::Point::<f32>::default(),
  622            window,
  623            cx,
  624        );
  625        assert_eq!(
  626            display_ranges(editor, cx),
  627            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  628        );
  629    });
  630}
  631
  632#[gpui::test]
  633fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  634    init_test(cx, |_| {});
  635
  636    let editor = cx.add_window(|window, cx| {
  637        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  638        build_editor(buffer, window, cx)
  639    });
  640
  641    _ = editor.update(cx, |editor, window, cx| {
  642        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  643        assert_eq!(
  644            display_ranges(editor, cx),
  645            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  646        );
  647
  648        editor.move_down(&Default::default(), window, cx);
  649        assert_eq!(
  650            display_ranges(editor, cx),
  651            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  652        );
  653
  654        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  655        assert_eq!(
  656            display_ranges(editor, cx),
  657            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  658        );
  659
  660        editor.move_up(&Default::default(), window, cx);
  661        assert_eq!(
  662            display_ranges(editor, cx),
  663            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  664        );
  665    });
  666}
  667
  668#[gpui::test]
  669fn test_extending_selection(cx: &mut TestAppContext) {
  670    init_test(cx, |_| {});
  671
  672    let editor = cx.add_window(|window, cx| {
  673        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  674        build_editor(buffer, window, cx)
  675    });
  676
  677    _ = editor.update(cx, |editor, window, cx| {
  678        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  679        editor.end_selection(window, cx);
  680        assert_eq!(
  681            display_ranges(editor, cx),
  682            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  683        );
  684
  685        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  686        editor.end_selection(window, cx);
  687        assert_eq!(
  688            display_ranges(editor, cx),
  689            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  690        );
  691
  692        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  693        editor.end_selection(window, cx);
  694        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  695        assert_eq!(
  696            display_ranges(editor, cx),
  697            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  698        );
  699
  700        editor.update_selection(
  701            DisplayPoint::new(DisplayRow(0), 1),
  702            0,
  703            gpui::Point::<f32>::default(),
  704            window,
  705            cx,
  706        );
  707        editor.end_selection(window, cx);
  708        assert_eq!(
  709            display_ranges(editor, cx),
  710            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  711        );
  712
  713        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  714        editor.end_selection(window, cx);
  715        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  716        editor.end_selection(window, cx);
  717        assert_eq!(
  718            display_ranges(editor, cx),
  719            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  720        );
  721
  722        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  723        assert_eq!(
  724            display_ranges(editor, cx),
  725            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  726        );
  727
  728        editor.update_selection(
  729            DisplayPoint::new(DisplayRow(0), 6),
  730            0,
  731            gpui::Point::<f32>::default(),
  732            window,
  733            cx,
  734        );
  735        assert_eq!(
  736            display_ranges(editor, cx),
  737            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  738        );
  739
  740        editor.update_selection(
  741            DisplayPoint::new(DisplayRow(0), 1),
  742            0,
  743            gpui::Point::<f32>::default(),
  744            window,
  745            cx,
  746        );
  747        editor.end_selection(window, cx);
  748        assert_eq!(
  749            display_ranges(editor, cx),
  750            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  751        );
  752    });
  753}
  754
  755#[gpui::test]
  756fn test_clone(cx: &mut TestAppContext) {
  757    init_test(cx, |_| {});
  758
  759    let (text, selection_ranges) = marked_text_ranges(
  760        indoc! {"
  761            one
  762            two
  763            threeˇ
  764            four
  765            fiveˇ
  766        "},
  767        true,
  768    );
  769
  770    let editor = cx.add_window(|window, cx| {
  771        let buffer = MultiBuffer::build_simple(&text, cx);
  772        build_editor(buffer, window, cx)
  773    });
  774
  775    _ = editor.update(cx, |editor, window, cx| {
  776        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  777            s.select_ranges(
  778                selection_ranges
  779                    .iter()
  780                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  781            )
  782        });
  783        editor.fold_creases(
  784            vec![
  785                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  786                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  787            ],
  788            true,
  789            window,
  790            cx,
  791        );
  792    });
  793
  794    let cloned_editor = editor
  795        .update(cx, |editor, _, cx| {
  796            cx.open_window(Default::default(), |window, cx| {
  797                cx.new(|cx| editor.clone(window, cx))
  798            })
  799        })
  800        .unwrap()
  801        .unwrap();
  802
  803    let snapshot = editor
  804        .update(cx, |e, window, cx| e.snapshot(window, cx))
  805        .unwrap();
  806    let cloned_snapshot = cloned_editor
  807        .update(cx, |e, window, cx| e.snapshot(window, cx))
  808        .unwrap();
  809
  810    assert_eq!(
  811        cloned_editor
  812            .update(cx, |e, _, cx| e.display_text(cx))
  813            .unwrap(),
  814        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  815    );
  816    assert_eq!(
  817        cloned_snapshot
  818            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  819            .collect::<Vec<_>>(),
  820        snapshot
  821            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  822            .collect::<Vec<_>>(),
  823    );
  824    assert_set_eq!(
  825        cloned_editor
  826            .update(cx, |editor, _, cx| editor
  827                .selections
  828                .ranges::<Point>(&editor.display_snapshot(cx)))
  829            .unwrap(),
  830        editor
  831            .update(cx, |editor, _, cx| editor
  832                .selections
  833                .ranges(&editor.display_snapshot(cx)))
  834            .unwrap()
  835    );
  836    assert_set_eq!(
  837        cloned_editor
  838            .update(cx, |e, _window, cx| e
  839                .selections
  840                .display_ranges(&e.display_snapshot(cx)))
  841            .unwrap(),
  842        editor
  843            .update(cx, |e, _, cx| e
  844                .selections
  845                .display_ranges(&e.display_snapshot(cx)))
  846            .unwrap()
  847    );
  848}
  849
  850#[gpui::test]
  851async fn test_navigation_history(cx: &mut TestAppContext) {
  852    init_test(cx, |_| {});
  853
  854    use workspace::item::Item;
  855
  856    let fs = FakeFs::new(cx.executor());
  857    let project = Project::test(fs, [], cx).await;
  858    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  859    let pane = workspace
  860        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  861        .unwrap();
  862
  863    _ = workspace.update(cx, |_v, window, cx| {
  864        cx.new(|cx| {
  865            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  866            let mut editor = build_editor(buffer, window, cx);
  867            let handle = cx.entity();
  868            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  869
  870            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  871                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  872            }
  873
  874            // Move the cursor a small distance.
  875            // Nothing is added to the navigation history.
  876            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  877                s.select_display_ranges([
  878                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  879                ])
  880            });
  881            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  882                s.select_display_ranges([
  883                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  884                ])
  885            });
  886            assert!(pop_history(&mut editor, cx).is_none());
  887
  888            // Move the cursor a large distance.
  889            // The history can jump back to the previous position.
  890            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  891                s.select_display_ranges([
  892                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  893                ])
  894            });
  895            let nav_entry = pop_history(&mut editor, cx).unwrap();
  896            editor.navigate(nav_entry.data.unwrap(), window, cx);
  897            assert_eq!(nav_entry.item.id(), cx.entity_id());
  898            assert_eq!(
  899                editor
  900                    .selections
  901                    .display_ranges(&editor.display_snapshot(cx)),
  902                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  903            );
  904            assert!(pop_history(&mut editor, cx).is_none());
  905
  906            // Move the cursor a small distance via the mouse.
  907            // Nothing is added to the navigation history.
  908            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  909            editor.end_selection(window, cx);
  910            assert_eq!(
  911                editor
  912                    .selections
  913                    .display_ranges(&editor.display_snapshot(cx)),
  914                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  915            );
  916            assert!(pop_history(&mut editor, cx).is_none());
  917
  918            // Move the cursor a large distance via the mouse.
  919            // The history can jump back to the previous position.
  920            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  921            editor.end_selection(window, cx);
  922            assert_eq!(
  923                editor
  924                    .selections
  925                    .display_ranges(&editor.display_snapshot(cx)),
  926                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  927            );
  928            let nav_entry = pop_history(&mut editor, cx).unwrap();
  929            editor.navigate(nav_entry.data.unwrap(), window, cx);
  930            assert_eq!(nav_entry.item.id(), cx.entity_id());
  931            assert_eq!(
  932                editor
  933                    .selections
  934                    .display_ranges(&editor.display_snapshot(cx)),
  935                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  936            );
  937            assert!(pop_history(&mut editor, cx).is_none());
  938
  939            // Set scroll position to check later
  940            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  941            let original_scroll_position = editor.scroll_manager.anchor();
  942
  943            // Jump to the end of the document and adjust scroll
  944            editor.move_to_end(&MoveToEnd, window, cx);
  945            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  946            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  947
  948            let nav_entry = pop_history(&mut editor, cx).unwrap();
  949            editor.navigate(nav_entry.data.unwrap(), window, cx);
  950            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  951
  952            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  953            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  954            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  955            let invalid_point = Point::new(9999, 0);
  956            editor.navigate(
  957                Box::new(NavigationData {
  958                    cursor_anchor: invalid_anchor,
  959                    cursor_position: invalid_point,
  960                    scroll_anchor: ScrollAnchor {
  961                        anchor: invalid_anchor,
  962                        offset: Default::default(),
  963                    },
  964                    scroll_top_row: invalid_point.row,
  965                }),
  966                window,
  967                cx,
  968            );
  969            assert_eq!(
  970                editor
  971                    .selections
  972                    .display_ranges(&editor.display_snapshot(cx)),
  973                &[editor.max_point(cx)..editor.max_point(cx)]
  974            );
  975            assert_eq!(
  976                editor.scroll_position(cx),
  977                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  978            );
  979
  980            editor
  981        })
  982    });
  983}
  984
  985#[gpui::test]
  986fn test_cancel(cx: &mut TestAppContext) {
  987    init_test(cx, |_| {});
  988
  989    let editor = cx.add_window(|window, cx| {
  990        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  991        build_editor(buffer, window, cx)
  992    });
  993
  994    _ = editor.update(cx, |editor, window, cx| {
  995        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  996        editor.update_selection(
  997            DisplayPoint::new(DisplayRow(1), 1),
  998            0,
  999            gpui::Point::<f32>::default(),
 1000            window,
 1001            cx,
 1002        );
 1003        editor.end_selection(window, cx);
 1004
 1005        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1006        editor.update_selection(
 1007            DisplayPoint::new(DisplayRow(0), 3),
 1008            0,
 1009            gpui::Point::<f32>::default(),
 1010            window,
 1011            cx,
 1012        );
 1013        editor.end_selection(window, cx);
 1014        assert_eq!(
 1015            display_ranges(editor, cx),
 1016            [
 1017                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1018                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1019            ]
 1020        );
 1021    });
 1022
 1023    _ = editor.update(cx, |editor, window, cx| {
 1024        editor.cancel(&Cancel, window, cx);
 1025        assert_eq!(
 1026            display_ranges(editor, cx),
 1027            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1028        );
 1029    });
 1030
 1031    _ = editor.update(cx, |editor, window, cx| {
 1032        editor.cancel(&Cancel, window, cx);
 1033        assert_eq!(
 1034            display_ranges(editor, cx),
 1035            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1036        );
 1037    });
 1038}
 1039
 1040#[gpui::test]
 1041fn test_fold_action(cx: &mut TestAppContext) {
 1042    init_test(cx, |_| {});
 1043
 1044    let editor = cx.add_window(|window, cx| {
 1045        let buffer = MultiBuffer::build_simple(
 1046            &"
 1047                impl Foo {
 1048                    // Hello!
 1049
 1050                    fn a() {
 1051                        1
 1052                    }
 1053
 1054                    fn b() {
 1055                        2
 1056                    }
 1057
 1058                    fn c() {
 1059                        3
 1060                    }
 1061                }
 1062            "
 1063            .unindent(),
 1064            cx,
 1065        );
 1066        build_editor(buffer, window, cx)
 1067    });
 1068
 1069    _ = editor.update(cx, |editor, window, cx| {
 1070        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1071            s.select_display_ranges([
 1072                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1073            ]);
 1074        });
 1075        editor.fold(&Fold, window, cx);
 1076        assert_eq!(
 1077            editor.display_text(cx),
 1078            "
 1079                impl Foo {
 1080                    // Hello!
 1081
 1082                    fn a() {
 1083                        1
 1084                    }
 1085
 1086                    fn b() {⋯
 1087                    }
 1088
 1089                    fn c() {⋯
 1090                    }
 1091                }
 1092            "
 1093            .unindent(),
 1094        );
 1095
 1096        editor.fold(&Fold, window, cx);
 1097        assert_eq!(
 1098            editor.display_text(cx),
 1099            "
 1100                impl Foo {⋯
 1101                }
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.unfold_lines(&UnfoldLines, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                impl Foo {
 1111                    // Hello!
 1112
 1113                    fn a() {
 1114                        1
 1115                    }
 1116
 1117                    fn b() {⋯
 1118                    }
 1119
 1120                    fn c() {⋯
 1121                    }
 1122                }
 1123            "
 1124            .unindent(),
 1125        );
 1126
 1127        editor.unfold_lines(&UnfoldLines, window, cx);
 1128        assert_eq!(
 1129            editor.display_text(cx),
 1130            editor.buffer.read(cx).read(cx).text()
 1131        );
 1132    });
 1133}
 1134
 1135#[gpui::test]
 1136fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1137    init_test(cx, |_| {});
 1138
 1139    let editor = cx.add_window(|window, cx| {
 1140        let buffer = MultiBuffer::build_simple(
 1141            &"
 1142                class Foo:
 1143                    # Hello!
 1144
 1145                    def a():
 1146                        print(1)
 1147
 1148                    def b():
 1149                        print(2)
 1150
 1151                    def c():
 1152                        print(3)
 1153            "
 1154            .unindent(),
 1155            cx,
 1156        );
 1157        build_editor(buffer, window, cx)
 1158    });
 1159
 1160    _ = editor.update(cx, |editor, window, cx| {
 1161        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1162            s.select_display_ranges([
 1163                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1164            ]);
 1165        });
 1166        editor.fold(&Fold, window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():
 1174                        print(1)
 1175
 1176                    def b():⋯
 1177
 1178                    def c():⋯
 1179            "
 1180            .unindent(),
 1181        );
 1182
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:⋯
 1188            "
 1189            .unindent(),
 1190        );
 1191
 1192        editor.unfold_lines(&UnfoldLines, window, cx);
 1193        assert_eq!(
 1194            editor.display_text(cx),
 1195            "
 1196                class Foo:
 1197                    # Hello!
 1198
 1199                    def a():
 1200                        print(1)
 1201
 1202                    def b():⋯
 1203
 1204                    def c():⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            editor.buffer.read(cx).read(cx).text()
 1213        );
 1214    });
 1215}
 1216
 1217#[gpui::test]
 1218fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1219    init_test(cx, |_| {});
 1220
 1221    let editor = cx.add_window(|window, cx| {
 1222        let buffer = MultiBuffer::build_simple(
 1223            &"
 1224                class Foo:
 1225                    # Hello!
 1226
 1227                    def a():
 1228                        print(1)
 1229
 1230                    def b():
 1231                        print(2)
 1232
 1233
 1234                    def c():
 1235                        print(3)
 1236
 1237
 1238            "
 1239            .unindent(),
 1240            cx,
 1241        );
 1242        build_editor(buffer, window, cx)
 1243    });
 1244
 1245    _ = editor.update(cx, |editor, window, cx| {
 1246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1247            s.select_display_ranges([
 1248                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1249            ]);
 1250        });
 1251        editor.fold(&Fold, window, cx);
 1252        assert_eq!(
 1253            editor.display_text(cx),
 1254            "
 1255                class Foo:
 1256                    # Hello!
 1257
 1258                    def a():
 1259                        print(1)
 1260
 1261                    def b():⋯
 1262
 1263
 1264                    def c():⋯
 1265
 1266
 1267            "
 1268            .unindent(),
 1269        );
 1270
 1271        editor.fold(&Fold, window, cx);
 1272        assert_eq!(
 1273            editor.display_text(cx),
 1274            "
 1275                class Foo:⋯
 1276
 1277
 1278            "
 1279            .unindent(),
 1280        );
 1281
 1282        editor.unfold_lines(&UnfoldLines, window, cx);
 1283        assert_eq!(
 1284            editor.display_text(cx),
 1285            "
 1286                class Foo:
 1287                    # Hello!
 1288
 1289                    def a():
 1290                        print(1)
 1291
 1292                    def b():⋯
 1293
 1294
 1295                    def c():⋯
 1296
 1297
 1298            "
 1299            .unindent(),
 1300        );
 1301
 1302        editor.unfold_lines(&UnfoldLines, window, cx);
 1303        assert_eq!(
 1304            editor.display_text(cx),
 1305            editor.buffer.read(cx).read(cx).text()
 1306        );
 1307    });
 1308}
 1309
 1310#[gpui::test]
 1311fn test_fold_at_level(cx: &mut TestAppContext) {
 1312    init_test(cx, |_| {});
 1313
 1314    let editor = cx.add_window(|window, cx| {
 1315        let buffer = MultiBuffer::build_simple(
 1316            &"
 1317                class Foo:
 1318                    # Hello!
 1319
 1320                    def a():
 1321                        print(1)
 1322
 1323                    def b():
 1324                        print(2)
 1325
 1326
 1327                class Bar:
 1328                    # World!
 1329
 1330                    def a():
 1331                        print(1)
 1332
 1333                    def b():
 1334                        print(2)
 1335
 1336
 1337            "
 1338            .unindent(),
 1339            cx,
 1340        );
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1346        assert_eq!(
 1347            editor.display_text(cx),
 1348            "
 1349                class Foo:
 1350                    # Hello!
 1351
 1352                    def a():⋯
 1353
 1354                    def b():⋯
 1355
 1356
 1357                class Bar:
 1358                    # World!
 1359
 1360                    def a():⋯
 1361
 1362                    def b():⋯
 1363
 1364
 1365            "
 1366            .unindent(),
 1367        );
 1368
 1369        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1370        assert_eq!(
 1371            editor.display_text(cx),
 1372            "
 1373                class Foo:⋯
 1374
 1375
 1376                class Bar:⋯
 1377
 1378
 1379            "
 1380            .unindent(),
 1381        );
 1382
 1383        editor.unfold_all(&UnfoldAll, window, cx);
 1384        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1385        assert_eq!(
 1386            editor.display_text(cx),
 1387            "
 1388                class Foo:
 1389                    # Hello!
 1390
 1391                    def a():
 1392                        print(1)
 1393
 1394                    def b():
 1395                        print(2)
 1396
 1397
 1398                class Bar:
 1399                    # World!
 1400
 1401                    def a():
 1402                        print(1)
 1403
 1404                    def b():
 1405                        print(2)
 1406
 1407
 1408            "
 1409            .unindent(),
 1410        );
 1411
 1412        assert_eq!(
 1413            editor.display_text(cx),
 1414            editor.buffer.read(cx).read(cx).text()
 1415        );
 1416        let (_, positions) = marked_text_ranges(
 1417            &"
 1418                       class Foo:
 1419                           # Hello!
 1420
 1421                           def a():
 1422                              print(1)
 1423
 1424                           def b():
 1425                               p«riˇ»nt(2)
 1426
 1427
 1428                       class Bar:
 1429                           # World!
 1430
 1431                           def a():
 1432                               «ˇprint(1)
 1433
 1434                           def b():
 1435                               print(2)»
 1436
 1437
 1438                   "
 1439            .unindent(),
 1440            true,
 1441        );
 1442
 1443        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1444            s.select_ranges(
 1445                positions
 1446                    .iter()
 1447                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1448            )
 1449        });
 1450
 1451        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1452        assert_eq!(
 1453            editor.display_text(cx),
 1454            "
 1455                class Foo:
 1456                    # Hello!
 1457
 1458                    def a():⋯
 1459
 1460                    def b():
 1461                        print(2)
 1462
 1463
 1464                class Bar:
 1465                    # World!
 1466
 1467                    def a():
 1468                        print(1)
 1469
 1470                    def b():
 1471                        print(2)
 1472
 1473
 1474            "
 1475            .unindent(),
 1476        );
 1477    });
 1478}
 1479
 1480#[gpui::test]
 1481fn test_move_cursor(cx: &mut TestAppContext) {
 1482    init_test(cx, |_| {});
 1483
 1484    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1485    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1486
 1487    buffer.update(cx, |buffer, cx| {
 1488        buffer.edit(
 1489            vec![
 1490                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1491                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1492            ],
 1493            None,
 1494            cx,
 1495        );
 1496    });
 1497    _ = editor.update(cx, |editor, window, cx| {
 1498        assert_eq!(
 1499            display_ranges(editor, cx),
 1500            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            display_ranges(editor, cx),
 1506            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1507        );
 1508
 1509        editor.move_right(&MoveRight, window, cx);
 1510        assert_eq!(
 1511            display_ranges(editor, cx),
 1512            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1513        );
 1514
 1515        editor.move_left(&MoveLeft, window, cx);
 1516        assert_eq!(
 1517            display_ranges(editor, cx),
 1518            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            display_ranges(editor, cx),
 1524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1525        );
 1526
 1527        editor.move_to_end(&MoveToEnd, window, cx);
 1528        assert_eq!(
 1529            display_ranges(editor, cx),
 1530            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1531        );
 1532
 1533        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1534        assert_eq!(
 1535            display_ranges(editor, cx),
 1536            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1537        );
 1538
 1539        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1540            s.select_display_ranges([
 1541                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1542            ]);
 1543        });
 1544        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1548        );
 1549
 1550        editor.select_to_end(&SelectToEnd, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1554        );
 1555    });
 1556}
 1557
 1558#[gpui::test]
 1559fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1560    init_test(cx, |_| {});
 1561
 1562    let editor = cx.add_window(|window, cx| {
 1563        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1564        build_editor(buffer, window, cx)
 1565    });
 1566
 1567    assert_eq!('🟥'.len_utf8(), 4);
 1568    assert_eq!('α'.len_utf8(), 2);
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.fold_creases(
 1572            vec![
 1573                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1575                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1576            ],
 1577            true,
 1578            window,
 1579            cx,
 1580        );
 1581        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1582
 1583        editor.move_right(&MoveRight, window, cx);
 1584        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1585        editor.move_right(&MoveRight, window, cx);
 1586        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1587        editor.move_right(&MoveRight, window, cx);
 1588        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1589
 1590        editor.move_down(&MoveDown, window, cx);
 1591        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1592        editor.move_left(&MoveLeft, window, cx);
 1593        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1594        editor.move_left(&MoveLeft, window, cx);
 1595        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1596        editor.move_left(&MoveLeft, window, cx);
 1597        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1598
 1599        editor.move_down(&MoveDown, window, cx);
 1600        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1601        editor.move_right(&MoveRight, window, cx);
 1602        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1603        editor.move_right(&MoveRight, window, cx);
 1604        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1605        editor.move_right(&MoveRight, window, cx);
 1606        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1607
 1608        editor.move_up(&MoveUp, window, cx);
 1609        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1610        editor.move_down(&MoveDown, window, cx);
 1611        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1612        editor.move_up(&MoveUp, window, cx);
 1613        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1614
 1615        editor.move_up(&MoveUp, window, cx);
 1616        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1617        editor.move_left(&MoveLeft, window, cx);
 1618        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1619        editor.move_left(&MoveLeft, window, cx);
 1620        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1621    });
 1622}
 1623
 1624#[gpui::test]
 1625fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1626    init_test(cx, |_| {});
 1627
 1628    let editor = cx.add_window(|window, cx| {
 1629        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1630        build_editor(buffer, window, cx)
 1631    });
 1632    _ = editor.update(cx, |editor, window, cx| {
 1633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1634            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1635        });
 1636
 1637        // moving above start of document should move selection to start of document,
 1638        // but the next move down should still be at the original goal_x
 1639        editor.move_up(&MoveUp, window, cx);
 1640        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1641
 1642        editor.move_down(&MoveDown, window, cx);
 1643        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1644
 1645        editor.move_down(&MoveDown, window, cx);
 1646        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1647
 1648        editor.move_down(&MoveDown, window, cx);
 1649        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1650
 1651        editor.move_down(&MoveDown, window, cx);
 1652        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1653
 1654        // moving past end of document should not change goal_x
 1655        editor.move_down(&MoveDown, window, cx);
 1656        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1657
 1658        editor.move_down(&MoveDown, window, cx);
 1659        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1660
 1661        editor.move_up(&MoveUp, window, cx);
 1662        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1663
 1664        editor.move_up(&MoveUp, window, cx);
 1665        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1666
 1667        editor.move_up(&MoveUp, window, cx);
 1668        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1669    });
 1670}
 1671
 1672#[gpui::test]
 1673fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1674    init_test(cx, |_| {});
 1675    let move_to_beg = MoveToBeginningOfLine {
 1676        stop_at_soft_wraps: true,
 1677        stop_at_indent: true,
 1678    };
 1679
 1680    let delete_to_beg = DeleteToBeginningOfLine {
 1681        stop_at_indent: false,
 1682    };
 1683
 1684    let move_to_end = MoveToEndOfLine {
 1685        stop_at_soft_wraps: true,
 1686    };
 1687
 1688    let editor = cx.add_window(|window, cx| {
 1689        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1690        build_editor(buffer, window, cx)
 1691    });
 1692    _ = editor.update(cx, |editor, window, cx| {
 1693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1694            s.select_display_ranges([
 1695                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1696                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1697            ]);
 1698        });
 1699    });
 1700
 1701    _ = editor.update(cx, |editor, window, cx| {
 1702        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1703        assert_eq!(
 1704            display_ranges(editor, cx),
 1705            &[
 1706                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1707                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1708            ]
 1709        );
 1710    });
 1711
 1712    _ = editor.update(cx, |editor, window, cx| {
 1713        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1714        assert_eq!(
 1715            display_ranges(editor, cx),
 1716            &[
 1717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1719            ]
 1720        );
 1721    });
 1722
 1723    _ = editor.update(cx, |editor, window, cx| {
 1724        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1725        assert_eq!(
 1726            display_ranges(editor, cx),
 1727            &[
 1728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1729                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1730            ]
 1731        );
 1732    });
 1733
 1734    _ = editor.update(cx, |editor, window, cx| {
 1735        editor.move_to_end_of_line(&move_to_end, window, cx);
 1736        assert_eq!(
 1737            display_ranges(editor, cx),
 1738            &[
 1739                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1740                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1741            ]
 1742        );
 1743    });
 1744
 1745    // Moving to the end of line again is a no-op.
 1746    _ = editor.update(cx, |editor, window, cx| {
 1747        editor.move_to_end_of_line(&move_to_end, window, cx);
 1748        assert_eq!(
 1749            display_ranges(editor, cx),
 1750            &[
 1751                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1752                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1753            ]
 1754        );
 1755    });
 1756
 1757    _ = editor.update(cx, |editor, window, cx| {
 1758        editor.move_left(&MoveLeft, window, cx);
 1759        editor.select_to_beginning_of_line(
 1760            &SelectToBeginningOfLine {
 1761                stop_at_soft_wraps: true,
 1762                stop_at_indent: true,
 1763            },
 1764            window,
 1765            cx,
 1766        );
 1767        assert_eq!(
 1768            display_ranges(editor, cx),
 1769            &[
 1770                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1771                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1772            ]
 1773        );
 1774    });
 1775
 1776    _ = editor.update(cx, |editor, window, cx| {
 1777        editor.select_to_beginning_of_line(
 1778            &SelectToBeginningOfLine {
 1779                stop_at_soft_wraps: true,
 1780                stop_at_indent: true,
 1781            },
 1782            window,
 1783            cx,
 1784        );
 1785        assert_eq!(
 1786            display_ranges(editor, cx),
 1787            &[
 1788                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1789                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1790            ]
 1791        );
 1792    });
 1793
 1794    _ = editor.update(cx, |editor, window, cx| {
 1795        editor.select_to_beginning_of_line(
 1796            &SelectToBeginningOfLine {
 1797                stop_at_soft_wraps: true,
 1798                stop_at_indent: true,
 1799            },
 1800            window,
 1801            cx,
 1802        );
 1803        assert_eq!(
 1804            display_ranges(editor, cx),
 1805            &[
 1806                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1807                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1808            ]
 1809        );
 1810    });
 1811
 1812    _ = editor.update(cx, |editor, window, cx| {
 1813        editor.select_to_end_of_line(
 1814            &SelectToEndOfLine {
 1815                stop_at_soft_wraps: true,
 1816            },
 1817            window,
 1818            cx,
 1819        );
 1820        assert_eq!(
 1821            display_ranges(editor, cx),
 1822            &[
 1823                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1831        assert_eq!(editor.display_text(cx), "ab\n  de");
 1832        assert_eq!(
 1833            display_ranges(editor, cx),
 1834            &[
 1835                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]
 1838        );
 1839    });
 1840
 1841    _ = editor.update(cx, |editor, window, cx| {
 1842        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1843        assert_eq!(editor.display_text(cx), "\n");
 1844        assert_eq!(
 1845            display_ranges(editor, cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1849            ]
 1850        );
 1851    });
 1852}
 1853
 1854#[gpui::test]
 1855fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1856    init_test(cx, |_| {});
 1857    let move_to_beg = MoveToBeginningOfLine {
 1858        stop_at_soft_wraps: false,
 1859        stop_at_indent: false,
 1860    };
 1861
 1862    let move_to_end = MoveToEndOfLine {
 1863        stop_at_soft_wraps: false,
 1864    };
 1865
 1866    let editor = cx.add_window(|window, cx| {
 1867        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1868        build_editor(buffer, window, cx)
 1869    });
 1870
 1871    _ = editor.update(cx, |editor, window, cx| {
 1872        editor.set_wrap_width(Some(140.0.into()), cx);
 1873
 1874        // We expect the following lines after wrapping
 1875        // ```
 1876        // thequickbrownfox
 1877        // jumpedoverthelazydo
 1878        // gs
 1879        // ```
 1880        // The final `gs` was soft-wrapped onto a new line.
 1881        assert_eq!(
 1882            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1883            editor.display_text(cx),
 1884        );
 1885
 1886        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1887        // Start the cursor at the `k` on the first line
 1888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1889            s.select_display_ranges([
 1890                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1891            ]);
 1892        });
 1893
 1894        // Moving to the beginning of the line should put us at the beginning of the line.
 1895        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1896        assert_eq!(
 1897            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1898            display_ranges(editor, cx)
 1899        );
 1900
 1901        // Moving to the end of the line should put us at the end of the line.
 1902        editor.move_to_end_of_line(&move_to_end, window, cx);
 1903        assert_eq!(
 1904            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1905            display_ranges(editor, cx)
 1906        );
 1907
 1908        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1909        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1911            s.select_display_ranges([
 1912                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1913            ]);
 1914        });
 1915
 1916        // Moving to the beginning of the line should put us at the start of the second line of
 1917        // display text, i.e., the `j`.
 1918        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1919        assert_eq!(
 1920            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1921            display_ranges(editor, cx)
 1922        );
 1923
 1924        // Moving to the beginning of the line again should be a no-op.
 1925        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1926        assert_eq!(
 1927            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1928            display_ranges(editor, cx)
 1929        );
 1930
 1931        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1932        // next display line.
 1933        editor.move_to_end_of_line(&move_to_end, window, cx);
 1934        assert_eq!(
 1935            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1936            display_ranges(editor, cx)
 1937        );
 1938
 1939        // Moving to the end of the line again should be a no-op.
 1940        editor.move_to_end_of_line(&move_to_end, window, cx);
 1941        assert_eq!(
 1942            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1943            display_ranges(editor, cx)
 1944        );
 1945    });
 1946}
 1947
 1948#[gpui::test]
 1949fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1950    init_test(cx, |_| {});
 1951
 1952    let move_to_beg = MoveToBeginningOfLine {
 1953        stop_at_soft_wraps: true,
 1954        stop_at_indent: true,
 1955    };
 1956
 1957    let select_to_beg = SelectToBeginningOfLine {
 1958        stop_at_soft_wraps: true,
 1959        stop_at_indent: true,
 1960    };
 1961
 1962    let delete_to_beg = DeleteToBeginningOfLine {
 1963        stop_at_indent: true,
 1964    };
 1965
 1966    let move_to_end = MoveToEndOfLine {
 1967        stop_at_soft_wraps: false,
 1968    };
 1969
 1970    let editor = cx.add_window(|window, cx| {
 1971        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1972        build_editor(buffer, window, cx)
 1973    });
 1974
 1975    _ = editor.update(cx, |editor, window, cx| {
 1976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1977            s.select_display_ranges([
 1978                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1979                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1984        // and the second cursor at the first non-whitespace character in the line.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            display_ranges(editor, cx),
 1988            &[
 1989                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1990                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1991            ]
 1992        );
 1993
 1994        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1995        // and should move the second cursor to the beginning of the line.
 1996        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1997        assert_eq!(
 1998            display_ranges(editor, cx),
 1999            &[
 2000                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2001                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2002            ]
 2003        );
 2004
 2005        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2006        // and should move the second cursor back to the first non-whitespace character in the line.
 2007        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2008        assert_eq!(
 2009            display_ranges(editor, cx),
 2010            &[
 2011                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2012                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2013            ]
 2014        );
 2015
 2016        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2017        // and to the first non-whitespace character in the line for the second cursor.
 2018        editor.move_to_end_of_line(&move_to_end, window, cx);
 2019        editor.move_left(&MoveLeft, window, cx);
 2020        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2021        assert_eq!(
 2022            display_ranges(editor, cx),
 2023            &[
 2024                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2025                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2026            ]
 2027        );
 2028
 2029        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2030        // and should select to the beginning of the line for the second cursor.
 2031        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2032        assert_eq!(
 2033            display_ranges(editor, cx),
 2034            &[
 2035                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2036                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2037            ]
 2038        );
 2039
 2040        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2041        // and should delete to the first non-whitespace character in the line for the second cursor.
 2042        editor.move_to_end_of_line(&move_to_end, window, cx);
 2043        editor.move_left(&MoveLeft, window, cx);
 2044        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2045        assert_eq!(editor.text(cx), "c\n  f");
 2046    });
 2047}
 2048
 2049#[gpui::test]
 2050fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2051    init_test(cx, |_| {});
 2052
 2053    let move_to_beg = MoveToBeginningOfLine {
 2054        stop_at_soft_wraps: true,
 2055        stop_at_indent: true,
 2056    };
 2057
 2058    let editor = cx.add_window(|window, cx| {
 2059        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2060        build_editor(buffer, window, cx)
 2061    });
 2062
 2063    _ = editor.update(cx, |editor, window, cx| {
 2064        // test cursor between line_start and indent_start
 2065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2066            s.select_display_ranges([
 2067                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2068            ]);
 2069        });
 2070
 2071        // cursor should move to line_start
 2072        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2073        assert_eq!(
 2074            display_ranges(editor, cx),
 2075            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2076        );
 2077
 2078        // cursor should move to indent_start
 2079        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2080        assert_eq!(
 2081            display_ranges(editor, cx),
 2082            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2083        );
 2084
 2085        // cursor should move to back to line_start
 2086        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2087        assert_eq!(
 2088            display_ranges(editor, cx),
 2089            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2090        );
 2091    });
 2092}
 2093
 2094#[gpui::test]
 2095fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2096    init_test(cx, |_| {});
 2097
 2098    let editor = cx.add_window(|window, cx| {
 2099        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2100        build_editor(buffer, window, cx)
 2101    });
 2102    _ = editor.update(cx, |editor, window, cx| {
 2103        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2104            s.select_display_ranges([
 2105                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2106                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2107            ])
 2108        });
 2109        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2110        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2111
 2112        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2113        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2114
 2115        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2116        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2117
 2118        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2119        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2120
 2121        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2122        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2123
 2124        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2125        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2126
 2127        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2128        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2129
 2130        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2131        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2132
 2133        editor.move_right(&MoveRight, window, cx);
 2134        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2135        assert_selection_ranges(
 2136            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2137            editor,
 2138            cx,
 2139        );
 2140
 2141        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2142        assert_selection_ranges(
 2143            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2144            editor,
 2145            cx,
 2146        );
 2147
 2148        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2149        assert_selection_ranges(
 2150            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2151            editor,
 2152            cx,
 2153        );
 2154    });
 2155}
 2156
 2157#[gpui::test]
 2158fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2159    init_test(cx, |_| {});
 2160
 2161    let editor = cx.add_window(|window, cx| {
 2162        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2163        build_editor(buffer, window, cx)
 2164    });
 2165
 2166    _ = editor.update(cx, |editor, window, cx| {
 2167        editor.set_wrap_width(Some(140.0.into()), cx);
 2168        assert_eq!(
 2169            editor.display_text(cx),
 2170            "use one::{\n    two::three::\n    four::five\n};"
 2171        );
 2172
 2173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2174            s.select_display_ranges([
 2175                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2176            ]);
 2177        });
 2178
 2179        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2180        assert_eq!(
 2181            display_ranges(editor, cx),
 2182            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2183        );
 2184
 2185        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2186        assert_eq!(
 2187            display_ranges(editor, cx),
 2188            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2189        );
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_eq!(
 2193            display_ranges(editor, cx),
 2194            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2195        );
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_eq!(
 2199            display_ranges(editor, cx),
 2200            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2201        );
 2202
 2203        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2204        assert_eq!(
 2205            display_ranges(editor, cx),
 2206            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2207        );
 2208
 2209        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2210        assert_eq!(
 2211            display_ranges(editor, cx),
 2212            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2213        );
 2214    });
 2215}
 2216
 2217#[gpui::test]
 2218async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2219    init_test(cx, |_| {});
 2220    let mut cx = EditorTestContext::new(cx).await;
 2221
 2222    let line_height = cx.update_editor(|editor, window, cx| {
 2223        editor
 2224            .style(cx)
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.update_editor(|editor, window, cx| {
 2338        editor
 2339            .style(cx)
 2340            .text
 2341            .line_height_in_pixels(window.rem_size())
 2342    });
 2343    let window = cx.window;
 2344    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2345
 2346    cx.set_state(
 2347        r#"ˇone
 2348        two
 2349        three
 2350        four
 2351        five
 2352        six
 2353        seven
 2354        eight
 2355        nine
 2356        ten
 2357        "#,
 2358    );
 2359
 2360    cx.update_editor(|editor, window, cx| {
 2361        assert_eq!(
 2362            editor.snapshot(window, cx).scroll_position(),
 2363            gpui::Point::new(0., 0.)
 2364        );
 2365        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2366        assert_eq!(
 2367            editor.snapshot(window, cx).scroll_position(),
 2368            gpui::Point::new(0., 3.)
 2369        );
 2370        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2371        assert_eq!(
 2372            editor.snapshot(window, cx).scroll_position(),
 2373            gpui::Point::new(0., 6.)
 2374        );
 2375        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2376        assert_eq!(
 2377            editor.snapshot(window, cx).scroll_position(),
 2378            gpui::Point::new(0., 3.)
 2379        );
 2380
 2381        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2382        assert_eq!(
 2383            editor.snapshot(window, cx).scroll_position(),
 2384            gpui::Point::new(0., 1.)
 2385        );
 2386        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2387        assert_eq!(
 2388            editor.snapshot(window, cx).scroll_position(),
 2389            gpui::Point::new(0., 3.)
 2390        );
 2391    });
 2392}
 2393
 2394#[gpui::test]
 2395async fn test_autoscroll(cx: &mut TestAppContext) {
 2396    init_test(cx, |_| {});
 2397    let mut cx = EditorTestContext::new(cx).await;
 2398
 2399    let line_height = cx.update_editor(|editor, window, cx| {
 2400        editor.set_vertical_scroll_margin(2, cx);
 2401        editor
 2402            .style(cx)
 2403            .text
 2404            .line_height_in_pixels(window.rem_size())
 2405    });
 2406    let window = cx.window;
 2407    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2408
 2409    cx.set_state(
 2410        r#"ˇone
 2411            two
 2412            three
 2413            four
 2414            five
 2415            six
 2416            seven
 2417            eight
 2418            nine
 2419            ten
 2420        "#,
 2421    );
 2422    cx.update_editor(|editor, window, cx| {
 2423        assert_eq!(
 2424            editor.snapshot(window, cx).scroll_position(),
 2425            gpui::Point::new(0., 0.0)
 2426        );
 2427    });
 2428
 2429    // Add a cursor below the visible area. Since both cursors cannot fit
 2430    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2431    // allows the vertical scroll margin below that cursor.
 2432    cx.update_editor(|editor, window, cx| {
 2433        editor.change_selections(Default::default(), window, cx, |selections| {
 2434            selections.select_ranges([
 2435                Point::new(0, 0)..Point::new(0, 0),
 2436                Point::new(6, 0)..Point::new(6, 0),
 2437            ]);
 2438        })
 2439    });
 2440    cx.update_editor(|editor, window, cx| {
 2441        assert_eq!(
 2442            editor.snapshot(window, cx).scroll_position(),
 2443            gpui::Point::new(0., 3.0)
 2444        );
 2445    });
 2446
 2447    // Move down. The editor cursor scrolls down to track the newest cursor.
 2448    cx.update_editor(|editor, window, cx| {
 2449        editor.move_down(&Default::default(), window, cx);
 2450    });
 2451    cx.update_editor(|editor, window, cx| {
 2452        assert_eq!(
 2453            editor.snapshot(window, cx).scroll_position(),
 2454            gpui::Point::new(0., 4.0)
 2455        );
 2456    });
 2457
 2458    // Add a cursor above the visible area. Since both cursors fit on screen,
 2459    // the editor scrolls to show both.
 2460    cx.update_editor(|editor, window, cx| {
 2461        editor.change_selections(Default::default(), window, cx, |selections| {
 2462            selections.select_ranges([
 2463                Point::new(1, 0)..Point::new(1, 0),
 2464                Point::new(6, 0)..Point::new(6, 0),
 2465            ]);
 2466        })
 2467    });
 2468    cx.update_editor(|editor, window, cx| {
 2469        assert_eq!(
 2470            editor.snapshot(window, cx).scroll_position(),
 2471            gpui::Point::new(0., 1.0)
 2472        );
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479    let mut cx = EditorTestContext::new(cx).await;
 2480
 2481    let line_height = cx.update_editor(|editor, window, cx| {
 2482        editor
 2483            .style(cx)
 2484            .text
 2485            .line_height_in_pixels(window.rem_size())
 2486    });
 2487    let window = cx.window;
 2488    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2489    cx.set_state(
 2490        &r#"
 2491        ˇone
 2492        two
 2493        threeˇ
 2494        four
 2495        five
 2496        six
 2497        seven
 2498        eight
 2499        nine
 2500        ten
 2501        "#
 2502        .unindent(),
 2503    );
 2504
 2505    cx.update_editor(|editor, window, cx| {
 2506        editor.move_page_down(&MovePageDown::default(), window, cx)
 2507    });
 2508    cx.assert_editor_state(
 2509        &r#"
 2510        one
 2511        two
 2512        three
 2513        ˇfour
 2514        five
 2515        sixˇ
 2516        seven
 2517        eight
 2518        nine
 2519        ten
 2520        "#
 2521        .unindent(),
 2522    );
 2523
 2524    cx.update_editor(|editor, window, cx| {
 2525        editor.move_page_down(&MovePageDown::default(), window, cx)
 2526    });
 2527    cx.assert_editor_state(
 2528        &r#"
 2529        one
 2530        two
 2531        three
 2532        four
 2533        five
 2534        six
 2535        ˇseven
 2536        eight
 2537        nineˇ
 2538        ten
 2539        "#
 2540        .unindent(),
 2541    );
 2542
 2543    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2544    cx.assert_editor_state(
 2545        &r#"
 2546        one
 2547        two
 2548        three
 2549        ˇfour
 2550        five
 2551        sixˇ
 2552        seven
 2553        eight
 2554        nine
 2555        ten
 2556        "#
 2557        .unindent(),
 2558    );
 2559
 2560    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2561    cx.assert_editor_state(
 2562        &r#"
 2563        ˇone
 2564        two
 2565        threeˇ
 2566        four
 2567        five
 2568        six
 2569        seven
 2570        eight
 2571        nine
 2572        ten
 2573        "#
 2574        .unindent(),
 2575    );
 2576
 2577    // Test select collapsing
 2578    cx.update_editor(|editor, window, cx| {
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582    });
 2583    cx.assert_editor_state(
 2584        &r#"
 2585        one
 2586        two
 2587        three
 2588        four
 2589        five
 2590        six
 2591        seven
 2592        eight
 2593        nine
 2594        ˇten
 2595        ˇ"#
 2596        .unindent(),
 2597    );
 2598}
 2599
 2600#[gpui::test]
 2601async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2602    init_test(cx, |_| {});
 2603    let mut cx = EditorTestContext::new(cx).await;
 2604    cx.set_state("one «two threeˇ» four");
 2605    cx.update_editor(|editor, window, cx| {
 2606        editor.delete_to_beginning_of_line(
 2607            &DeleteToBeginningOfLine {
 2608                stop_at_indent: false,
 2609            },
 2610            window,
 2611            cx,
 2612        );
 2613        assert_eq!(editor.text(cx), " four");
 2614    });
 2615}
 2616
 2617#[gpui::test]
 2618async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2619    init_test(cx, |_| {});
 2620
 2621    let mut cx = EditorTestContext::new(cx).await;
 2622
 2623    // For an empty selection, the preceding word fragment is deleted.
 2624    // For non-empty selections, only selected characters are deleted.
 2625    cx.set_state("onˇe two t«hreˇ»e four");
 2626    cx.update_editor(|editor, window, cx| {
 2627        editor.delete_to_previous_word_start(
 2628            &DeleteToPreviousWordStart {
 2629                ignore_newlines: false,
 2630                ignore_brackets: false,
 2631            },
 2632            window,
 2633            cx,
 2634        );
 2635    });
 2636    cx.assert_editor_state("ˇe two tˇe four");
 2637
 2638    cx.set_state("e tˇwo te «fˇ»our");
 2639    cx.update_editor(|editor, window, cx| {
 2640        editor.delete_to_next_word_end(
 2641            &DeleteToNextWordEnd {
 2642                ignore_newlines: false,
 2643                ignore_brackets: false,
 2644            },
 2645            window,
 2646            cx,
 2647        );
 2648    });
 2649    cx.assert_editor_state("e tˇ te ˇour");
 2650}
 2651
 2652#[gpui::test]
 2653async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2654    init_test(cx, |_| {});
 2655
 2656    let mut cx = EditorTestContext::new(cx).await;
 2657
 2658    cx.set_state("here is some text    ˇwith a space");
 2659    cx.update_editor(|editor, window, cx| {
 2660        editor.delete_to_previous_word_start(
 2661            &DeleteToPreviousWordStart {
 2662                ignore_newlines: false,
 2663                ignore_brackets: true,
 2664            },
 2665            window,
 2666            cx,
 2667        );
 2668    });
 2669    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2670    cx.assert_editor_state("here is some textˇwith a space");
 2671
 2672    cx.set_state("here is some text    ˇwith a space");
 2673    cx.update_editor(|editor, window, cx| {
 2674        editor.delete_to_previous_word_start(
 2675            &DeleteToPreviousWordStart {
 2676                ignore_newlines: false,
 2677                ignore_brackets: false,
 2678            },
 2679            window,
 2680            cx,
 2681        );
 2682    });
 2683    cx.assert_editor_state("here is some textˇwith a space");
 2684
 2685    cx.set_state("here is some textˇ    with a space");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_next_word_end(
 2688            &DeleteToNextWordEnd {
 2689                ignore_newlines: false,
 2690                ignore_brackets: true,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    // Same happens in the other direction.
 2697    cx.assert_editor_state("here is some textˇwith a space");
 2698
 2699    cx.set_state("here is some textˇ    with a space");
 2700    cx.update_editor(|editor, window, cx| {
 2701        editor.delete_to_next_word_end(
 2702            &DeleteToNextWordEnd {
 2703                ignore_newlines: false,
 2704                ignore_brackets: false,
 2705            },
 2706            window,
 2707            cx,
 2708        );
 2709    });
 2710    cx.assert_editor_state("here is some textˇwith a space");
 2711
 2712    cx.set_state("here is some textˇ    with a space");
 2713    cx.update_editor(|editor, window, cx| {
 2714        editor.delete_to_next_word_end(
 2715            &DeleteToNextWordEnd {
 2716                ignore_newlines: true,
 2717                ignore_brackets: false,
 2718            },
 2719            window,
 2720            cx,
 2721        );
 2722    });
 2723    cx.assert_editor_state("here is some textˇwith a space");
 2724    cx.update_editor(|editor, window, cx| {
 2725        editor.delete_to_previous_word_start(
 2726            &DeleteToPreviousWordStart {
 2727                ignore_newlines: true,
 2728                ignore_brackets: false,
 2729            },
 2730            window,
 2731            cx,
 2732        );
 2733    });
 2734    cx.assert_editor_state("here is some ˇwith a space");
 2735    cx.update_editor(|editor, window, cx| {
 2736        editor.delete_to_previous_word_start(
 2737            &DeleteToPreviousWordStart {
 2738                ignore_newlines: true,
 2739                ignore_brackets: false,
 2740            },
 2741            window,
 2742            cx,
 2743        );
 2744    });
 2745    // Single whitespaces are removed with the word behind them.
 2746    cx.assert_editor_state("here is ˇwith a space");
 2747    cx.update_editor(|editor, window, cx| {
 2748        editor.delete_to_previous_word_start(
 2749            &DeleteToPreviousWordStart {
 2750                ignore_newlines: true,
 2751                ignore_brackets: false,
 2752            },
 2753            window,
 2754            cx,
 2755        );
 2756    });
 2757    cx.assert_editor_state("here ˇwith a space");
 2758    cx.update_editor(|editor, window, cx| {
 2759        editor.delete_to_previous_word_start(
 2760            &DeleteToPreviousWordStart {
 2761                ignore_newlines: true,
 2762                ignore_brackets: false,
 2763            },
 2764            window,
 2765            cx,
 2766        );
 2767    });
 2768    cx.assert_editor_state("ˇwith a space");
 2769    cx.update_editor(|editor, window, cx| {
 2770        editor.delete_to_previous_word_start(
 2771            &DeleteToPreviousWordStart {
 2772                ignore_newlines: true,
 2773                ignore_brackets: false,
 2774            },
 2775            window,
 2776            cx,
 2777        );
 2778    });
 2779    cx.assert_editor_state("ˇwith a space");
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_next_word_end(
 2782            &DeleteToNextWordEnd {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    // Same happens in the other direction.
 2791    cx.assert_editor_state("ˇ a space");
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_next_word_end(
 2794            &DeleteToNextWordEnd {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state("ˇ space");
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    cx.assert_editor_state("ˇ");
 2814    cx.update_editor(|editor, window, cx| {
 2815        editor.delete_to_next_word_end(
 2816            &DeleteToNextWordEnd {
 2817                ignore_newlines: true,
 2818                ignore_brackets: false,
 2819            },
 2820            window,
 2821            cx,
 2822        );
 2823    });
 2824    cx.assert_editor_state("ˇ");
 2825    cx.update_editor(|editor, window, cx| {
 2826        editor.delete_to_previous_word_start(
 2827            &DeleteToPreviousWordStart {
 2828                ignore_newlines: true,
 2829                ignore_brackets: false,
 2830            },
 2831            window,
 2832            cx,
 2833        );
 2834    });
 2835    cx.assert_editor_state("ˇ");
 2836}
 2837
 2838#[gpui::test]
 2839async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2840    init_test(cx, |_| {});
 2841
 2842    let language = Arc::new(
 2843        Language::new(
 2844            LanguageConfig {
 2845                brackets: BracketPairConfig {
 2846                    pairs: vec![
 2847                        BracketPair {
 2848                            start: "\"".to_string(),
 2849                            end: "\"".to_string(),
 2850                            close: true,
 2851                            surround: true,
 2852                            newline: false,
 2853                        },
 2854                        BracketPair {
 2855                            start: "(".to_string(),
 2856                            end: ")".to_string(),
 2857                            close: true,
 2858                            surround: true,
 2859                            newline: true,
 2860                        },
 2861                    ],
 2862                    ..BracketPairConfig::default()
 2863                },
 2864                ..LanguageConfig::default()
 2865            },
 2866            Some(tree_sitter_rust::LANGUAGE.into()),
 2867        )
 2868        .with_brackets_query(
 2869            r#"
 2870                ("(" @open ")" @close)
 2871                ("\"" @open "\"" @close)
 2872            "#,
 2873        )
 2874        .unwrap(),
 2875    );
 2876
 2877    let mut cx = EditorTestContext::new(cx).await;
 2878    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2879
 2880    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2881    cx.update_editor(|editor, window, cx| {
 2882        editor.delete_to_previous_word_start(
 2883            &DeleteToPreviousWordStart {
 2884                ignore_newlines: true,
 2885                ignore_brackets: false,
 2886            },
 2887            window,
 2888            cx,
 2889        );
 2890    });
 2891    // Deletion stops before brackets if asked to not ignore them.
 2892    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2893    cx.update_editor(|editor, window, cx| {
 2894        editor.delete_to_previous_word_start(
 2895            &DeleteToPreviousWordStart {
 2896                ignore_newlines: true,
 2897                ignore_brackets: false,
 2898            },
 2899            window,
 2900            cx,
 2901        );
 2902    });
 2903    // Deletion has to remove a single bracket and then stop again.
 2904    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2905
 2906    cx.update_editor(|editor, window, cx| {
 2907        editor.delete_to_previous_word_start(
 2908            &DeleteToPreviousWordStart {
 2909                ignore_newlines: true,
 2910                ignore_brackets: false,
 2911            },
 2912            window,
 2913            cx,
 2914        );
 2915    });
 2916    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2917
 2918    cx.update_editor(|editor, window, cx| {
 2919        editor.delete_to_previous_word_start(
 2920            &DeleteToPreviousWordStart {
 2921                ignore_newlines: true,
 2922                ignore_brackets: false,
 2923            },
 2924            window,
 2925            cx,
 2926        );
 2927    });
 2928    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2929
 2930    cx.update_editor(|editor, window, cx| {
 2931        editor.delete_to_previous_word_start(
 2932            &DeleteToPreviousWordStart {
 2933                ignore_newlines: true,
 2934                ignore_brackets: false,
 2935            },
 2936            window,
 2937            cx,
 2938        );
 2939    });
 2940    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2941
 2942    cx.update_editor(|editor, window, cx| {
 2943        editor.delete_to_next_word_end(
 2944            &DeleteToNextWordEnd {
 2945                ignore_newlines: true,
 2946                ignore_brackets: false,
 2947            },
 2948            window,
 2949            cx,
 2950        );
 2951    });
 2952    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2953    cx.assert_editor_state(r#"ˇ");"#);
 2954
 2955    cx.update_editor(|editor, window, cx| {
 2956        editor.delete_to_next_word_end(
 2957            &DeleteToNextWordEnd {
 2958                ignore_newlines: true,
 2959                ignore_brackets: false,
 2960            },
 2961            window,
 2962            cx,
 2963        );
 2964    });
 2965    cx.assert_editor_state(r#"ˇ"#);
 2966
 2967    cx.update_editor(|editor, window, cx| {
 2968        editor.delete_to_next_word_end(
 2969            &DeleteToNextWordEnd {
 2970                ignore_newlines: true,
 2971                ignore_brackets: false,
 2972            },
 2973            window,
 2974            cx,
 2975        );
 2976    });
 2977    cx.assert_editor_state(r#"ˇ"#);
 2978
 2979    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2980    cx.update_editor(|editor, window, cx| {
 2981        editor.delete_to_previous_word_start(
 2982            &DeleteToPreviousWordStart {
 2983                ignore_newlines: true,
 2984                ignore_brackets: true,
 2985            },
 2986            window,
 2987            cx,
 2988        );
 2989    });
 2990    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2991}
 2992
 2993#[gpui::test]
 2994fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2995    init_test(cx, |_| {});
 2996
 2997    let editor = cx.add_window(|window, cx| {
 2998        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2999        build_editor(buffer, window, cx)
 3000    });
 3001    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3002        ignore_newlines: false,
 3003        ignore_brackets: false,
 3004    };
 3005    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3006        ignore_newlines: true,
 3007        ignore_brackets: false,
 3008    };
 3009
 3010    _ = editor.update(cx, |editor, window, cx| {
 3011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3012            s.select_display_ranges([
 3013                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3014            ])
 3015        });
 3016        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3017        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3018        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3019        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3020        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3021        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3022        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3023        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3024        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3025        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3026        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3027        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3028    });
 3029}
 3030
 3031#[gpui::test]
 3032fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3033    init_test(cx, |_| {});
 3034
 3035    let editor = cx.add_window(|window, cx| {
 3036        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3037        build_editor(buffer, window, cx)
 3038    });
 3039    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3040        ignore_newlines: false,
 3041        ignore_brackets: false,
 3042    };
 3043    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 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(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3052            ])
 3053        });
 3054        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3055        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3056        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3057        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3058        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3059        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3060        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3061        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3062        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3063        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3064        editor.delete_to_previous_subword_start(
 3065            &del_to_prev_sub_word_start_ignore_newlines,
 3066            window,
 3067            cx,
 3068        );
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3070    });
 3071}
 3072
 3073#[gpui::test]
 3074fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3075    init_test(cx, |_| {});
 3076
 3077    let editor = cx.add_window(|window, cx| {
 3078        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3079        build_editor(buffer, window, cx)
 3080    });
 3081    let del_to_next_word_end = DeleteToNextWordEnd {
 3082        ignore_newlines: false,
 3083        ignore_brackets: false,
 3084    };
 3085    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3086        ignore_newlines: true,
 3087        ignore_brackets: false,
 3088    };
 3089
 3090    _ = editor.update(cx, |editor, window, cx| {
 3091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3092            s.select_display_ranges([
 3093                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3094            ])
 3095        });
 3096        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3097        assert_eq!(
 3098            editor.buffer.read(cx).read(cx).text(),
 3099            "one\n   two\nthree\n   four"
 3100        );
 3101        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3102        assert_eq!(
 3103            editor.buffer.read(cx).read(cx).text(),
 3104            "\n   two\nthree\n   four"
 3105        );
 3106        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3107        assert_eq!(
 3108            editor.buffer.read(cx).read(cx).text(),
 3109            "two\nthree\n   four"
 3110        );
 3111        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3112        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3113        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3114        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3115        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3116        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3117        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3118        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3119    });
 3120}
 3121
 3122#[gpui::test]
 3123fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3124    init_test(cx, |_| {});
 3125
 3126    let editor = cx.add_window(|window, cx| {
 3127        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3128        build_editor(buffer, window, cx)
 3129    });
 3130    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3131        ignore_newlines: false,
 3132        ignore_brackets: false,
 3133    };
 3134    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3135        ignore_newlines: true,
 3136        ignore_brackets: false,
 3137    };
 3138
 3139    _ = editor.update(cx, |editor, window, cx| {
 3140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3141            s.select_display_ranges([
 3142                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3143            ])
 3144        });
 3145        // Delete "\n" (empty line)
 3146        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3147        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3148        // Delete "foo" (subword boundary)
 3149        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3150        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3151        // Delete "Bar"
 3152        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3153        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3154        // Delete "\n   " (newline + leading whitespace)
 3155        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3156        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3157        // Delete "baz" (subword boundary)
 3158        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3159        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3160        // With ignore_newlines, delete "Qux"
 3161        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3162        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3163    });
 3164}
 3165
 3166#[gpui::test]
 3167fn test_newline(cx: &mut TestAppContext) {
 3168    init_test(cx, |_| {});
 3169
 3170    let editor = cx.add_window(|window, cx| {
 3171        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3172        build_editor(buffer, window, cx)
 3173    });
 3174
 3175    _ = editor.update(cx, |editor, window, cx| {
 3176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3177            s.select_display_ranges([
 3178                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3179                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3180                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3181            ])
 3182        });
 3183
 3184        editor.newline(&Newline, window, cx);
 3185        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3186    });
 3187}
 3188
 3189#[gpui::test]
 3190async fn test_newline_yaml(cx: &mut TestAppContext) {
 3191    init_test(cx, |_| {});
 3192
 3193    let mut cx = EditorTestContext::new(cx).await;
 3194    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3195    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3196
 3197    // Object (between 2 fields)
 3198    cx.set_state(indoc! {"
 3199    test:ˇ
 3200    hello: bye"});
 3201    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203    test:
 3204        ˇ
 3205    hello: bye"});
 3206
 3207    // Object (first and single line)
 3208    cx.set_state(indoc! {"
 3209    test:ˇ"});
 3210    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3211    cx.assert_editor_state(indoc! {"
 3212    test:
 3213        ˇ"});
 3214
 3215    // Array with objects (after first element)
 3216    cx.set_state(indoc! {"
 3217    test:
 3218        - foo: barˇ"});
 3219    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3220    cx.assert_editor_state(indoc! {"
 3221    test:
 3222        - foo: bar
 3223        ˇ"});
 3224
 3225    // Array with objects and comment
 3226    cx.set_state(indoc! {"
 3227    test:
 3228        - foo: bar
 3229        - bar: # testˇ"});
 3230    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3231    cx.assert_editor_state(indoc! {"
 3232    test:
 3233        - foo: bar
 3234        - bar: # test
 3235            ˇ"});
 3236
 3237    // Array with objects (after second element)
 3238    cx.set_state(indoc! {"
 3239    test:
 3240        - foo: bar
 3241        - bar: fooˇ"});
 3242    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243    cx.assert_editor_state(indoc! {"
 3244    test:
 3245        - foo: bar
 3246        - bar: foo
 3247        ˇ"});
 3248
 3249    // Array with strings (after first element)
 3250    cx.set_state(indoc! {"
 3251    test:
 3252        - fooˇ"});
 3253    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3254    cx.assert_editor_state(indoc! {"
 3255    test:
 3256        - foo
 3257        ˇ"});
 3258}
 3259
 3260#[gpui::test]
 3261fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3262    init_test(cx, |_| {});
 3263
 3264    let editor = cx.add_window(|window, cx| {
 3265        let buffer = MultiBuffer::build_simple(
 3266            "
 3267                a
 3268                b(
 3269                    X
 3270                )
 3271                c(
 3272                    X
 3273                )
 3274            "
 3275            .unindent()
 3276            .as_str(),
 3277            cx,
 3278        );
 3279        let mut editor = build_editor(buffer, window, cx);
 3280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3281            s.select_ranges([
 3282                Point::new(2, 4)..Point::new(2, 5),
 3283                Point::new(5, 4)..Point::new(5, 5),
 3284            ])
 3285        });
 3286        editor
 3287    });
 3288
 3289    _ = editor.update(cx, |editor, window, cx| {
 3290        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3291        editor.buffer.update(cx, |buffer, cx| {
 3292            buffer.edit(
 3293                [
 3294                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3295                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3296                ],
 3297                None,
 3298                cx,
 3299            );
 3300            assert_eq!(
 3301                buffer.read(cx).text(),
 3302                "
 3303                    a
 3304                    b()
 3305                    c()
 3306                "
 3307                .unindent()
 3308            );
 3309        });
 3310        assert_eq!(
 3311            editor.selections.ranges(&editor.display_snapshot(cx)),
 3312            &[
 3313                Point::new(1, 2)..Point::new(1, 2),
 3314                Point::new(2, 2)..Point::new(2, 2),
 3315            ],
 3316        );
 3317
 3318        editor.newline(&Newline, window, cx);
 3319        assert_eq!(
 3320            editor.text(cx),
 3321            "
 3322                a
 3323                b(
 3324                )
 3325                c(
 3326                )
 3327            "
 3328            .unindent()
 3329        );
 3330
 3331        // The selections are moved after the inserted newlines
 3332        assert_eq!(
 3333            editor.selections.ranges(&editor.display_snapshot(cx)),
 3334            &[
 3335                Point::new(2, 0)..Point::new(2, 0),
 3336                Point::new(4, 0)..Point::new(4, 0),
 3337            ],
 3338        );
 3339    });
 3340}
 3341
 3342#[gpui::test]
 3343async fn test_newline_above(cx: &mut TestAppContext) {
 3344    init_test(cx, |settings| {
 3345        settings.defaults.tab_size = NonZeroU32::new(4)
 3346    });
 3347
 3348    let language = Arc::new(
 3349        Language::new(
 3350            LanguageConfig::default(),
 3351            Some(tree_sitter_rust::LANGUAGE.into()),
 3352        )
 3353        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3354        .unwrap(),
 3355    );
 3356
 3357    let mut cx = EditorTestContext::new(cx).await;
 3358    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3359    cx.set_state(indoc! {"
 3360        const a: ˇA = (
 3361 3362                «const_functionˇ»(ˇ),
 3363                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3364 3365        ˇ);ˇ
 3366    "});
 3367
 3368    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3369    cx.assert_editor_state(indoc! {"
 3370        ˇ
 3371        const a: A = (
 3372            ˇ
 3373            (
 3374                ˇ
 3375                ˇ
 3376                const_function(),
 3377                ˇ
 3378                ˇ
 3379                ˇ
 3380                ˇ
 3381                something_else,
 3382                ˇ
 3383            )
 3384            ˇ
 3385            ˇ
 3386        );
 3387    "});
 3388}
 3389
 3390#[gpui::test]
 3391async fn test_newline_below(cx: &mut TestAppContext) {
 3392    init_test(cx, |settings| {
 3393        settings.defaults.tab_size = NonZeroU32::new(4)
 3394    });
 3395
 3396    let language = Arc::new(
 3397        Language::new(
 3398            LanguageConfig::default(),
 3399            Some(tree_sitter_rust::LANGUAGE.into()),
 3400        )
 3401        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3402        .unwrap(),
 3403    );
 3404
 3405    let mut cx = EditorTestContext::new(cx).await;
 3406    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3407    cx.set_state(indoc! {"
 3408        const a: ˇA = (
 3409 3410                «const_functionˇ»(ˇ),
 3411                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3412 3413        ˇ);ˇ
 3414    "});
 3415
 3416    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3417    cx.assert_editor_state(indoc! {"
 3418        const a: A = (
 3419            ˇ
 3420            (
 3421                ˇ
 3422                const_function(),
 3423                ˇ
 3424                ˇ
 3425                something_else,
 3426                ˇ
 3427                ˇ
 3428                ˇ
 3429                ˇ
 3430            )
 3431            ˇ
 3432        );
 3433        ˇ
 3434        ˇ
 3435    "});
 3436}
 3437
 3438#[gpui::test]
 3439async fn test_newline_comments(cx: &mut TestAppContext) {
 3440    init_test(cx, |settings| {
 3441        settings.defaults.tab_size = NonZeroU32::new(4)
 3442    });
 3443
 3444    let language = Arc::new(Language::new(
 3445        LanguageConfig {
 3446            line_comments: vec!["// ".into()],
 3447            ..LanguageConfig::default()
 3448        },
 3449        None,
 3450    ));
 3451    {
 3452        let mut cx = EditorTestContext::new(cx).await;
 3453        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3454        cx.set_state(indoc! {"
 3455        // Fooˇ
 3456    "});
 3457
 3458        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3459        cx.assert_editor_state(indoc! {"
 3460        // Foo
 3461        // ˇ
 3462    "});
 3463        // Ensure that we add comment prefix when existing line contains space
 3464        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3465        cx.assert_editor_state(
 3466            indoc! {"
 3467        // Foo
 3468        //s
 3469        // ˇ
 3470    "}
 3471            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3472            .as_str(),
 3473        );
 3474        // Ensure that we add comment prefix when existing line does not contain space
 3475        cx.set_state(indoc! {"
 3476        // Foo
 3477        //ˇ
 3478    "});
 3479        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3480        cx.assert_editor_state(indoc! {"
 3481        // Foo
 3482        //
 3483        // ˇ
 3484    "});
 3485        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3486        cx.set_state(indoc! {"
 3487        ˇ// Foo
 3488    "});
 3489        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3490        cx.assert_editor_state(indoc! {"
 3491
 3492        ˇ// Foo
 3493    "});
 3494    }
 3495    // Ensure that comment continuations can be disabled.
 3496    update_test_language_settings(cx, |settings| {
 3497        settings.defaults.extend_comment_on_newline = Some(false);
 3498    });
 3499    let mut cx = EditorTestContext::new(cx).await;
 3500    cx.set_state(indoc! {"
 3501        // Fooˇ
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        // Foo
 3506        ˇ
 3507    "});
 3508}
 3509
 3510#[gpui::test]
 3511async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3512    init_test(cx, |settings| {
 3513        settings.defaults.tab_size = NonZeroU32::new(4)
 3514    });
 3515
 3516    let language = Arc::new(Language::new(
 3517        LanguageConfig {
 3518            line_comments: vec!["// ".into(), "/// ".into()],
 3519            ..LanguageConfig::default()
 3520        },
 3521        None,
 3522    ));
 3523    {
 3524        let mut cx = EditorTestContext::new(cx).await;
 3525        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 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        cx.set_state(indoc! {"
 3536        ///ˇ
 3537    "});
 3538        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3539        cx.assert_editor_state(indoc! {"
 3540        ///
 3541        /// ˇ
 3542    "});
 3543    }
 3544}
 3545
 3546#[gpui::test]
 3547async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3548    init_test(cx, |settings| {
 3549        settings.defaults.tab_size = NonZeroU32::new(4)
 3550    });
 3551
 3552    let language = Arc::new(
 3553        Language::new(
 3554            LanguageConfig {
 3555                documentation_comment: Some(language::BlockCommentConfig {
 3556                    start: "/**".into(),
 3557                    end: "*/".into(),
 3558                    prefix: "* ".into(),
 3559                    tab_size: 1,
 3560                }),
 3561
 3562                ..LanguageConfig::default()
 3563            },
 3564            Some(tree_sitter_rust::LANGUAGE.into()),
 3565        )
 3566        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3567        .unwrap(),
 3568    );
 3569
 3570    {
 3571        let mut cx = EditorTestContext::new(cx).await;
 3572        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3573        cx.set_state(indoc! {"
 3574        /**ˇ
 3575    "});
 3576
 3577        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3578        cx.assert_editor_state(indoc! {"
 3579        /**
 3580         * ˇ
 3581    "});
 3582        // Ensure that if cursor is before the comment start,
 3583        // we do not actually insert a comment prefix.
 3584        cx.set_state(indoc! {"
 3585        ˇ/**
 3586    "});
 3587        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3588        cx.assert_editor_state(indoc! {"
 3589
 3590        ˇ/**
 3591    "});
 3592        // Ensure that if cursor is between it doesn't add comment prefix.
 3593        cx.set_state(indoc! {"
 3594        /*ˇ*
 3595    "});
 3596        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3597        cx.assert_editor_state(indoc! {"
 3598        /*
 3599        ˇ*
 3600    "});
 3601        // Ensure that if suffix exists on same line after cursor it adds new line.
 3602        cx.set_state(indoc! {"
 3603        /**ˇ*/
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /**
 3608         * ˇ
 3609         */
 3610    "});
 3611        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3612        cx.set_state(indoc! {"
 3613        /**ˇ */
 3614    "});
 3615        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3616        cx.assert_editor_state(indoc! {"
 3617        /**
 3618         * ˇ
 3619         */
 3620    "});
 3621        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3622        cx.set_state(indoc! {"
 3623        /** ˇ*/
 3624    "});
 3625        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3626        cx.assert_editor_state(
 3627            indoc! {"
 3628        /**s
 3629         * ˇ
 3630         */
 3631    "}
 3632            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3633            .as_str(),
 3634        );
 3635        // Ensure that delimiter space is preserved when newline on already
 3636        // spaced delimiter.
 3637        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3638        cx.assert_editor_state(
 3639            indoc! {"
 3640        /**s
 3641         *s
 3642         * ˇ
 3643         */
 3644    "}
 3645            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3646            .as_str(),
 3647        );
 3648        // Ensure that delimiter space is preserved when space is not
 3649        // on existing delimiter.
 3650        cx.set_state(indoc! {"
 3651        /**
 3652 3653         */
 3654    "});
 3655        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3656        cx.assert_editor_state(indoc! {"
 3657        /**
 3658         *
 3659         * ˇ
 3660         */
 3661    "});
 3662        // Ensure that if suffix exists on same line after cursor it
 3663        // doesn't add extra new line if prefix is not on same line.
 3664        cx.set_state(indoc! {"
 3665        /**
 3666        ˇ*/
 3667    "});
 3668        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3669        cx.assert_editor_state(indoc! {"
 3670        /**
 3671
 3672        ˇ*/
 3673    "});
 3674        // Ensure that it detects suffix after existing prefix.
 3675        cx.set_state(indoc! {"
 3676        /**ˇ/
 3677    "});
 3678        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3679        cx.assert_editor_state(indoc! {"
 3680        /**
 3681        ˇ/
 3682    "});
 3683        // Ensure that if suffix exists on same line before
 3684        // cursor it does not add comment prefix.
 3685        cx.set_state(indoc! {"
 3686        /** */ˇ
 3687    "});
 3688        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3689        cx.assert_editor_state(indoc! {"
 3690        /** */
 3691        ˇ
 3692    "});
 3693        // Ensure that if suffix exists on same line before
 3694        // cursor it does not add comment prefix.
 3695        cx.set_state(indoc! {"
 3696        /**
 3697         *
 3698         */ˇ
 3699    "});
 3700        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3701        cx.assert_editor_state(indoc! {"
 3702        /**
 3703         *
 3704         */
 3705         ˇ
 3706    "});
 3707
 3708        // Ensure that inline comment followed by code
 3709        // doesn't add comment prefix on newline
 3710        cx.set_state(indoc! {"
 3711        /** */ textˇ
 3712    "});
 3713        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3714        cx.assert_editor_state(indoc! {"
 3715        /** */ text
 3716        ˇ
 3717    "});
 3718
 3719        // Ensure that text after comment end tag
 3720        // doesn't add comment prefix on newline
 3721        cx.set_state(indoc! {"
 3722        /**
 3723         *
 3724         */ˇtext
 3725    "});
 3726        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3727        cx.assert_editor_state(indoc! {"
 3728        /**
 3729         *
 3730         */
 3731         ˇtext
 3732    "});
 3733
 3734        // Ensure if not comment block it doesn't
 3735        // add comment prefix on newline
 3736        cx.set_state(indoc! {"
 3737        * textˇ
 3738    "});
 3739        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3740        cx.assert_editor_state(indoc! {"
 3741        * text
 3742        ˇ
 3743    "});
 3744    }
 3745    // Ensure that comment continuations can be disabled.
 3746    update_test_language_settings(cx, |settings| {
 3747        settings.defaults.extend_comment_on_newline = Some(false);
 3748    });
 3749    let mut cx = EditorTestContext::new(cx).await;
 3750    cx.set_state(indoc! {"
 3751        /**ˇ
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        /**
 3756        ˇ
 3757    "});
 3758}
 3759
 3760#[gpui::test]
 3761async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3762    init_test(cx, |settings| {
 3763        settings.defaults.tab_size = NonZeroU32::new(4)
 3764    });
 3765
 3766    let lua_language = Arc::new(Language::new(
 3767        LanguageConfig {
 3768            line_comments: vec!["--".into()],
 3769            block_comment: Some(language::BlockCommentConfig {
 3770                start: "--[[".into(),
 3771                prefix: "".into(),
 3772                end: "]]".into(),
 3773                tab_size: 0,
 3774            }),
 3775            ..LanguageConfig::default()
 3776        },
 3777        None,
 3778    ));
 3779
 3780    let mut cx = EditorTestContext::new(cx).await;
 3781    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3782
 3783    // Line with line comment should extend
 3784    cx.set_state(indoc! {"
 3785        --ˇ
 3786    "});
 3787    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3788    cx.assert_editor_state(indoc! {"
 3789        --
 3790        --ˇ
 3791    "});
 3792
 3793    // Line with block comment that matches line comment should not extend
 3794    cx.set_state(indoc! {"
 3795        --[[ˇ
 3796    "});
 3797    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3798    cx.assert_editor_state(indoc! {"
 3799        --[[
 3800        ˇ
 3801    "});
 3802}
 3803
 3804#[gpui::test]
 3805fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3806    init_test(cx, |_| {});
 3807
 3808    let editor = cx.add_window(|window, cx| {
 3809        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3810        let mut editor = build_editor(buffer, window, cx);
 3811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3812            s.select_ranges([
 3813                MultiBufferOffset(3)..MultiBufferOffset(4),
 3814                MultiBufferOffset(11)..MultiBufferOffset(12),
 3815                MultiBufferOffset(19)..MultiBufferOffset(20),
 3816            ])
 3817        });
 3818        editor
 3819    });
 3820
 3821    _ = editor.update(cx, |editor, window, cx| {
 3822        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3823        editor.buffer.update(cx, |buffer, cx| {
 3824            buffer.edit(
 3825                [
 3826                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3827                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3828                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3829                ],
 3830                None,
 3831                cx,
 3832            );
 3833            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3834        });
 3835        assert_eq!(
 3836            editor.selections.ranges(&editor.display_snapshot(cx)),
 3837            &[
 3838                MultiBufferOffset(2)..MultiBufferOffset(2),
 3839                MultiBufferOffset(7)..MultiBufferOffset(7),
 3840                MultiBufferOffset(12)..MultiBufferOffset(12)
 3841            ],
 3842        );
 3843
 3844        editor.insert("Z", window, cx);
 3845        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3846
 3847        // The selections are moved after the inserted characters
 3848        assert_eq!(
 3849            editor.selections.ranges(&editor.display_snapshot(cx)),
 3850            &[
 3851                MultiBufferOffset(3)..MultiBufferOffset(3),
 3852                MultiBufferOffset(9)..MultiBufferOffset(9),
 3853                MultiBufferOffset(15)..MultiBufferOffset(15)
 3854            ],
 3855        );
 3856    });
 3857}
 3858
 3859#[gpui::test]
 3860async fn test_tab(cx: &mut TestAppContext) {
 3861    init_test(cx, |settings| {
 3862        settings.defaults.tab_size = NonZeroU32::new(3)
 3863    });
 3864
 3865    let mut cx = EditorTestContext::new(cx).await;
 3866    cx.set_state(indoc! {"
 3867        ˇabˇc
 3868        ˇ🏀ˇ🏀ˇefg
 3869 3870    "});
 3871    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3872    cx.assert_editor_state(indoc! {"
 3873           ˇab ˇc
 3874           ˇ🏀  ˇ🏀  ˇefg
 3875        d  ˇ
 3876    "});
 3877
 3878    cx.set_state(indoc! {"
 3879        a
 3880        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3881    "});
 3882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3883    cx.assert_editor_state(indoc! {"
 3884        a
 3885           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3886    "});
 3887}
 3888
 3889#[gpui::test]
 3890async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3891    init_test(cx, |_| {});
 3892
 3893    let mut cx = EditorTestContext::new(cx).await;
 3894    let language = Arc::new(
 3895        Language::new(
 3896            LanguageConfig::default(),
 3897            Some(tree_sitter_rust::LANGUAGE.into()),
 3898        )
 3899        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3900        .unwrap(),
 3901    );
 3902    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3903
 3904    // test when all cursors are not at suggested indent
 3905    // then simply move to their suggested indent location
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909        ˇ
 3910        ˇ    )
 3911        );
 3912    "});
 3913    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3914    cx.assert_editor_state(indoc! {"
 3915        const a: B = (
 3916            c(
 3917                ˇ
 3918            ˇ)
 3919        );
 3920    "});
 3921
 3922    // test cursor already at suggested indent not moving when
 3923    // other cursors are yet to reach their suggested indents
 3924    cx.set_state(indoc! {"
 3925        ˇ
 3926        const a: B = (
 3927            c(
 3928                d(
 3929        ˇ
 3930                )
 3931        ˇ
 3932        ˇ    )
 3933        );
 3934    "});
 3935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3936    cx.assert_editor_state(indoc! {"
 3937        ˇ
 3938        const a: B = (
 3939            c(
 3940                d(
 3941                    ˇ
 3942                )
 3943                ˇ
 3944            ˇ)
 3945        );
 3946    "});
 3947    // test when all cursors are at suggested indent then tab is inserted
 3948    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3949    cx.assert_editor_state(indoc! {"
 3950            ˇ
 3951        const a: B = (
 3952            c(
 3953                d(
 3954                        ˇ
 3955                )
 3956                    ˇ
 3957                ˇ)
 3958        );
 3959    "});
 3960
 3961    // test when current indent is less than suggested indent,
 3962    // we adjust line to match suggested indent and move cursor to it
 3963    //
 3964    // when no other cursor is at word boundary, all of them should move
 3965    cx.set_state(indoc! {"
 3966        const a: B = (
 3967            c(
 3968                d(
 3969        ˇ
 3970        ˇ   )
 3971        ˇ   )
 3972        );
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        const a: B = (
 3977            c(
 3978                d(
 3979                    ˇ
 3980                ˇ)
 3981            ˇ)
 3982        );
 3983    "});
 3984
 3985    // test when current indent is less than suggested indent,
 3986    // we adjust line to match suggested indent and move cursor to it
 3987    //
 3988    // when some other cursor is at word boundary, it should not move
 3989    cx.set_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                d(
 3993        ˇ
 3994        ˇ   )
 3995           ˇ)
 3996        );
 3997    "});
 3998    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3999    cx.assert_editor_state(indoc! {"
 4000        const a: B = (
 4001            c(
 4002                d(
 4003                    ˇ
 4004                ˇ)
 4005            ˇ)
 4006        );
 4007    "});
 4008
 4009    // test when current indent is more than suggested indent,
 4010    // we just move cursor to current indent instead of suggested indent
 4011    //
 4012    // when no other cursor is at word boundary, all of them should move
 4013    cx.set_state(indoc! {"
 4014        const a: B = (
 4015            c(
 4016                d(
 4017        ˇ
 4018        ˇ                )
 4019        ˇ   )
 4020        );
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        const a: B = (
 4025            c(
 4026                d(
 4027                    ˇ
 4028                        ˇ)
 4029            ˇ)
 4030        );
 4031    "});
 4032    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4033    cx.assert_editor_state(indoc! {"
 4034        const a: B = (
 4035            c(
 4036                d(
 4037                        ˇ
 4038                            ˇ)
 4039                ˇ)
 4040        );
 4041    "});
 4042
 4043    // test when current indent is more than suggested indent,
 4044    // we just move cursor to current indent instead of suggested indent
 4045    //
 4046    // when some other cursor is at word boundary, it doesn't move
 4047    cx.set_state(indoc! {"
 4048        const a: B = (
 4049            c(
 4050                d(
 4051        ˇ
 4052        ˇ                )
 4053            ˇ)
 4054        );
 4055    "});
 4056    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4057    cx.assert_editor_state(indoc! {"
 4058        const a: B = (
 4059            c(
 4060                d(
 4061                    ˇ
 4062                        ˇ)
 4063            ˇ)
 4064        );
 4065    "});
 4066
 4067    // handle auto-indent when there are multiple cursors on the same line
 4068    cx.set_state(indoc! {"
 4069        const a: B = (
 4070            c(
 4071        ˇ    ˇ
 4072        ˇ    )
 4073        );
 4074    "});
 4075    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4076    cx.assert_editor_state(indoc! {"
 4077        const a: B = (
 4078            c(
 4079                ˇ
 4080            ˇ)
 4081        );
 4082    "});
 4083}
 4084
 4085#[gpui::test]
 4086async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4087    init_test(cx, |settings| {
 4088        settings.defaults.tab_size = NonZeroU32::new(3)
 4089    });
 4090
 4091    let mut cx = EditorTestContext::new(cx).await;
 4092    cx.set_state(indoc! {"
 4093         ˇ
 4094        \t ˇ
 4095        \t  ˇ
 4096        \t   ˇ
 4097         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4098    "});
 4099
 4100    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4101    cx.assert_editor_state(indoc! {"
 4102           ˇ
 4103        \t   ˇ
 4104        \t   ˇ
 4105        \t      ˇ
 4106         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4107    "});
 4108}
 4109
 4110#[gpui::test]
 4111async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4112    init_test(cx, |settings| {
 4113        settings.defaults.tab_size = NonZeroU32::new(4)
 4114    });
 4115
 4116    let language = Arc::new(
 4117        Language::new(
 4118            LanguageConfig::default(),
 4119            Some(tree_sitter_rust::LANGUAGE.into()),
 4120        )
 4121        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4122        .unwrap(),
 4123    );
 4124
 4125    let mut cx = EditorTestContext::new(cx).await;
 4126    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4127    cx.set_state(indoc! {"
 4128        fn a() {
 4129            if b {
 4130        \t ˇc
 4131            }
 4132        }
 4133    "});
 4134
 4135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4136    cx.assert_editor_state(indoc! {"
 4137        fn a() {
 4138            if b {
 4139                ˇc
 4140            }
 4141        }
 4142    "});
 4143}
 4144
 4145#[gpui::test]
 4146async fn test_indent_outdent(cx: &mut TestAppContext) {
 4147    init_test(cx, |settings| {
 4148        settings.defaults.tab_size = NonZeroU32::new(4);
 4149    });
 4150
 4151    let mut cx = EditorTestContext::new(cx).await;
 4152
 4153    cx.set_state(indoc! {"
 4154          «oneˇ» «twoˇ»
 4155        three
 4156         four
 4157    "});
 4158    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4159    cx.assert_editor_state(indoc! {"
 4160            «oneˇ» «twoˇ»
 4161        three
 4162         four
 4163    "});
 4164
 4165    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4166    cx.assert_editor_state(indoc! {"
 4167        «oneˇ» «twoˇ»
 4168        three
 4169         four
 4170    "});
 4171
 4172    // select across line ending
 4173    cx.set_state(indoc! {"
 4174        one two
 4175        t«hree
 4176        ˇ» four
 4177    "});
 4178    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4179    cx.assert_editor_state(indoc! {"
 4180        one two
 4181            t«hree
 4182        ˇ» four
 4183    "});
 4184
 4185    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4186    cx.assert_editor_state(indoc! {"
 4187        one two
 4188        t«hree
 4189        ˇ» four
 4190    "});
 4191
 4192    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4193    cx.set_state(indoc! {"
 4194        one two
 4195        ˇthree
 4196            four
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        one two
 4201            ˇthree
 4202            four
 4203    "});
 4204
 4205    cx.set_state(indoc! {"
 4206        one two
 4207        ˇ    three
 4208            four
 4209    "});
 4210    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4211    cx.assert_editor_state(indoc! {"
 4212        one two
 4213        ˇthree
 4214            four
 4215    "});
 4216}
 4217
 4218#[gpui::test]
 4219async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4220    // This is a regression test for issue #33761
 4221    init_test(cx, |_| {});
 4222
 4223    let mut cx = EditorTestContext::new(cx).await;
 4224    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4225    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4226
 4227    cx.set_state(
 4228        r#"ˇ#     ingress:
 4229ˇ#         api:
 4230ˇ#             enabled: false
 4231ˇ#             pathType: Prefix
 4232ˇ#           console:
 4233ˇ#               enabled: false
 4234ˇ#               pathType: Prefix
 4235"#,
 4236    );
 4237
 4238    // Press tab to indent all lines
 4239    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4240
 4241    cx.assert_editor_state(
 4242        r#"    ˇ#     ingress:
 4243    ˇ#         api:
 4244    ˇ#             enabled: false
 4245    ˇ#             pathType: Prefix
 4246    ˇ#           console:
 4247    ˇ#               enabled: false
 4248    ˇ#               pathType: Prefix
 4249"#,
 4250    );
 4251}
 4252
 4253#[gpui::test]
 4254async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4255    // This is a test to make sure our fix for issue #33761 didn't break anything
 4256    init_test(cx, |_| {});
 4257
 4258    let mut cx = EditorTestContext::new(cx).await;
 4259    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4260    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4261
 4262    cx.set_state(
 4263        r#"ˇingress:
 4264ˇ  api:
 4265ˇ    enabled: false
 4266ˇ    pathType: Prefix
 4267"#,
 4268    );
 4269
 4270    // Press tab to indent all lines
 4271    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4272
 4273    cx.assert_editor_state(
 4274        r#"ˇingress:
 4275    ˇapi:
 4276        ˇenabled: false
 4277        ˇpathType: Prefix
 4278"#,
 4279    );
 4280}
 4281
 4282#[gpui::test]
 4283async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4284    init_test(cx, |settings| {
 4285        settings.defaults.hard_tabs = Some(true);
 4286    });
 4287
 4288    let mut cx = EditorTestContext::new(cx).await;
 4289
 4290    // select two ranges on one line
 4291    cx.set_state(indoc! {"
 4292        «oneˇ» «twoˇ»
 4293        three
 4294        four
 4295    "});
 4296    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4297    cx.assert_editor_state(indoc! {"
 4298        \t«oneˇ» «twoˇ»
 4299        three
 4300        four
 4301    "});
 4302    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4303    cx.assert_editor_state(indoc! {"
 4304        \t\t«oneˇ» «twoˇ»
 4305        three
 4306        four
 4307    "});
 4308    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4309    cx.assert_editor_state(indoc! {"
 4310        \t«oneˇ» «twoˇ»
 4311        three
 4312        four
 4313    "});
 4314    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4315    cx.assert_editor_state(indoc! {"
 4316        «oneˇ» «twoˇ»
 4317        three
 4318        four
 4319    "});
 4320
 4321    // select across a line ending
 4322    cx.set_state(indoc! {"
 4323        one two
 4324        t«hree
 4325        ˇ»four
 4326    "});
 4327    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4328    cx.assert_editor_state(indoc! {"
 4329        one two
 4330        \tt«hree
 4331        ˇ»four
 4332    "});
 4333    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4334    cx.assert_editor_state(indoc! {"
 4335        one two
 4336        \t\tt«hree
 4337        ˇ»four
 4338    "});
 4339    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4340    cx.assert_editor_state(indoc! {"
 4341        one two
 4342        \tt«hree
 4343        ˇ»four
 4344    "});
 4345    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4346    cx.assert_editor_state(indoc! {"
 4347        one two
 4348        t«hree
 4349        ˇ»four
 4350    "});
 4351
 4352    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4353    cx.set_state(indoc! {"
 4354        one two
 4355        ˇthree
 4356        four
 4357    "});
 4358    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4359    cx.assert_editor_state(indoc! {"
 4360        one two
 4361        ˇthree
 4362        four
 4363    "});
 4364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4365    cx.assert_editor_state(indoc! {"
 4366        one two
 4367        \tˇthree
 4368        four
 4369    "});
 4370    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4371    cx.assert_editor_state(indoc! {"
 4372        one two
 4373        ˇthree
 4374        four
 4375    "});
 4376}
 4377
 4378#[gpui::test]
 4379fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4380    init_test(cx, |settings| {
 4381        settings.languages.0.extend([
 4382            (
 4383                "TOML".into(),
 4384                LanguageSettingsContent {
 4385                    tab_size: NonZeroU32::new(2),
 4386                    ..Default::default()
 4387                },
 4388            ),
 4389            (
 4390                "Rust".into(),
 4391                LanguageSettingsContent {
 4392                    tab_size: NonZeroU32::new(4),
 4393                    ..Default::default()
 4394                },
 4395            ),
 4396        ]);
 4397    });
 4398
 4399    let toml_language = Arc::new(Language::new(
 4400        LanguageConfig {
 4401            name: "TOML".into(),
 4402            ..Default::default()
 4403        },
 4404        None,
 4405    ));
 4406    let rust_language = Arc::new(Language::new(
 4407        LanguageConfig {
 4408            name: "Rust".into(),
 4409            ..Default::default()
 4410        },
 4411        None,
 4412    ));
 4413
 4414    let toml_buffer =
 4415        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4416    let rust_buffer =
 4417        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4418    let multibuffer = cx.new(|cx| {
 4419        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4420        multibuffer.push_excerpts(
 4421            toml_buffer.clone(),
 4422            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4423            cx,
 4424        );
 4425        multibuffer.push_excerpts(
 4426            rust_buffer.clone(),
 4427            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4428            cx,
 4429        );
 4430        multibuffer
 4431    });
 4432
 4433    cx.add_window(|window, cx| {
 4434        let mut editor = build_editor(multibuffer, window, cx);
 4435
 4436        assert_eq!(
 4437            editor.text(cx),
 4438            indoc! {"
 4439                a = 1
 4440                b = 2
 4441
 4442                const c: usize = 3;
 4443            "}
 4444        );
 4445
 4446        select_ranges(
 4447            &mut editor,
 4448            indoc! {"
 4449                «aˇ» = 1
 4450                b = 2
 4451
 4452                «const c:ˇ» usize = 3;
 4453            "},
 4454            window,
 4455            cx,
 4456        );
 4457
 4458        editor.tab(&Tab, window, cx);
 4459        assert_text_with_selections(
 4460            &mut editor,
 4461            indoc! {"
 4462                  «aˇ» = 1
 4463                b = 2
 4464
 4465                    «const c:ˇ» usize = 3;
 4466            "},
 4467            cx,
 4468        );
 4469        editor.backtab(&Backtab, window, cx);
 4470        assert_text_with_selections(
 4471            &mut editor,
 4472            indoc! {"
 4473                «aˇ» = 1
 4474                b = 2
 4475
 4476                «const c:ˇ» usize = 3;
 4477            "},
 4478            cx,
 4479        );
 4480
 4481        editor
 4482    });
 4483}
 4484
 4485#[gpui::test]
 4486async fn test_backspace(cx: &mut TestAppContext) {
 4487    init_test(cx, |_| {});
 4488
 4489    let mut cx = EditorTestContext::new(cx).await;
 4490
 4491    // Basic backspace
 4492    cx.set_state(indoc! {"
 4493        onˇe two three
 4494        fou«rˇ» five six
 4495        seven «ˇeight nine
 4496        »ten
 4497    "});
 4498    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4499    cx.assert_editor_state(indoc! {"
 4500        oˇe two three
 4501        fouˇ five six
 4502        seven ˇten
 4503    "});
 4504
 4505    // Test backspace inside and around indents
 4506    cx.set_state(indoc! {"
 4507        zero
 4508            ˇone
 4509                ˇtwo
 4510            ˇ ˇ ˇ  three
 4511        ˇ  ˇ  four
 4512    "});
 4513    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4514    cx.assert_editor_state(indoc! {"
 4515        zero
 4516        ˇone
 4517            ˇtwo
 4518        ˇ  threeˇ  four
 4519    "});
 4520}
 4521
 4522#[gpui::test]
 4523async fn test_delete(cx: &mut TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    let mut cx = EditorTestContext::new(cx).await;
 4527    cx.set_state(indoc! {"
 4528        onˇe two three
 4529        fou«rˇ» five six
 4530        seven «ˇeight nine
 4531        »ten
 4532    "});
 4533    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4534    cx.assert_editor_state(indoc! {"
 4535        onˇ two three
 4536        fouˇ five six
 4537        seven ˇten
 4538    "});
 4539}
 4540
 4541#[gpui::test]
 4542fn test_delete_line(cx: &mut TestAppContext) {
 4543    init_test(cx, |_| {});
 4544
 4545    let editor = cx.add_window(|window, cx| {
 4546        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4547        build_editor(buffer, window, cx)
 4548    });
 4549    _ = editor.update(cx, |editor, window, cx| {
 4550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4551            s.select_display_ranges([
 4552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4553                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4554                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4555            ])
 4556        });
 4557        editor.delete_line(&DeleteLine, window, cx);
 4558        assert_eq!(editor.display_text(cx), "ghi");
 4559        assert_eq!(
 4560            display_ranges(editor, cx),
 4561            vec![
 4562                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4563                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4564            ]
 4565        );
 4566    });
 4567
 4568    let editor = cx.add_window(|window, cx| {
 4569        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4570        build_editor(buffer, window, cx)
 4571    });
 4572    _ = editor.update(cx, |editor, window, cx| {
 4573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4574            s.select_display_ranges([
 4575                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4576            ])
 4577        });
 4578        editor.delete_line(&DeleteLine, window, cx);
 4579        assert_eq!(editor.display_text(cx), "ghi\n");
 4580        assert_eq!(
 4581            display_ranges(editor, cx),
 4582            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4583        );
 4584    });
 4585
 4586    let editor = cx.add_window(|window, cx| {
 4587        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4588        build_editor(buffer, window, cx)
 4589    });
 4590    _ = editor.update(cx, |editor, window, cx| {
 4591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4592            s.select_display_ranges([
 4593                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4594            ])
 4595        });
 4596        editor.delete_line(&DeleteLine, window, cx);
 4597        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4598        assert_eq!(
 4599            display_ranges(editor, cx),
 4600            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4601        );
 4602    });
 4603}
 4604
 4605#[gpui::test]
 4606fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4607    init_test(cx, |_| {});
 4608
 4609    cx.add_window(|window, cx| {
 4610        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4611        let mut editor = build_editor(buffer.clone(), window, cx);
 4612        let buffer = buffer.read(cx).as_singleton().unwrap();
 4613
 4614        assert_eq!(
 4615            editor
 4616                .selections
 4617                .ranges::<Point>(&editor.display_snapshot(cx)),
 4618            &[Point::new(0, 0)..Point::new(0, 0)]
 4619        );
 4620
 4621        // When on single line, replace newline at end by space
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4624        assert_eq!(
 4625            editor
 4626                .selections
 4627                .ranges::<Point>(&editor.display_snapshot(cx)),
 4628            &[Point::new(0, 3)..Point::new(0, 3)]
 4629        );
 4630
 4631        // When multiple lines are selected, remove newlines that are spanned by the selection
 4632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4633            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4634        });
 4635        editor.join_lines(&JoinLines, window, cx);
 4636        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4637        assert_eq!(
 4638            editor
 4639                .selections
 4640                .ranges::<Point>(&editor.display_snapshot(cx)),
 4641            &[Point::new(0, 11)..Point::new(0, 11)]
 4642        );
 4643
 4644        // Undo should be transactional
 4645        editor.undo(&Undo, window, cx);
 4646        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4647        assert_eq!(
 4648            editor
 4649                .selections
 4650                .ranges::<Point>(&editor.display_snapshot(cx)),
 4651            &[Point::new(0, 5)..Point::new(2, 2)]
 4652        );
 4653
 4654        // When joining an empty line don't insert a space
 4655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4656            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4657        });
 4658        editor.join_lines(&JoinLines, window, cx);
 4659        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4660        assert_eq!(
 4661            editor
 4662                .selections
 4663                .ranges::<Point>(&editor.display_snapshot(cx)),
 4664            [Point::new(2, 3)..Point::new(2, 3)]
 4665        );
 4666
 4667        // We can remove trailing newlines
 4668        editor.join_lines(&JoinLines, window, cx);
 4669        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4670        assert_eq!(
 4671            editor
 4672                .selections
 4673                .ranges::<Point>(&editor.display_snapshot(cx)),
 4674            [Point::new(2, 3)..Point::new(2, 3)]
 4675        );
 4676
 4677        // We don't blow up on the last line
 4678        editor.join_lines(&JoinLines, window, cx);
 4679        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4680        assert_eq!(
 4681            editor
 4682                .selections
 4683                .ranges::<Point>(&editor.display_snapshot(cx)),
 4684            [Point::new(2, 3)..Point::new(2, 3)]
 4685        );
 4686
 4687        // reset to test indentation
 4688        editor.buffer.update(cx, |buffer, cx| {
 4689            buffer.edit(
 4690                [
 4691                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4692                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4693                ],
 4694                None,
 4695                cx,
 4696            )
 4697        });
 4698
 4699        // We remove any leading spaces
 4700        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4702            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4703        });
 4704        editor.join_lines(&JoinLines, window, cx);
 4705        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4706
 4707        // We don't insert a space for a line containing only spaces
 4708        editor.join_lines(&JoinLines, window, cx);
 4709        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4710
 4711        // We ignore any leading tabs
 4712        editor.join_lines(&JoinLines, window, cx);
 4713        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4714
 4715        editor
 4716    });
 4717}
 4718
 4719#[gpui::test]
 4720fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4721    init_test(cx, |_| {});
 4722
 4723    cx.add_window(|window, cx| {
 4724        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4725        let mut editor = build_editor(buffer.clone(), window, cx);
 4726        let buffer = buffer.read(cx).as_singleton().unwrap();
 4727
 4728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4729            s.select_ranges([
 4730                Point::new(0, 2)..Point::new(1, 1),
 4731                Point::new(1, 2)..Point::new(1, 2),
 4732                Point::new(3, 1)..Point::new(3, 2),
 4733            ])
 4734        });
 4735
 4736        editor.join_lines(&JoinLines, window, cx);
 4737        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4738
 4739        assert_eq!(
 4740            editor
 4741                .selections
 4742                .ranges::<Point>(&editor.display_snapshot(cx)),
 4743            [
 4744                Point::new(0, 7)..Point::new(0, 7),
 4745                Point::new(1, 3)..Point::new(1, 3)
 4746            ]
 4747        );
 4748        editor
 4749    });
 4750}
 4751
 4752#[gpui::test]
 4753async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4754    init_test(cx, |_| {});
 4755
 4756    let mut cx = EditorTestContext::new(cx).await;
 4757
 4758    let diff_base = r#"
 4759        Line 0
 4760        Line 1
 4761        Line 2
 4762        Line 3
 4763        "#
 4764    .unindent();
 4765
 4766    cx.set_state(
 4767        &r#"
 4768        ˇLine 0
 4769        Line 1
 4770        Line 2
 4771        Line 3
 4772        "#
 4773        .unindent(),
 4774    );
 4775
 4776    cx.set_head_text(&diff_base);
 4777    executor.run_until_parked();
 4778
 4779    // Join lines
 4780    cx.update_editor(|editor, window, cx| {
 4781        editor.join_lines(&JoinLines, window, cx);
 4782    });
 4783    executor.run_until_parked();
 4784
 4785    cx.assert_editor_state(
 4786        &r#"
 4787        Line 0ˇ Line 1
 4788        Line 2
 4789        Line 3
 4790        "#
 4791        .unindent(),
 4792    );
 4793    // Join again
 4794    cx.update_editor(|editor, window, cx| {
 4795        editor.join_lines(&JoinLines, window, cx);
 4796    });
 4797    executor.run_until_parked();
 4798
 4799    cx.assert_editor_state(
 4800        &r#"
 4801        Line 0 Line 1ˇ Line 2
 4802        Line 3
 4803        "#
 4804        .unindent(),
 4805    );
 4806}
 4807
 4808#[gpui::test]
 4809async fn test_custom_newlines_cause_no_false_positive_diffs(
 4810    executor: BackgroundExecutor,
 4811    cx: &mut TestAppContext,
 4812) {
 4813    init_test(cx, |_| {});
 4814    let mut cx = EditorTestContext::new(cx).await;
 4815    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4816    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4817    executor.run_until_parked();
 4818
 4819    cx.update_editor(|editor, window, cx| {
 4820        let snapshot = editor.snapshot(window, cx);
 4821        assert_eq!(
 4822            snapshot
 4823                .buffer_snapshot()
 4824                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4825                .collect::<Vec<_>>(),
 4826            Vec::new(),
 4827            "Should not have any diffs for files with custom newlines"
 4828        );
 4829    });
 4830}
 4831
 4832#[gpui::test]
 4833async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4834    init_test(cx, |_| {});
 4835
 4836    let mut cx = EditorTestContext::new(cx).await;
 4837
 4838    // Test sort_lines_case_insensitive()
 4839    cx.set_state(indoc! {"
 4840        «z
 4841        y
 4842        x
 4843        Z
 4844        Y
 4845        Xˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «x
 4852        X
 4853        y
 4854        Y
 4855        z
 4856        Zˇ»
 4857    "});
 4858
 4859    // Test sort_lines_by_length()
 4860    //
 4861    // Demonstrates:
 4862    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4863    // - sort is stable
 4864    cx.set_state(indoc! {"
 4865        «123
 4866        æ
 4867        12
 4868 4869        1
 4870        æˇ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «æ
 4875 4876        1
 4877        æ
 4878        12
 4879        123ˇ»
 4880    "});
 4881
 4882    // Test reverse_lines()
 4883    cx.set_state(indoc! {"
 4884        «5
 4885        4
 4886        3
 4887        2
 4888        1ˇ»
 4889    "});
 4890    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4891    cx.assert_editor_state(indoc! {"
 4892        «1
 4893        2
 4894        3
 4895        4
 4896        5ˇ»
 4897    "});
 4898
 4899    // Skip testing shuffle_line()
 4900
 4901    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4902    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4903
 4904    // Don't manipulate when cursor is on single line, but expand the selection
 4905    cx.set_state(indoc! {"
 4906        ddˇdd
 4907        ccc
 4908        bb
 4909        a
 4910    "});
 4911    cx.update_editor(|e, window, cx| {
 4912        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4913    });
 4914    cx.assert_editor_state(indoc! {"
 4915        «ddddˇ»
 4916        ccc
 4917        bb
 4918        a
 4919    "});
 4920
 4921    // Basic manipulate case
 4922    // Start selection moves to column 0
 4923    // End of selection shrinks to fit shorter line
 4924    cx.set_state(indoc! {"
 4925        dd«d
 4926        ccc
 4927        bb
 4928        aaaaaˇ»
 4929    "});
 4930    cx.update_editor(|e, window, cx| {
 4931        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4932    });
 4933    cx.assert_editor_state(indoc! {"
 4934        «aaaaa
 4935        bb
 4936        ccc
 4937        dddˇ»
 4938    "});
 4939
 4940    // Manipulate case with newlines
 4941    cx.set_state(indoc! {"
 4942        dd«d
 4943        ccc
 4944
 4945        bb
 4946        aaaaa
 4947
 4948        ˇ»
 4949    "});
 4950    cx.update_editor(|e, window, cx| {
 4951        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4952    });
 4953    cx.assert_editor_state(indoc! {"
 4954        «
 4955
 4956        aaaaa
 4957        bb
 4958        ccc
 4959        dddˇ»
 4960
 4961    "});
 4962
 4963    // Adding new line
 4964    cx.set_state(indoc! {"
 4965        aa«a
 4966        bbˇ»b
 4967    "});
 4968    cx.update_editor(|e, window, cx| {
 4969        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4970    });
 4971    cx.assert_editor_state(indoc! {"
 4972        «aaa
 4973        bbb
 4974        added_lineˇ»
 4975    "});
 4976
 4977    // Removing line
 4978    cx.set_state(indoc! {"
 4979        aa«a
 4980        bbbˇ»
 4981    "});
 4982    cx.update_editor(|e, window, cx| {
 4983        e.manipulate_immutable_lines(window, cx, |lines| {
 4984            lines.pop();
 4985        })
 4986    });
 4987    cx.assert_editor_state(indoc! {"
 4988        «aaaˇ»
 4989    "});
 4990
 4991    // Removing all lines
 4992    cx.set_state(indoc! {"
 4993        aa«a
 4994        bbbˇ»
 4995    "});
 4996    cx.update_editor(|e, window, cx| {
 4997        e.manipulate_immutable_lines(window, cx, |lines| {
 4998            lines.drain(..);
 4999        })
 5000    });
 5001    cx.assert_editor_state(indoc! {"
 5002        ˇ
 5003    "});
 5004}
 5005
 5006#[gpui::test]
 5007async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5008    init_test(cx, |_| {});
 5009
 5010    let mut cx = EditorTestContext::new(cx).await;
 5011
 5012    // Consider continuous selection as single selection
 5013    cx.set_state(indoc! {"
 5014        Aaa«aa
 5015        cˇ»c«c
 5016        bb
 5017        aaaˇ»aa
 5018    "});
 5019    cx.update_editor(|e, window, cx| {
 5020        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5021    });
 5022    cx.assert_editor_state(indoc! {"
 5023        «Aaaaa
 5024        ccc
 5025        bb
 5026        aaaaaˇ»
 5027    "});
 5028
 5029    cx.set_state(indoc! {"
 5030        Aaa«aa
 5031        cˇ»c«c
 5032        bb
 5033        aaaˇ»aa
 5034    "});
 5035    cx.update_editor(|e, window, cx| {
 5036        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5037    });
 5038    cx.assert_editor_state(indoc! {"
 5039        «Aaaaa
 5040        ccc
 5041        bbˇ»
 5042    "});
 5043
 5044    // Consider non continuous selection as distinct dedup operations
 5045    cx.set_state(indoc! {"
 5046        «aaaaa
 5047        bb
 5048        aaaaa
 5049        aaaaaˇ»
 5050
 5051        aaa«aaˇ»
 5052    "});
 5053    cx.update_editor(|e, window, cx| {
 5054        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5055    });
 5056    cx.assert_editor_state(indoc! {"
 5057        «aaaaa
 5058        bbˇ»
 5059
 5060        «aaaaaˇ»
 5061    "});
 5062}
 5063
 5064#[gpui::test]
 5065async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5066    init_test(cx, |_| {});
 5067
 5068    let mut cx = EditorTestContext::new(cx).await;
 5069
 5070    cx.set_state(indoc! {"
 5071        «Aaa
 5072        aAa
 5073        Aaaˇ»
 5074    "});
 5075    cx.update_editor(|e, window, cx| {
 5076        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5077    });
 5078    cx.assert_editor_state(indoc! {"
 5079        «Aaa
 5080        aAaˇ»
 5081    "});
 5082
 5083    cx.set_state(indoc! {"
 5084        «Aaa
 5085        aAa
 5086        aaAˇ»
 5087    "});
 5088    cx.update_editor(|e, window, cx| {
 5089        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5090    });
 5091    cx.assert_editor_state(indoc! {"
 5092        «Aaaˇ»
 5093    "});
 5094}
 5095
 5096#[gpui::test]
 5097async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5098    init_test(cx, |_| {});
 5099
 5100    let mut cx = EditorTestContext::new(cx).await;
 5101
 5102    let js_language = Arc::new(Language::new(
 5103        LanguageConfig {
 5104            name: "JavaScript".into(),
 5105            wrap_characters: Some(language::WrapCharactersConfig {
 5106                start_prefix: "<".into(),
 5107                start_suffix: ">".into(),
 5108                end_prefix: "</".into(),
 5109                end_suffix: ">".into(),
 5110            }),
 5111            ..LanguageConfig::default()
 5112        },
 5113        None,
 5114    ));
 5115
 5116    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5117
 5118    cx.set_state(indoc! {"
 5119        «testˇ»
 5120    "});
 5121    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5122    cx.assert_editor_state(indoc! {"
 5123        <«ˇ»>test</«ˇ»>
 5124    "});
 5125
 5126    cx.set_state(indoc! {"
 5127        «test
 5128         testˇ»
 5129    "});
 5130    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5131    cx.assert_editor_state(indoc! {"
 5132        <«ˇ»>test
 5133         test</«ˇ»>
 5134    "});
 5135
 5136    cx.set_state(indoc! {"
 5137        teˇst
 5138    "});
 5139    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5140    cx.assert_editor_state(indoc! {"
 5141        te<«ˇ»></«ˇ»>st
 5142    "});
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5147    init_test(cx, |_| {});
 5148
 5149    let mut cx = EditorTestContext::new(cx).await;
 5150
 5151    let js_language = Arc::new(Language::new(
 5152        LanguageConfig {
 5153            name: "JavaScript".into(),
 5154            wrap_characters: Some(language::WrapCharactersConfig {
 5155                start_prefix: "<".into(),
 5156                start_suffix: ">".into(),
 5157                end_prefix: "</".into(),
 5158                end_suffix: ">".into(),
 5159            }),
 5160            ..LanguageConfig::default()
 5161        },
 5162        None,
 5163    ));
 5164
 5165    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5166
 5167    cx.set_state(indoc! {"
 5168        «testˇ»
 5169        «testˇ» «testˇ»
 5170        «testˇ»
 5171    "});
 5172    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5173    cx.assert_editor_state(indoc! {"
 5174        <«ˇ»>test</«ˇ»>
 5175        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5176        <«ˇ»>test</«ˇ»>
 5177    "});
 5178
 5179    cx.set_state(indoc! {"
 5180        «test
 5181         testˇ»
 5182        «test
 5183         testˇ»
 5184    "});
 5185    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5186    cx.assert_editor_state(indoc! {"
 5187        <«ˇ»>test
 5188         test</«ˇ»>
 5189        <«ˇ»>test
 5190         test</«ˇ»>
 5191    "});
 5192}
 5193
 5194#[gpui::test]
 5195async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5196    init_test(cx, |_| {});
 5197
 5198    let mut cx = EditorTestContext::new(cx).await;
 5199
 5200    let plaintext_language = Arc::new(Language::new(
 5201        LanguageConfig {
 5202            name: "Plain Text".into(),
 5203            ..LanguageConfig::default()
 5204        },
 5205        None,
 5206    ));
 5207
 5208    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5209
 5210    cx.set_state(indoc! {"
 5211        «testˇ»
 5212    "});
 5213    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5214    cx.assert_editor_state(indoc! {"
 5215      «testˇ»
 5216    "});
 5217}
 5218
 5219#[gpui::test]
 5220async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5221    init_test(cx, |_| {});
 5222
 5223    let mut cx = EditorTestContext::new(cx).await;
 5224
 5225    // Manipulate with multiple selections on a single line
 5226    cx.set_state(indoc! {"
 5227        dd«dd
 5228        cˇ»c«c
 5229        bb
 5230        aaaˇ»aa
 5231    "});
 5232    cx.update_editor(|e, window, cx| {
 5233        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5234    });
 5235    cx.assert_editor_state(indoc! {"
 5236        «aaaaa
 5237        bb
 5238        ccc
 5239        ddddˇ»
 5240    "});
 5241
 5242    // Manipulate with multiple disjoin selections
 5243    cx.set_state(indoc! {"
 5244 5245        4
 5246        3
 5247        2
 5248        1ˇ»
 5249
 5250        dd«dd
 5251        ccc
 5252        bb
 5253        aaaˇ»aa
 5254    "});
 5255    cx.update_editor(|e, window, cx| {
 5256        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5257    });
 5258    cx.assert_editor_state(indoc! {"
 5259        «1
 5260        2
 5261        3
 5262        4
 5263        5ˇ»
 5264
 5265        «aaaaa
 5266        bb
 5267        ccc
 5268        ddddˇ»
 5269    "});
 5270
 5271    // Adding lines on each selection
 5272    cx.set_state(indoc! {"
 5273 5274        1ˇ»
 5275
 5276        bb«bb
 5277        aaaˇ»aa
 5278    "});
 5279    cx.update_editor(|e, window, cx| {
 5280        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5281    });
 5282    cx.assert_editor_state(indoc! {"
 5283        «2
 5284        1
 5285        added lineˇ»
 5286
 5287        «bbbb
 5288        aaaaa
 5289        added lineˇ»
 5290    "});
 5291
 5292    // Removing lines on each selection
 5293    cx.set_state(indoc! {"
 5294 5295        1ˇ»
 5296
 5297        bb«bb
 5298        aaaˇ»aa
 5299    "});
 5300    cx.update_editor(|e, window, cx| {
 5301        e.manipulate_immutable_lines(window, cx, |lines| {
 5302            lines.pop();
 5303        })
 5304    });
 5305    cx.assert_editor_state(indoc! {"
 5306        «2ˇ»
 5307
 5308        «bbbbˇ»
 5309    "});
 5310}
 5311
 5312#[gpui::test]
 5313async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5314    init_test(cx, |settings| {
 5315        settings.defaults.tab_size = NonZeroU32::new(3)
 5316    });
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319
 5320    // MULTI SELECTION
 5321    // Ln.1 "«" tests empty lines
 5322    // Ln.9 tests just leading whitespace
 5323    cx.set_state(indoc! {"
 5324        «
 5325        abc                 // No indentationˇ»
 5326        «\tabc              // 1 tabˇ»
 5327        \t\tabc «      ˇ»   // 2 tabs
 5328        \t ab«c             // Tab followed by space
 5329         \tabc              // Space followed by tab (3 spaces should be the result)
 5330        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5331           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5332        \t
 5333        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5334    "});
 5335    cx.update_editor(|e, window, cx| {
 5336        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5337    });
 5338    cx.assert_editor_state(
 5339        indoc! {"
 5340            «
 5341            abc                 // No indentation
 5342               abc              // 1 tab
 5343                  abc          // 2 tabs
 5344                abc             // Tab followed by space
 5345               abc              // Space followed by tab (3 spaces should be the result)
 5346                           abc   // Mixed indentation (tab conversion depends on the column)
 5347               abc         // Already space indented
 5348               ·
 5349               abc\tdef          // Only the leading tab is manipulatedˇ»
 5350        "}
 5351        .replace("·", "")
 5352        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5353    );
 5354
 5355    // Test on just a few lines, the others should remain unchanged
 5356    // Only lines (3, 5, 10, 11) should change
 5357    cx.set_state(
 5358        indoc! {"
 5359            ·
 5360            abc                 // No indentation
 5361            \tabcˇ               // 1 tab
 5362            \t\tabc             // 2 tabs
 5363            \t abcˇ              // Tab followed by space
 5364             \tabc              // Space followed by tab (3 spaces should be the result)
 5365            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5366               abc              // Already space indented
 5367            «\t
 5368            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5369        "}
 5370        .replace("·", "")
 5371        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5372    );
 5373    cx.update_editor(|e, window, cx| {
 5374        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5375    });
 5376    cx.assert_editor_state(
 5377        indoc! {"
 5378            ·
 5379            abc                 // No indentation
 5380            «   abc               // 1 tabˇ»
 5381            \t\tabc             // 2 tabs
 5382            «    abc              // Tab followed by spaceˇ»
 5383             \tabc              // Space followed by tab (3 spaces should be the result)
 5384            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5385               abc              // Already space indented
 5386            «   ·
 5387               abc\tdef          // Only the leading tab is manipulatedˇ»
 5388        "}
 5389        .replace("·", "")
 5390        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5391    );
 5392
 5393    // SINGLE SELECTION
 5394    // Ln.1 "«" tests empty lines
 5395    // Ln.9 tests just leading whitespace
 5396    cx.set_state(indoc! {"
 5397        «
 5398        abc                 // No indentation
 5399        \tabc               // 1 tab
 5400        \t\tabc             // 2 tabs
 5401        \t abc              // Tab followed by space
 5402         \tabc              // Space followed by tab (3 spaces should be the result)
 5403        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5404           abc              // Already space indented
 5405        \t
 5406        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5407    "});
 5408    cx.update_editor(|e, window, cx| {
 5409        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5410    });
 5411    cx.assert_editor_state(
 5412        indoc! {"
 5413            «
 5414            abc                 // No indentation
 5415               abc               // 1 tab
 5416                  abc             // 2 tabs
 5417                abc              // Tab followed by space
 5418               abc              // Space followed by tab (3 spaces should be the result)
 5419                           abc   // Mixed indentation (tab conversion depends on the column)
 5420               abc              // Already space indented
 5421               ·
 5422               abc\tdef          // Only the leading tab is manipulatedˇ»
 5423        "}
 5424        .replace("·", "")
 5425        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5426    );
 5427}
 5428
 5429#[gpui::test]
 5430async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5431    init_test(cx, |settings| {
 5432        settings.defaults.tab_size = NonZeroU32::new(3)
 5433    });
 5434
 5435    let mut cx = EditorTestContext::new(cx).await;
 5436
 5437    // MULTI SELECTION
 5438    // Ln.1 "«" tests empty lines
 5439    // Ln.11 tests just leading whitespace
 5440    cx.set_state(indoc! {"
 5441        «
 5442        abˇ»ˇc                 // No indentation
 5443         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5444          abc  «             // 2 spaces (< 3 so dont convert)
 5445           abc              // 3 spaces (convert)
 5446             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5447        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5448        «\t abc              // Tab followed by space
 5449         \tabc              // Space followed by tab (should be consumed due to tab)
 5450        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5451           \tˇ»  «\t
 5452           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5453    "});
 5454    cx.update_editor(|e, window, cx| {
 5455        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5456    });
 5457    cx.assert_editor_state(indoc! {"
 5458        «
 5459        abc                 // No indentation
 5460         abc                // 1 space (< 3 so dont convert)
 5461          abc               // 2 spaces (< 3 so dont convert)
 5462        \tabc              // 3 spaces (convert)
 5463        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5464        \t\t\tabc           // Already tab indented
 5465        \t abc              // Tab followed by space
 5466        \tabc              // Space followed by tab (should be consumed due to tab)
 5467        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5468        \t\t\t
 5469        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5470    "});
 5471
 5472    // Test on just a few lines, the other should remain unchanged
 5473    // Only lines (4, 8, 11, 12) should change
 5474    cx.set_state(
 5475        indoc! {"
 5476            ·
 5477            abc                 // No indentation
 5478             abc                // 1 space (< 3 so dont convert)
 5479              abc               // 2 spaces (< 3 so dont convert)
 5480            «   abc              // 3 spaces (convert)ˇ»
 5481                 abc            // 5 spaces (1 tab + 2 spaces)
 5482            \t\t\tabc           // Already tab indented
 5483            \t abc              // Tab followed by space
 5484             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5485               \t\t  \tabc      // Mixed indentation
 5486            \t \t  \t   \tabc   // Mixed indentation
 5487               \t  \tˇ
 5488            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5489        "}
 5490        .replace("·", "")
 5491        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5492    );
 5493    cx.update_editor(|e, window, cx| {
 5494        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5495    });
 5496    cx.assert_editor_state(
 5497        indoc! {"
 5498            ·
 5499            abc                 // No indentation
 5500             abc                // 1 space (< 3 so dont convert)
 5501              abc               // 2 spaces (< 3 so dont convert)
 5502            «\tabc              // 3 spaces (convert)ˇ»
 5503                 abc            // 5 spaces (1 tab + 2 spaces)
 5504            \t\t\tabc           // Already tab indented
 5505            \t abc              // Tab followed by space
 5506            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5507               \t\t  \tabc      // Mixed indentation
 5508            \t \t  \t   \tabc   // Mixed indentation
 5509            «\t\t\t
 5510            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5511        "}
 5512        .replace("·", "")
 5513        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5514    );
 5515
 5516    // SINGLE SELECTION
 5517    // Ln.1 "«" tests empty lines
 5518    // Ln.11 tests just leading whitespace
 5519    cx.set_state(indoc! {"
 5520        «
 5521        abc                 // No indentation
 5522         abc                // 1 space (< 3 so dont convert)
 5523          abc               // 2 spaces (< 3 so dont convert)
 5524           abc              // 3 spaces (convert)
 5525             abc            // 5 spaces (1 tab + 2 spaces)
 5526        \t\t\tabc           // Already tab indented
 5527        \t abc              // Tab followed by space
 5528         \tabc              // Space followed by tab (should be consumed due to tab)
 5529        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5530           \t  \t
 5531           abc   \t         // Only the leading spaces should be convertedˇ»
 5532    "});
 5533    cx.update_editor(|e, window, cx| {
 5534        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5535    });
 5536    cx.assert_editor_state(indoc! {"
 5537        «
 5538        abc                 // No indentation
 5539         abc                // 1 space (< 3 so dont convert)
 5540          abc               // 2 spaces (< 3 so dont convert)
 5541        \tabc              // 3 spaces (convert)
 5542        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5543        \t\t\tabc           // Already tab indented
 5544        \t abc              // Tab followed by space
 5545        \tabc              // Space followed by tab (should be consumed due to tab)
 5546        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5547        \t\t\t
 5548        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5549    "});
 5550}
 5551
 5552#[gpui::test]
 5553async fn test_toggle_case(cx: &mut TestAppContext) {
 5554    init_test(cx, |_| {});
 5555
 5556    let mut cx = EditorTestContext::new(cx).await;
 5557
 5558    // If all lower case -> upper case
 5559    cx.set_state(indoc! {"
 5560        «hello worldˇ»
 5561    "});
 5562    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5563    cx.assert_editor_state(indoc! {"
 5564        «HELLO WORLDˇ»
 5565    "});
 5566
 5567    // If all upper case -> lower case
 5568    cx.set_state(indoc! {"
 5569        «HELLO WORLDˇ»
 5570    "});
 5571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5572    cx.assert_editor_state(indoc! {"
 5573        «hello worldˇ»
 5574    "});
 5575
 5576    // If any upper case characters are identified -> lower case
 5577    // This matches JetBrains IDEs
 5578    cx.set_state(indoc! {"
 5579        «hEllo worldˇ»
 5580    "});
 5581    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5582    cx.assert_editor_state(indoc! {"
 5583        «hello worldˇ»
 5584    "});
 5585}
 5586
 5587#[gpui::test]
 5588async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5589    init_test(cx, |_| {});
 5590
 5591    let mut cx = EditorTestContext::new(cx).await;
 5592
 5593    cx.set_state(indoc! {"
 5594        «implement-windows-supportˇ»
 5595    "});
 5596    cx.update_editor(|e, window, cx| {
 5597        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5598    });
 5599    cx.assert_editor_state(indoc! {"
 5600        «Implement windows supportˇ»
 5601    "});
 5602}
 5603
 5604#[gpui::test]
 5605async fn test_manipulate_text(cx: &mut TestAppContext) {
 5606    init_test(cx, |_| {});
 5607
 5608    let mut cx = EditorTestContext::new(cx).await;
 5609
 5610    // Test convert_to_upper_case()
 5611    cx.set_state(indoc! {"
 5612        «hello worldˇ»
 5613    "});
 5614    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5615    cx.assert_editor_state(indoc! {"
 5616        «HELLO WORLDˇ»
 5617    "});
 5618
 5619    // Test convert_to_lower_case()
 5620    cx.set_state(indoc! {"
 5621        «HELLO WORLDˇ»
 5622    "});
 5623    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5624    cx.assert_editor_state(indoc! {"
 5625        «hello worldˇ»
 5626    "});
 5627
 5628    // Test multiple line, single selection case
 5629    cx.set_state(indoc! {"
 5630        «The quick brown
 5631        fox jumps over
 5632        the lazy dogˇ»
 5633    "});
 5634    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5635    cx.assert_editor_state(indoc! {"
 5636        «The Quick Brown
 5637        Fox Jumps Over
 5638        The Lazy Dogˇ»
 5639    "});
 5640
 5641    // Test multiple line, single selection case
 5642    cx.set_state(indoc! {"
 5643        «The quick brown
 5644        fox jumps over
 5645        the lazy dogˇ»
 5646    "});
 5647    cx.update_editor(|e, window, cx| {
 5648        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5649    });
 5650    cx.assert_editor_state(indoc! {"
 5651        «TheQuickBrown
 5652        FoxJumpsOver
 5653        TheLazyDogˇ»
 5654    "});
 5655
 5656    // From here on out, test more complex cases of manipulate_text()
 5657
 5658    // Test no selection case - should affect words cursors are in
 5659    // Cursor at beginning, middle, and end of word
 5660    cx.set_state(indoc! {"
 5661        ˇhello big beauˇtiful worldˇ
 5662    "});
 5663    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5664    cx.assert_editor_state(indoc! {"
 5665        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5666    "});
 5667
 5668    // Test multiple selections on a single line and across multiple lines
 5669    cx.set_state(indoc! {"
 5670        «Theˇ» quick «brown
 5671        foxˇ» jumps «overˇ»
 5672        the «lazyˇ» dog
 5673    "});
 5674    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5675    cx.assert_editor_state(indoc! {"
 5676        «THEˇ» quick «BROWN
 5677        FOXˇ» jumps «OVERˇ»
 5678        the «LAZYˇ» dog
 5679    "});
 5680
 5681    // Test case where text length grows
 5682    cx.set_state(indoc! {"
 5683        «tschüߡ»
 5684    "});
 5685    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5686    cx.assert_editor_state(indoc! {"
 5687        «TSCHÜSSˇ»
 5688    "});
 5689
 5690    // Test to make sure we don't crash when text shrinks
 5691    cx.set_state(indoc! {"
 5692        aaa_bbbˇ
 5693    "});
 5694    cx.update_editor(|e, window, cx| {
 5695        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5696    });
 5697    cx.assert_editor_state(indoc! {"
 5698        «aaaBbbˇ»
 5699    "});
 5700
 5701    // Test to make sure we all aware of the fact that each word can grow and shrink
 5702    // Final selections should be aware of this fact
 5703    cx.set_state(indoc! {"
 5704        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5705    "});
 5706    cx.update_editor(|e, window, cx| {
 5707        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5708    });
 5709    cx.assert_editor_state(indoc! {"
 5710        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5711    "});
 5712
 5713    cx.set_state(indoc! {"
 5714        «hElLo, WoRld!ˇ»
 5715    "});
 5716    cx.update_editor(|e, window, cx| {
 5717        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5718    });
 5719    cx.assert_editor_state(indoc! {"
 5720        «HeLlO, wOrLD!ˇ»
 5721    "});
 5722
 5723    // Test selections with `line_mode() = true`.
 5724    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5725    cx.set_state(indoc! {"
 5726        «The quick brown
 5727        fox jumps over
 5728        tˇ»he lazy dog
 5729    "});
 5730    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5731    cx.assert_editor_state(indoc! {"
 5732        «THE QUICK BROWN
 5733        FOX JUMPS OVER
 5734        THE LAZY DOGˇ»
 5735    "});
 5736}
 5737
 5738#[gpui::test]
 5739fn test_duplicate_line(cx: &mut TestAppContext) {
 5740    init_test(cx, |_| {});
 5741
 5742    let editor = cx.add_window(|window, cx| {
 5743        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5744        build_editor(buffer, window, cx)
 5745    });
 5746    _ = editor.update(cx, |editor, window, cx| {
 5747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5748            s.select_display_ranges([
 5749                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5750                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5751                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5752                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5753            ])
 5754        });
 5755        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5756        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5757        assert_eq!(
 5758            display_ranges(editor, cx),
 5759            vec![
 5760                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5762                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5763                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5764            ]
 5765        );
 5766    });
 5767
 5768    let editor = cx.add_window(|window, cx| {
 5769        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5770        build_editor(buffer, window, cx)
 5771    });
 5772    _ = editor.update(cx, |editor, window, cx| {
 5773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5774            s.select_display_ranges([
 5775                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5776                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5777            ])
 5778        });
 5779        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5780        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5781        assert_eq!(
 5782            display_ranges(editor, cx),
 5783            vec![
 5784                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5785                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5786            ]
 5787        );
 5788    });
 5789
 5790    // With `duplicate_line_up` the selections move to the duplicated lines,
 5791    // which are inserted above the original lines
 5792    let editor = cx.add_window(|window, cx| {
 5793        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5794        build_editor(buffer, window, cx)
 5795    });
 5796    _ = editor.update(cx, |editor, window, cx| {
 5797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5798            s.select_display_ranges([
 5799                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5800                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5801                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5802                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5803            ])
 5804        });
 5805        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5806        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5807        assert_eq!(
 5808            display_ranges(editor, cx),
 5809            vec![
 5810                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5811                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5812                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5813                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5814            ]
 5815        );
 5816    });
 5817
 5818    let editor = cx.add_window(|window, cx| {
 5819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5820        build_editor(buffer, window, cx)
 5821    });
 5822    _ = editor.update(cx, |editor, window, cx| {
 5823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5824            s.select_display_ranges([
 5825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5827            ])
 5828        });
 5829        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5830        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5831        assert_eq!(
 5832            display_ranges(editor, cx),
 5833            vec![
 5834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5835                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5836            ]
 5837        );
 5838    });
 5839
 5840    let editor = cx.add_window(|window, cx| {
 5841        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5842        build_editor(buffer, window, cx)
 5843    });
 5844    _ = editor.update(cx, |editor, window, cx| {
 5845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5846            s.select_display_ranges([
 5847                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5848                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5849            ])
 5850        });
 5851        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5852        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5853        assert_eq!(
 5854            display_ranges(editor, cx),
 5855            vec![
 5856                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5857                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5858            ]
 5859        );
 5860    });
 5861}
 5862
 5863#[gpui::test]
 5864async fn test_rotate_selections(cx: &mut TestAppContext) {
 5865    init_test(cx, |_| {});
 5866
 5867    let mut cx = EditorTestContext::new(cx).await;
 5868
 5869    // Rotate text selections (horizontal)
 5870    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5871    cx.update_editor(|e, window, cx| {
 5872        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5873    });
 5874    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5875    cx.update_editor(|e, window, cx| {
 5876        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5877    });
 5878    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5879
 5880    // Rotate text selections (vertical)
 5881    cx.set_state(indoc! {"
 5882        x=«1ˇ»
 5883        y=«2ˇ»
 5884        z=«3ˇ»
 5885    "});
 5886    cx.update_editor(|e, window, cx| {
 5887        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5888    });
 5889    cx.assert_editor_state(indoc! {"
 5890        x=«3ˇ»
 5891        y=«1ˇ»
 5892        z=«2ˇ»
 5893    "});
 5894    cx.update_editor(|e, window, cx| {
 5895        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5896    });
 5897    cx.assert_editor_state(indoc! {"
 5898        x=«1ˇ»
 5899        y=«2ˇ»
 5900        z=«3ˇ»
 5901    "});
 5902
 5903    // Rotate text selections (vertical, different lengths)
 5904    cx.set_state(indoc! {"
 5905        x=\"«ˇ»\"
 5906        y=\"«aˇ»\"
 5907        z=\"«aaˇ»\"
 5908    "});
 5909    cx.update_editor(|e, window, cx| {
 5910        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5911    });
 5912    cx.assert_editor_state(indoc! {"
 5913        x=\"«aaˇ»\"
 5914        y=\"«ˇ»\"
 5915        z=\"«aˇ»\"
 5916    "});
 5917    cx.update_editor(|e, window, cx| {
 5918        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5919    });
 5920    cx.assert_editor_state(indoc! {"
 5921        x=\"«ˇ»\"
 5922        y=\"«aˇ»\"
 5923        z=\"«aaˇ»\"
 5924    "});
 5925
 5926    // Rotate whole lines (cursor positions preserved)
 5927    cx.set_state(indoc! {"
 5928        ˇline123
 5929        liˇne23
 5930        line3ˇ
 5931    "});
 5932    cx.update_editor(|e, window, cx| {
 5933        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5934    });
 5935    cx.assert_editor_state(indoc! {"
 5936        line3ˇ
 5937        ˇline123
 5938        liˇne23
 5939    "});
 5940    cx.update_editor(|e, window, cx| {
 5941        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5942    });
 5943    cx.assert_editor_state(indoc! {"
 5944        ˇline123
 5945        liˇne23
 5946        line3ˇ
 5947    "});
 5948
 5949    // Rotate whole lines, multiple cursors per line (positions preserved)
 5950    cx.set_state(indoc! {"
 5951        ˇliˇne123
 5952        ˇline23
 5953        ˇline3
 5954    "});
 5955    cx.update_editor(|e, window, cx| {
 5956        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5957    });
 5958    cx.assert_editor_state(indoc! {"
 5959        ˇline3
 5960        ˇliˇne123
 5961        ˇline23
 5962    "});
 5963    cx.update_editor(|e, window, cx| {
 5964        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5965    });
 5966    cx.assert_editor_state(indoc! {"
 5967        ˇliˇne123
 5968        ˇline23
 5969        ˇline3
 5970    "});
 5971}
 5972
 5973#[gpui::test]
 5974fn test_move_line_up_down(cx: &mut TestAppContext) {
 5975    init_test(cx, |_| {});
 5976
 5977    let editor = cx.add_window(|window, cx| {
 5978        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5979        build_editor(buffer, window, cx)
 5980    });
 5981    _ = editor.update(cx, |editor, window, cx| {
 5982        editor.fold_creases(
 5983            vec![
 5984                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5985                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5986                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5987            ],
 5988            true,
 5989            window,
 5990            cx,
 5991        );
 5992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5993            s.select_display_ranges([
 5994                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5995                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5996                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5997                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5998            ])
 5999        });
 6000        assert_eq!(
 6001            editor.display_text(cx),
 6002            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6003        );
 6004
 6005        editor.move_line_up(&MoveLineUp, window, cx);
 6006        assert_eq!(
 6007            editor.display_text(cx),
 6008            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6009        );
 6010        assert_eq!(
 6011            display_ranges(editor, cx),
 6012            vec![
 6013                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6014                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6015                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6016                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6017            ]
 6018        );
 6019    });
 6020
 6021    _ = editor.update(cx, |editor, window, cx| {
 6022        editor.move_line_down(&MoveLineDown, window, cx);
 6023        assert_eq!(
 6024            editor.display_text(cx),
 6025            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6026        );
 6027        assert_eq!(
 6028            display_ranges(editor, cx),
 6029            vec![
 6030                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6031                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6032                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6033                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6034            ]
 6035        );
 6036    });
 6037
 6038    _ = editor.update(cx, |editor, window, cx| {
 6039        editor.move_line_down(&MoveLineDown, window, cx);
 6040        assert_eq!(
 6041            editor.display_text(cx),
 6042            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6043        );
 6044        assert_eq!(
 6045            display_ranges(editor, cx),
 6046            vec![
 6047                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6048                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6049                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6050                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6051            ]
 6052        );
 6053    });
 6054
 6055    _ = editor.update(cx, |editor, window, cx| {
 6056        editor.move_line_up(&MoveLineUp, window, cx);
 6057        assert_eq!(
 6058            editor.display_text(cx),
 6059            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6060        );
 6061        assert_eq!(
 6062            display_ranges(editor, cx),
 6063            vec![
 6064                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6065                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6066                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6067                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6068            ]
 6069        );
 6070    });
 6071}
 6072
 6073#[gpui::test]
 6074fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6075    init_test(cx, |_| {});
 6076    let editor = cx.add_window(|window, cx| {
 6077        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6078        build_editor(buffer, window, cx)
 6079    });
 6080    _ = editor.update(cx, |editor, window, cx| {
 6081        editor.fold_creases(
 6082            vec![Crease::simple(
 6083                Point::new(6, 4)..Point::new(7, 4),
 6084                FoldPlaceholder::test(),
 6085            )],
 6086            true,
 6087            window,
 6088            cx,
 6089        );
 6090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6091            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6092        });
 6093        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6094        editor.move_line_up(&MoveLineUp, window, cx);
 6095        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6096        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6097    });
 6098}
 6099
 6100#[gpui::test]
 6101fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6102    init_test(cx, |_| {});
 6103
 6104    let editor = cx.add_window(|window, cx| {
 6105        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6106        build_editor(buffer, window, cx)
 6107    });
 6108    _ = editor.update(cx, |editor, window, cx| {
 6109        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6110        editor.insert_blocks(
 6111            [BlockProperties {
 6112                style: BlockStyle::Fixed,
 6113                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6114                height: Some(1),
 6115                render: Arc::new(|_| div().into_any()),
 6116                priority: 0,
 6117            }],
 6118            Some(Autoscroll::fit()),
 6119            cx,
 6120        );
 6121        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6122            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6123        });
 6124        editor.move_line_down(&MoveLineDown, window, cx);
 6125    });
 6126}
 6127
 6128#[gpui::test]
 6129async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6130    init_test(cx, |_| {});
 6131
 6132    let mut cx = EditorTestContext::new(cx).await;
 6133    cx.set_state(
 6134        &"
 6135            ˇzero
 6136            one
 6137            two
 6138            three
 6139            four
 6140            five
 6141        "
 6142        .unindent(),
 6143    );
 6144
 6145    // Create a four-line block that replaces three lines of text.
 6146    cx.update_editor(|editor, window, cx| {
 6147        let snapshot = editor.snapshot(window, cx);
 6148        let snapshot = &snapshot.buffer_snapshot();
 6149        let placement = BlockPlacement::Replace(
 6150            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6151        );
 6152        editor.insert_blocks(
 6153            [BlockProperties {
 6154                placement,
 6155                height: Some(4),
 6156                style: BlockStyle::Sticky,
 6157                render: Arc::new(|_| gpui::div().into_any_element()),
 6158                priority: 0,
 6159            }],
 6160            None,
 6161            cx,
 6162        );
 6163    });
 6164
 6165    // Move down so that the cursor touches the block.
 6166    cx.update_editor(|editor, window, cx| {
 6167        editor.move_down(&Default::default(), window, cx);
 6168    });
 6169    cx.assert_editor_state(
 6170        &"
 6171            zero
 6172            «one
 6173            two
 6174            threeˇ»
 6175            four
 6176            five
 6177        "
 6178        .unindent(),
 6179    );
 6180
 6181    // Move down past the block.
 6182    cx.update_editor(|editor, window, cx| {
 6183        editor.move_down(&Default::default(), window, cx);
 6184    });
 6185    cx.assert_editor_state(
 6186        &"
 6187            zero
 6188            one
 6189            two
 6190            three
 6191            ˇfour
 6192            five
 6193        "
 6194        .unindent(),
 6195    );
 6196}
 6197
 6198#[gpui::test]
 6199fn test_transpose(cx: &mut TestAppContext) {
 6200    init_test(cx, |_| {});
 6201
 6202    _ = cx.add_window(|window, cx| {
 6203        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6204        editor.set_style(EditorStyle::default(), window, cx);
 6205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6206            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6207        });
 6208        editor.transpose(&Default::default(), window, cx);
 6209        assert_eq!(editor.text(cx), "bac");
 6210        assert_eq!(
 6211            editor.selections.ranges(&editor.display_snapshot(cx)),
 6212            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6213        );
 6214
 6215        editor.transpose(&Default::default(), window, cx);
 6216        assert_eq!(editor.text(cx), "bca");
 6217        assert_eq!(
 6218            editor.selections.ranges(&editor.display_snapshot(cx)),
 6219            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6220        );
 6221
 6222        editor.transpose(&Default::default(), window, cx);
 6223        assert_eq!(editor.text(cx), "bac");
 6224        assert_eq!(
 6225            editor.selections.ranges(&editor.display_snapshot(cx)),
 6226            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6227        );
 6228
 6229        editor
 6230    });
 6231
 6232    _ = cx.add_window(|window, cx| {
 6233        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6234        editor.set_style(EditorStyle::default(), window, cx);
 6235        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6236            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6237        });
 6238        editor.transpose(&Default::default(), window, cx);
 6239        assert_eq!(editor.text(cx), "acb\nde");
 6240        assert_eq!(
 6241            editor.selections.ranges(&editor.display_snapshot(cx)),
 6242            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6243        );
 6244
 6245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6246            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6247        });
 6248        editor.transpose(&Default::default(), window, cx);
 6249        assert_eq!(editor.text(cx), "acbd\ne");
 6250        assert_eq!(
 6251            editor.selections.ranges(&editor.display_snapshot(cx)),
 6252            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6253        );
 6254
 6255        editor.transpose(&Default::default(), window, cx);
 6256        assert_eq!(editor.text(cx), "acbde\n");
 6257        assert_eq!(
 6258            editor.selections.ranges(&editor.display_snapshot(cx)),
 6259            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6260        );
 6261
 6262        editor.transpose(&Default::default(), window, cx);
 6263        assert_eq!(editor.text(cx), "acbd\ne");
 6264        assert_eq!(
 6265            editor.selections.ranges(&editor.display_snapshot(cx)),
 6266            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6267        );
 6268
 6269        editor
 6270    });
 6271
 6272    _ = cx.add_window(|window, cx| {
 6273        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6274        editor.set_style(EditorStyle::default(), window, cx);
 6275        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6276            s.select_ranges([
 6277                MultiBufferOffset(1)..MultiBufferOffset(1),
 6278                MultiBufferOffset(2)..MultiBufferOffset(2),
 6279                MultiBufferOffset(4)..MultiBufferOffset(4),
 6280            ])
 6281        });
 6282        editor.transpose(&Default::default(), window, cx);
 6283        assert_eq!(editor.text(cx), "bacd\ne");
 6284        assert_eq!(
 6285            editor.selections.ranges(&editor.display_snapshot(cx)),
 6286            [
 6287                MultiBufferOffset(2)..MultiBufferOffset(2),
 6288                MultiBufferOffset(3)..MultiBufferOffset(3),
 6289                MultiBufferOffset(5)..MultiBufferOffset(5)
 6290            ]
 6291        );
 6292
 6293        editor.transpose(&Default::default(), window, cx);
 6294        assert_eq!(editor.text(cx), "bcade\n");
 6295        assert_eq!(
 6296            editor.selections.ranges(&editor.display_snapshot(cx)),
 6297            [
 6298                MultiBufferOffset(3)..MultiBufferOffset(3),
 6299                MultiBufferOffset(4)..MultiBufferOffset(4),
 6300                MultiBufferOffset(6)..MultiBufferOffset(6)
 6301            ]
 6302        );
 6303
 6304        editor.transpose(&Default::default(), window, cx);
 6305        assert_eq!(editor.text(cx), "bcda\ne");
 6306        assert_eq!(
 6307            editor.selections.ranges(&editor.display_snapshot(cx)),
 6308            [
 6309                MultiBufferOffset(4)..MultiBufferOffset(4),
 6310                MultiBufferOffset(6)..MultiBufferOffset(6)
 6311            ]
 6312        );
 6313
 6314        editor.transpose(&Default::default(), window, cx);
 6315        assert_eq!(editor.text(cx), "bcade\n");
 6316        assert_eq!(
 6317            editor.selections.ranges(&editor.display_snapshot(cx)),
 6318            [
 6319                MultiBufferOffset(4)..MultiBufferOffset(4),
 6320                MultiBufferOffset(6)..MultiBufferOffset(6)
 6321            ]
 6322        );
 6323
 6324        editor.transpose(&Default::default(), window, cx);
 6325        assert_eq!(editor.text(cx), "bcaed\n");
 6326        assert_eq!(
 6327            editor.selections.ranges(&editor.display_snapshot(cx)),
 6328            [
 6329                MultiBufferOffset(5)..MultiBufferOffset(5),
 6330                MultiBufferOffset(6)..MultiBufferOffset(6)
 6331            ]
 6332        );
 6333
 6334        editor
 6335    });
 6336
 6337    _ = cx.add_window(|window, cx| {
 6338        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6339        editor.set_style(EditorStyle::default(), window, cx);
 6340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6341            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6342        });
 6343        editor.transpose(&Default::default(), window, cx);
 6344        assert_eq!(editor.text(cx), "🏀🍐✋");
 6345        assert_eq!(
 6346            editor.selections.ranges(&editor.display_snapshot(cx)),
 6347            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6348        );
 6349
 6350        editor.transpose(&Default::default(), window, cx);
 6351        assert_eq!(editor.text(cx), "🏀✋🍐");
 6352        assert_eq!(
 6353            editor.selections.ranges(&editor.display_snapshot(cx)),
 6354            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6355        );
 6356
 6357        editor.transpose(&Default::default(), window, cx);
 6358        assert_eq!(editor.text(cx), "🏀🍐✋");
 6359        assert_eq!(
 6360            editor.selections.ranges(&editor.display_snapshot(cx)),
 6361            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6362        );
 6363
 6364        editor
 6365    });
 6366}
 6367
 6368#[gpui::test]
 6369async fn test_rewrap(cx: &mut TestAppContext) {
 6370    init_test(cx, |settings| {
 6371        settings.languages.0.extend([
 6372            (
 6373                "Markdown".into(),
 6374                LanguageSettingsContent {
 6375                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6376                    preferred_line_length: Some(40),
 6377                    ..Default::default()
 6378                },
 6379            ),
 6380            (
 6381                "Plain Text".into(),
 6382                LanguageSettingsContent {
 6383                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6384                    preferred_line_length: Some(40),
 6385                    ..Default::default()
 6386                },
 6387            ),
 6388            (
 6389                "C++".into(),
 6390                LanguageSettingsContent {
 6391                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6392                    preferred_line_length: Some(40),
 6393                    ..Default::default()
 6394                },
 6395            ),
 6396            (
 6397                "Python".into(),
 6398                LanguageSettingsContent {
 6399                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6400                    preferred_line_length: Some(40),
 6401                    ..Default::default()
 6402                },
 6403            ),
 6404            (
 6405                "Rust".into(),
 6406                LanguageSettingsContent {
 6407                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6408                    preferred_line_length: Some(40),
 6409                    ..Default::default()
 6410                },
 6411            ),
 6412        ])
 6413    });
 6414
 6415    let mut cx = EditorTestContext::new(cx).await;
 6416
 6417    let cpp_language = Arc::new(Language::new(
 6418        LanguageConfig {
 6419            name: "C++".into(),
 6420            line_comments: vec!["// ".into()],
 6421            ..LanguageConfig::default()
 6422        },
 6423        None,
 6424    ));
 6425    let python_language = Arc::new(Language::new(
 6426        LanguageConfig {
 6427            name: "Python".into(),
 6428            line_comments: vec!["# ".into()],
 6429            ..LanguageConfig::default()
 6430        },
 6431        None,
 6432    ));
 6433    let markdown_language = Arc::new(Language::new(
 6434        LanguageConfig {
 6435            name: "Markdown".into(),
 6436            rewrap_prefixes: vec![
 6437                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6438                regex::Regex::new("[-*+]\\s+").unwrap(),
 6439            ],
 6440            ..LanguageConfig::default()
 6441        },
 6442        None,
 6443    ));
 6444    let rust_language = Arc::new(
 6445        Language::new(
 6446            LanguageConfig {
 6447                name: "Rust".into(),
 6448                line_comments: vec!["// ".into(), "/// ".into()],
 6449                ..LanguageConfig::default()
 6450            },
 6451            Some(tree_sitter_rust::LANGUAGE.into()),
 6452        )
 6453        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6454        .unwrap(),
 6455    );
 6456
 6457    let plaintext_language = Arc::new(Language::new(
 6458        LanguageConfig {
 6459            name: "Plain Text".into(),
 6460            ..LanguageConfig::default()
 6461        },
 6462        None,
 6463    ));
 6464
 6465    // Test basic rewrapping of a long line with a cursor
 6466    assert_rewrap(
 6467        indoc! {"
 6468            // ˇThis is a long comment that needs to be wrapped.
 6469        "},
 6470        indoc! {"
 6471            // ˇThis is a long comment that needs to
 6472            // be wrapped.
 6473        "},
 6474        cpp_language.clone(),
 6475        &mut cx,
 6476    );
 6477
 6478    // Test rewrapping a full selection
 6479    assert_rewrap(
 6480        indoc! {"
 6481            «// This selected long comment needs to be wrapped.ˇ»"
 6482        },
 6483        indoc! {"
 6484            «// This selected long comment needs to
 6485            // be wrapped.ˇ»"
 6486        },
 6487        cpp_language.clone(),
 6488        &mut cx,
 6489    );
 6490
 6491    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6492    assert_rewrap(
 6493        indoc! {"
 6494            // ˇThis is the first line.
 6495            // Thisˇ is the second line.
 6496            // This is the thirdˇ line, all part of one paragraph.
 6497         "},
 6498        indoc! {"
 6499            // ˇThis is the first line. Thisˇ is the
 6500            // second line. This is the thirdˇ line,
 6501            // all part of one paragraph.
 6502         "},
 6503        cpp_language.clone(),
 6504        &mut cx,
 6505    );
 6506
 6507    // Test multiple cursors in different paragraphs trigger separate rewraps
 6508    assert_rewrap(
 6509        indoc! {"
 6510            // ˇThis is the first paragraph, first line.
 6511            // ˇThis is the first paragraph, second line.
 6512
 6513            // ˇThis is the second paragraph, first line.
 6514            // ˇThis is the second paragraph, second line.
 6515        "},
 6516        indoc! {"
 6517            // ˇThis is the first paragraph, first
 6518            // line. ˇThis is the first paragraph,
 6519            // second line.
 6520
 6521            // ˇThis is the second paragraph, first
 6522            // line. ˇThis is the second paragraph,
 6523            // second line.
 6524        "},
 6525        cpp_language.clone(),
 6526        &mut cx,
 6527    );
 6528
 6529    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6530    assert_rewrap(
 6531        indoc! {"
 6532            «// A regular long long comment to be wrapped.
 6533            /// A documentation long comment to be wrapped.ˇ»
 6534          "},
 6535        indoc! {"
 6536            «// A regular long long comment to be
 6537            // wrapped.
 6538            /// A documentation long comment to be
 6539            /// wrapped.ˇ»
 6540          "},
 6541        rust_language.clone(),
 6542        &mut cx,
 6543    );
 6544
 6545    // Test that change in indentation level trigger seperate rewraps
 6546    assert_rewrap(
 6547        indoc! {"
 6548            fn foo() {
 6549                «// This is a long comment at the base indent.
 6550                    // This is a long comment at the next indent.ˇ»
 6551            }
 6552        "},
 6553        indoc! {"
 6554            fn foo() {
 6555                «// This is a long comment at the
 6556                // base indent.
 6557                    // This is a long comment at the
 6558                    // next indent.ˇ»
 6559            }
 6560        "},
 6561        rust_language.clone(),
 6562        &mut cx,
 6563    );
 6564
 6565    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6566    assert_rewrap(
 6567        indoc! {"
 6568            # ˇThis is a long comment using a pound sign.
 6569        "},
 6570        indoc! {"
 6571            # ˇThis is a long comment using a pound
 6572            # sign.
 6573        "},
 6574        python_language,
 6575        &mut cx,
 6576    );
 6577
 6578    // Test rewrapping only affects comments, not code even when selected
 6579    assert_rewrap(
 6580        indoc! {"
 6581            «/// This doc comment is long and should be wrapped.
 6582            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6583        "},
 6584        indoc! {"
 6585            «/// This doc comment is long and should
 6586            /// be wrapped.
 6587            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6588        "},
 6589        rust_language.clone(),
 6590        &mut cx,
 6591    );
 6592
 6593    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6594    assert_rewrap(
 6595        indoc! {"
 6596            # Header
 6597
 6598            A long long long line of markdown text to wrap.ˇ
 6599         "},
 6600        indoc! {"
 6601            # Header
 6602
 6603            A long long long line of markdown text
 6604            to wrap.ˇ
 6605         "},
 6606        markdown_language.clone(),
 6607        &mut cx,
 6608    );
 6609
 6610    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6611    assert_rewrap(
 6612        indoc! {"
 6613            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6614            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6615            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6616        "},
 6617        indoc! {"
 6618            «1. This is a numbered list item that is
 6619               very long and needs to be wrapped
 6620               properly.
 6621            2. This is a numbered list item that is
 6622               very long and needs to be wrapped
 6623               properly.
 6624            - This is an unordered list item that is
 6625              also very long and should not merge
 6626              with the numbered item.ˇ»
 6627        "},
 6628        markdown_language.clone(),
 6629        &mut cx,
 6630    );
 6631
 6632    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6633    assert_rewrap(
 6634        indoc! {"
 6635            «1. This is a numbered list item that is
 6636            very long and needs to be wrapped
 6637            properly.
 6638            2. This is a numbered list item that is
 6639            very long and needs to be wrapped
 6640            properly.
 6641            - This is an unordered list item that is
 6642            also very long and should not merge with
 6643            the numbered item.ˇ»
 6644        "},
 6645        indoc! {"
 6646            «1. This is a numbered list item that is
 6647               very long and needs to be wrapped
 6648               properly.
 6649            2. This is a numbered list item that is
 6650               very long and needs to be wrapped
 6651               properly.
 6652            - This is an unordered list item that is
 6653              also very long and should not merge
 6654              with the numbered item.ˇ»
 6655        "},
 6656        markdown_language.clone(),
 6657        &mut cx,
 6658    );
 6659
 6660    // Test that rewrapping maintain indents even when they already exists.
 6661    assert_rewrap(
 6662        indoc! {"
 6663            «1. This is a numbered list
 6664               item that is very long and needs to be wrapped properly.
 6665            2. This is a numbered list
 6666               item that is very long and needs to be wrapped properly.
 6667            - This is an unordered list item that is also very long and
 6668              should not merge with the numbered item.ˇ»
 6669        "},
 6670        indoc! {"
 6671            «1. This is a numbered list item that is
 6672               very long and needs to be wrapped
 6673               properly.
 6674            2. This is a numbered list item that is
 6675               very long and needs to be wrapped
 6676               properly.
 6677            - This is an unordered list item that is
 6678              also very long and should not merge
 6679              with the numbered item.ˇ»
 6680        "},
 6681        markdown_language,
 6682        &mut cx,
 6683    );
 6684
 6685    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6686    assert_rewrap(
 6687        indoc! {"
 6688            ˇThis is a very long line of plain text that will be wrapped.
 6689        "},
 6690        indoc! {"
 6691            ˇThis is a very long line of plain text
 6692            that will be wrapped.
 6693        "},
 6694        plaintext_language.clone(),
 6695        &mut cx,
 6696    );
 6697
 6698    // Test that non-commented code acts as a paragraph boundary within a selection
 6699    assert_rewrap(
 6700        indoc! {"
 6701               «// This is the first long comment block to be wrapped.
 6702               fn my_func(a: u32);
 6703               // This is the second long comment block to be wrapped.ˇ»
 6704           "},
 6705        indoc! {"
 6706               «// This is the first long comment block
 6707               // to be wrapped.
 6708               fn my_func(a: u32);
 6709               // This is the second long comment block
 6710               // to be wrapped.ˇ»
 6711           "},
 6712        rust_language,
 6713        &mut cx,
 6714    );
 6715
 6716    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6717    assert_rewrap(
 6718        indoc! {"
 6719            «ˇThis is a very long line that will be wrapped.
 6720
 6721            This is another paragraph in the same selection.»
 6722
 6723            «\tThis is a very long indented line that will be wrapped.ˇ»
 6724         "},
 6725        indoc! {"
 6726            «ˇThis is a very long line that will be
 6727            wrapped.
 6728
 6729            This is another paragraph in the same
 6730            selection.»
 6731
 6732            «\tThis is a very long indented line
 6733            \tthat will be wrapped.ˇ»
 6734         "},
 6735        plaintext_language,
 6736        &mut cx,
 6737    );
 6738
 6739    // Test that an empty comment line acts as a paragraph boundary
 6740    assert_rewrap(
 6741        indoc! {"
 6742            // ˇThis is a long comment that will be wrapped.
 6743            //
 6744            // And this is another long comment that will also be wrapped.ˇ
 6745         "},
 6746        indoc! {"
 6747            // ˇThis is a long comment that will be
 6748            // wrapped.
 6749            //
 6750            // And this is another long comment that
 6751            // will also be wrapped.ˇ
 6752         "},
 6753        cpp_language,
 6754        &mut cx,
 6755    );
 6756
 6757    #[track_caller]
 6758    fn assert_rewrap(
 6759        unwrapped_text: &str,
 6760        wrapped_text: &str,
 6761        language: Arc<Language>,
 6762        cx: &mut EditorTestContext,
 6763    ) {
 6764        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6765        cx.set_state(unwrapped_text);
 6766        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6767        cx.assert_editor_state(wrapped_text);
 6768    }
 6769}
 6770
 6771#[gpui::test]
 6772async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6773    init_test(cx, |settings| {
 6774        settings.languages.0.extend([(
 6775            "Rust".into(),
 6776            LanguageSettingsContent {
 6777                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6778                preferred_line_length: Some(40),
 6779                ..Default::default()
 6780            },
 6781        )])
 6782    });
 6783
 6784    let mut cx = EditorTestContext::new(cx).await;
 6785
 6786    let rust_lang = Arc::new(
 6787        Language::new(
 6788            LanguageConfig {
 6789                name: "Rust".into(),
 6790                line_comments: vec!["// ".into()],
 6791                block_comment: Some(BlockCommentConfig {
 6792                    start: "/*".into(),
 6793                    end: "*/".into(),
 6794                    prefix: "* ".into(),
 6795                    tab_size: 1,
 6796                }),
 6797                documentation_comment: Some(BlockCommentConfig {
 6798                    start: "/**".into(),
 6799                    end: "*/".into(),
 6800                    prefix: "* ".into(),
 6801                    tab_size: 1,
 6802                }),
 6803
 6804                ..LanguageConfig::default()
 6805            },
 6806            Some(tree_sitter_rust::LANGUAGE.into()),
 6807        )
 6808        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6809        .unwrap(),
 6810    );
 6811
 6812    // regular block comment
 6813    assert_rewrap(
 6814        indoc! {"
 6815            /*
 6816             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6817             */
 6818            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6819        "},
 6820        indoc! {"
 6821            /*
 6822             *ˇ Lorem ipsum dolor sit amet,
 6823             * consectetur adipiscing elit.
 6824             */
 6825            /*
 6826             *ˇ Lorem ipsum dolor sit amet,
 6827             * consectetur adipiscing elit.
 6828             */
 6829        "},
 6830        rust_lang.clone(),
 6831        &mut cx,
 6832    );
 6833
 6834    // indent is respected
 6835    assert_rewrap(
 6836        indoc! {"
 6837            {}
 6838                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6839        "},
 6840        indoc! {"
 6841            {}
 6842                /*
 6843                 *ˇ Lorem ipsum dolor sit amet,
 6844                 * consectetur adipiscing elit.
 6845                 */
 6846        "},
 6847        rust_lang.clone(),
 6848        &mut cx,
 6849    );
 6850
 6851    // short block comments with inline delimiters
 6852    assert_rewrap(
 6853        indoc! {"
 6854            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6855            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6856             */
 6857            /*
 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            /*
 6870             *ˇ Lorem ipsum dolor sit amet,
 6871             * consectetur adipiscing elit.
 6872             */
 6873        "},
 6874        rust_lang.clone(),
 6875        &mut cx,
 6876    );
 6877
 6878    // multiline block comment with inline start/end delimiters
 6879    assert_rewrap(
 6880        indoc! {"
 6881            /*ˇ Lorem ipsum dolor sit amet,
 6882             * consectetur adipiscing elit. */
 6883        "},
 6884        indoc! {"
 6885            /*
 6886             *ˇ Lorem ipsum dolor sit amet,
 6887             * consectetur adipiscing elit.
 6888             */
 6889        "},
 6890        rust_lang.clone(),
 6891        &mut cx,
 6892    );
 6893
 6894    // block comment rewrap still respects paragraph bounds
 6895    assert_rewrap(
 6896        indoc! {"
 6897            /*
 6898             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6899             *
 6900             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6901             */
 6902        "},
 6903        indoc! {"
 6904            /*
 6905             *ˇ Lorem ipsum dolor sit amet,
 6906             * consectetur adipiscing elit.
 6907             *
 6908             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6909             */
 6910        "},
 6911        rust_lang.clone(),
 6912        &mut cx,
 6913    );
 6914
 6915    // documentation comments
 6916    assert_rewrap(
 6917        indoc! {"
 6918            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6919            /**
 6920             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6921             */
 6922        "},
 6923        indoc! {"
 6924            /**
 6925             *ˇ Lorem ipsum dolor sit amet,
 6926             * consectetur adipiscing elit.
 6927             */
 6928            /**
 6929             *ˇ Lorem ipsum dolor sit amet,
 6930             * consectetur adipiscing elit.
 6931             */
 6932        "},
 6933        rust_lang.clone(),
 6934        &mut cx,
 6935    );
 6936
 6937    // different, adjacent comments
 6938    assert_rewrap(
 6939        indoc! {"
 6940            /**
 6941             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6942             */
 6943            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6944            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6945        "},
 6946        indoc! {"
 6947            /**
 6948             *ˇ Lorem ipsum dolor sit amet,
 6949             * consectetur adipiscing elit.
 6950             */
 6951            /*
 6952             *ˇ Lorem ipsum dolor sit amet,
 6953             * consectetur adipiscing elit.
 6954             */
 6955            //ˇ Lorem ipsum dolor sit amet,
 6956            // consectetur adipiscing elit.
 6957        "},
 6958        rust_lang.clone(),
 6959        &mut cx,
 6960    );
 6961
 6962    // selection w/ single short block comment
 6963    assert_rewrap(
 6964        indoc! {"
 6965            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6966        "},
 6967        indoc! {"
 6968            «/*
 6969             * Lorem ipsum dolor sit amet,
 6970             * consectetur adipiscing elit.
 6971             */ˇ»
 6972        "},
 6973        rust_lang.clone(),
 6974        &mut cx,
 6975    );
 6976
 6977    // rewrapping a single comment w/ abutting comments
 6978    assert_rewrap(
 6979        indoc! {"
 6980            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6981            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6982        "},
 6983        indoc! {"
 6984            /*
 6985             * ˇLorem ipsum dolor sit amet,
 6986             * consectetur adipiscing elit.
 6987             */
 6988            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6989        "},
 6990        rust_lang.clone(),
 6991        &mut cx,
 6992    );
 6993
 6994    // selection w/ non-abutting short block comments
 6995    assert_rewrap(
 6996        indoc! {"
 6997            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6998
 6999            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7000        "},
 7001        indoc! {"
 7002            «/*
 7003             * Lorem ipsum dolor sit amet,
 7004             * consectetur adipiscing elit.
 7005             */
 7006
 7007            /*
 7008             * Lorem ipsum dolor sit amet,
 7009             * consectetur adipiscing elit.
 7010             */ˇ»
 7011        "},
 7012        rust_lang.clone(),
 7013        &mut cx,
 7014    );
 7015
 7016    // selection of multiline block comments
 7017    assert_rewrap(
 7018        indoc! {"
 7019            «/* Lorem ipsum dolor sit amet,
 7020             * consectetur adipiscing elit. */ˇ»
 7021        "},
 7022        indoc! {"
 7023            «/*
 7024             * Lorem ipsum dolor sit amet,
 7025             * consectetur adipiscing elit.
 7026             */ˇ»
 7027        "},
 7028        rust_lang.clone(),
 7029        &mut cx,
 7030    );
 7031
 7032    // partial selection of multiline block comments
 7033    assert_rewrap(
 7034        indoc! {"
 7035            «/* Lorem ipsum dolor sit amet,ˇ»
 7036             * consectetur adipiscing elit. */
 7037            /* Lorem ipsum dolor sit amet,
 7038             «* consectetur adipiscing elit. */ˇ»
 7039        "},
 7040        indoc! {"
 7041            «/*
 7042             * Lorem ipsum dolor sit amet,ˇ»
 7043             * consectetur adipiscing elit. */
 7044            /* Lorem ipsum dolor sit amet,
 7045             «* consectetur adipiscing elit.
 7046             */ˇ»
 7047        "},
 7048        rust_lang.clone(),
 7049        &mut cx,
 7050    );
 7051
 7052    // selection w/ abutting short block comments
 7053    // TODO: should not be combined; should rewrap as 2 comments
 7054    assert_rewrap(
 7055        indoc! {"
 7056            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7057            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7058        "},
 7059        // desired behavior:
 7060        // indoc! {"
 7061        //     «/*
 7062        //      * Lorem ipsum dolor sit amet,
 7063        //      * consectetur adipiscing elit.
 7064        //      */
 7065        //     /*
 7066        //      * Lorem ipsum dolor sit amet,
 7067        //      * consectetur adipiscing elit.
 7068        //      */ˇ»
 7069        // "},
 7070        // actual behaviour:
 7071        indoc! {"
 7072            «/*
 7073             * Lorem ipsum dolor sit amet,
 7074             * consectetur adipiscing elit. Lorem
 7075             * ipsum dolor sit amet, consectetur
 7076             * adipiscing elit.
 7077             */ˇ»
 7078        "},
 7079        rust_lang.clone(),
 7080        &mut cx,
 7081    );
 7082
 7083    // TODO: same as above, but with delimiters on separate line
 7084    // assert_rewrap(
 7085    //     indoc! {"
 7086    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7087    //          */
 7088    //         /*
 7089    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7090    //     "},
 7091    //     // desired:
 7092    //     // indoc! {"
 7093    //     //     «/*
 7094    //     //      * Lorem ipsum dolor sit amet,
 7095    //     //      * consectetur adipiscing elit.
 7096    //     //      */
 7097    //     //     /*
 7098    //     //      * Lorem ipsum dolor sit amet,
 7099    //     //      * consectetur adipiscing elit.
 7100    //     //      */ˇ»
 7101    //     // "},
 7102    //     // actual: (but with trailing w/s on the empty lines)
 7103    //     indoc! {"
 7104    //         «/*
 7105    //          * Lorem ipsum dolor sit amet,
 7106    //          * consectetur adipiscing elit.
 7107    //          *
 7108    //          */
 7109    //         /*
 7110    //          *
 7111    //          * Lorem ipsum dolor sit amet,
 7112    //          * consectetur adipiscing elit.
 7113    //          */ˇ»
 7114    //     "},
 7115    //     rust_lang.clone(),
 7116    //     &mut cx,
 7117    // );
 7118
 7119    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7120    assert_rewrap(
 7121        indoc! {"
 7122            /*
 7123             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7124             */
 7125            /*
 7126             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7127            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7128        "},
 7129        // desired:
 7130        // indoc! {"
 7131        //     /*
 7132        //      *ˇ Lorem ipsum dolor sit amet,
 7133        //      * consectetur adipiscing elit.
 7134        //      */
 7135        //     /*
 7136        //      *ˇ Lorem ipsum dolor sit amet,
 7137        //      * consectetur adipiscing elit.
 7138        //      */
 7139        //     /*
 7140        //      *ˇ Lorem ipsum dolor sit amet
 7141        //      */ /* consectetur adipiscing elit. */
 7142        // "},
 7143        // actual:
 7144        indoc! {"
 7145            /*
 7146             //ˇ Lorem ipsum dolor sit amet,
 7147             // consectetur adipiscing elit.
 7148             */
 7149            /*
 7150             * //ˇ Lorem ipsum dolor sit amet,
 7151             * consectetur adipiscing elit.
 7152             */
 7153            /*
 7154             *ˇ Lorem ipsum dolor sit amet */ /*
 7155             * consectetur adipiscing elit.
 7156             */
 7157        "},
 7158        rust_lang,
 7159        &mut cx,
 7160    );
 7161
 7162    #[track_caller]
 7163    fn assert_rewrap(
 7164        unwrapped_text: &str,
 7165        wrapped_text: &str,
 7166        language: Arc<Language>,
 7167        cx: &mut EditorTestContext,
 7168    ) {
 7169        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7170        cx.set_state(unwrapped_text);
 7171        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7172        cx.assert_editor_state(wrapped_text);
 7173    }
 7174}
 7175
 7176#[gpui::test]
 7177async fn test_hard_wrap(cx: &mut TestAppContext) {
 7178    init_test(cx, |_| {});
 7179    let mut cx = EditorTestContext::new(cx).await;
 7180
 7181    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7182    cx.update_editor(|editor, _, cx| {
 7183        editor.set_hard_wrap(Some(14), cx);
 7184    });
 7185
 7186    cx.set_state(indoc!(
 7187        "
 7188        one two three ˇ
 7189        "
 7190    ));
 7191    cx.simulate_input("four");
 7192    cx.run_until_parked();
 7193
 7194    cx.assert_editor_state(indoc!(
 7195        "
 7196        one two three
 7197        fourˇ
 7198        "
 7199    ));
 7200
 7201    cx.update_editor(|editor, window, cx| {
 7202        editor.newline(&Default::default(), window, cx);
 7203    });
 7204    cx.run_until_parked();
 7205    cx.assert_editor_state(indoc!(
 7206        "
 7207        one two three
 7208        four
 7209        ˇ
 7210        "
 7211    ));
 7212
 7213    cx.simulate_input("five");
 7214    cx.run_until_parked();
 7215    cx.assert_editor_state(indoc!(
 7216        "
 7217        one two three
 7218        four
 7219        fiveˇ
 7220        "
 7221    ));
 7222
 7223    cx.update_editor(|editor, window, cx| {
 7224        editor.newline(&Default::default(), window, cx);
 7225    });
 7226    cx.run_until_parked();
 7227    cx.simulate_input("# ");
 7228    cx.run_until_parked();
 7229    cx.assert_editor_state(indoc!(
 7230        "
 7231        one two three
 7232        four
 7233        five
 7234        # ˇ
 7235        "
 7236    ));
 7237
 7238    cx.update_editor(|editor, window, cx| {
 7239        editor.newline(&Default::default(), window, cx);
 7240    });
 7241    cx.run_until_parked();
 7242    cx.assert_editor_state(indoc!(
 7243        "
 7244        one two three
 7245        four
 7246        five
 7247        #\x20
 7248 7249        "
 7250    ));
 7251
 7252    cx.simulate_input(" 6");
 7253    cx.run_until_parked();
 7254    cx.assert_editor_state(indoc!(
 7255        "
 7256        one two three
 7257        four
 7258        five
 7259        #
 7260        # 6ˇ
 7261        "
 7262    ));
 7263}
 7264
 7265#[gpui::test]
 7266async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7267    init_test(cx, |_| {});
 7268
 7269    let mut cx = EditorTestContext::new(cx).await;
 7270
 7271    cx.set_state(indoc! {"The quick brownˇ"});
 7272    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7273    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7274
 7275    cx.set_state(indoc! {"The emacs foxˇ"});
 7276    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7277    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7278
 7279    cx.set_state(indoc! {"
 7280        The quick« brownˇ»
 7281        fox jumps overˇ
 7282        the lazy dog"});
 7283    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7284    cx.assert_editor_state(indoc! {"
 7285        The quickˇ
 7286        ˇthe lazy dog"});
 7287
 7288    cx.set_state(indoc! {"
 7289        The quick« brownˇ»
 7290        fox jumps overˇ
 7291        the lazy dog"});
 7292    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7293    cx.assert_editor_state(indoc! {"
 7294        The quickˇ
 7295        fox jumps overˇthe lazy dog"});
 7296
 7297    cx.set_state(indoc! {"
 7298        The quick« brownˇ»
 7299        fox jumps overˇ
 7300        the lazy dog"});
 7301    cx.update_editor(|e, window, cx| {
 7302        e.cut_to_end_of_line(
 7303            &CutToEndOfLine {
 7304                stop_at_newlines: true,
 7305            },
 7306            window,
 7307            cx,
 7308        )
 7309    });
 7310    cx.assert_editor_state(indoc! {"
 7311        The quickˇ
 7312        fox jumps overˇ
 7313        the lazy dog"});
 7314
 7315    cx.set_state(indoc! {"
 7316        The quick« brownˇ»
 7317        fox jumps overˇ
 7318        the lazy dog"});
 7319    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7320    cx.assert_editor_state(indoc! {"
 7321        The quickˇ
 7322        fox jumps overˇthe lazy dog"});
 7323}
 7324
 7325#[gpui::test]
 7326async fn test_clipboard(cx: &mut TestAppContext) {
 7327    init_test(cx, |_| {});
 7328
 7329    let mut cx = EditorTestContext::new(cx).await;
 7330
 7331    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7332    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7333    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7334
 7335    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7336    cx.set_state("two ˇfour ˇsix ˇ");
 7337    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7338    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7339
 7340    // Paste again but with only two cursors. Since the number of cursors doesn't
 7341    // match the number of slices in the clipboard, the entire clipboard text
 7342    // is pasted at each cursor.
 7343    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7344    cx.update_editor(|e, window, cx| {
 7345        e.handle_input("( ", window, cx);
 7346        e.paste(&Paste, window, cx);
 7347        e.handle_input(") ", window, cx);
 7348    });
 7349    cx.assert_editor_state(
 7350        &([
 7351            "( one✅ ",
 7352            "three ",
 7353            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7354            "three ",
 7355            "five ) ˇ",
 7356        ]
 7357        .join("\n")),
 7358    );
 7359
 7360    // Cut with three selections, one of which is full-line.
 7361    cx.set_state(indoc! {"
 7362        1«2ˇ»3
 7363        4ˇ567
 7364        «8ˇ»9"});
 7365    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7366    cx.assert_editor_state(indoc! {"
 7367        1ˇ3
 7368        ˇ9"});
 7369
 7370    // Paste with three selections, noticing how the copied selection that was full-line
 7371    // gets inserted before the second cursor.
 7372    cx.set_state(indoc! {"
 7373        1ˇ3
 7374 7375        «oˇ»ne"});
 7376    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7377    cx.assert_editor_state(indoc! {"
 7378        12ˇ3
 7379        4567
 7380 7381        8ˇne"});
 7382
 7383    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7384    cx.set_state(indoc! {"
 7385        The quick brown
 7386        fox juˇmps over
 7387        the lazy dog"});
 7388    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7389    assert_eq!(
 7390        cx.read_from_clipboard()
 7391            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7392        Some("fox jumps over\n".to_string())
 7393    );
 7394
 7395    // Paste with three selections, noticing how the copied full-line selection is inserted
 7396    // before the empty selections but replaces the selection that is non-empty.
 7397    cx.set_state(indoc! {"
 7398        Tˇhe quick brown
 7399        «foˇ»x jumps over
 7400        tˇhe lazy dog"});
 7401    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7402    cx.assert_editor_state(indoc! {"
 7403        fox jumps over
 7404        Tˇhe quick brown
 7405        fox jumps over
 7406        ˇx jumps over
 7407        fox jumps over
 7408        tˇhe lazy dog"});
 7409}
 7410
 7411#[gpui::test]
 7412async fn test_copy_trim(cx: &mut TestAppContext) {
 7413    init_test(cx, |_| {});
 7414
 7415    let mut cx = EditorTestContext::new(cx).await;
 7416    cx.set_state(
 7417        r#"            «for selection in selections.iter() {
 7418            let mut start = selection.start;
 7419            let mut end = selection.end;
 7420            let is_entire_line = selection.is_empty();
 7421            if is_entire_line {
 7422                start = Point::new(start.row, 0);ˇ»
 7423                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7424            }
 7425        "#,
 7426    );
 7427    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7428    assert_eq!(
 7429        cx.read_from_clipboard()
 7430            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7431        Some(
 7432            "for selection in selections.iter() {
 7433            let mut start = selection.start;
 7434            let mut end = selection.end;
 7435            let is_entire_line = selection.is_empty();
 7436            if is_entire_line {
 7437                start = Point::new(start.row, 0);"
 7438                .to_string()
 7439        ),
 7440        "Regular copying preserves all indentation selected",
 7441    );
 7442    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7443    assert_eq!(
 7444        cx.read_from_clipboard()
 7445            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7446        Some(
 7447            "for selection in selections.iter() {
 7448let mut start = selection.start;
 7449let mut end = selection.end;
 7450let is_entire_line = selection.is_empty();
 7451if is_entire_line {
 7452    start = Point::new(start.row, 0);"
 7453                .to_string()
 7454        ),
 7455        "Copying with stripping should strip all leading whitespaces"
 7456    );
 7457
 7458    cx.set_state(
 7459        r#"       «     for selection in selections.iter() {
 7460            let mut start = selection.start;
 7461            let mut end = selection.end;
 7462            let is_entire_line = selection.is_empty();
 7463            if is_entire_line {
 7464                start = Point::new(start.row, 0);ˇ»
 7465                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7466            }
 7467        "#,
 7468    );
 7469    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7470    assert_eq!(
 7471        cx.read_from_clipboard()
 7472            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7473        Some(
 7474            "     for selection in selections.iter() {
 7475            let mut start = selection.start;
 7476            let mut end = selection.end;
 7477            let is_entire_line = selection.is_empty();
 7478            if is_entire_line {
 7479                start = Point::new(start.row, 0);"
 7480                .to_string()
 7481        ),
 7482        "Regular copying preserves all indentation selected",
 7483    );
 7484    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7485    assert_eq!(
 7486        cx.read_from_clipboard()
 7487            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7488        Some(
 7489            "for selection in selections.iter() {
 7490let mut start = selection.start;
 7491let mut end = selection.end;
 7492let is_entire_line = selection.is_empty();
 7493if is_entire_line {
 7494    start = Point::new(start.row, 0);"
 7495                .to_string()
 7496        ),
 7497        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7498    );
 7499
 7500    cx.set_state(
 7501        r#"       «ˇ     for selection in selections.iter() {
 7502            let mut start = selection.start;
 7503            let mut end = selection.end;
 7504            let is_entire_line = selection.is_empty();
 7505            if is_entire_line {
 7506                start = Point::new(start.row, 0);»
 7507                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7508            }
 7509        "#,
 7510    );
 7511    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7512    assert_eq!(
 7513        cx.read_from_clipboard()
 7514            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7515        Some(
 7516            "     for selection in selections.iter() {
 7517            let mut start = selection.start;
 7518            let mut end = selection.end;
 7519            let is_entire_line = selection.is_empty();
 7520            if is_entire_line {
 7521                start = Point::new(start.row, 0);"
 7522                .to_string()
 7523        ),
 7524        "Regular copying for reverse selection works the same",
 7525    );
 7526    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7527    assert_eq!(
 7528        cx.read_from_clipboard()
 7529            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7530        Some(
 7531            "for selection in selections.iter() {
 7532let mut start = selection.start;
 7533let mut end = selection.end;
 7534let is_entire_line = selection.is_empty();
 7535if is_entire_line {
 7536    start = Point::new(start.row, 0);"
 7537                .to_string()
 7538        ),
 7539        "Copying with stripping for reverse selection works the same"
 7540    );
 7541
 7542    cx.set_state(
 7543        r#"            for selection «in selections.iter() {
 7544            let mut start = selection.start;
 7545            let mut end = selection.end;
 7546            let is_entire_line = selection.is_empty();
 7547            if is_entire_line {
 7548                start = Point::new(start.row, 0);ˇ»
 7549                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7550            }
 7551        "#,
 7552    );
 7553    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7554    assert_eq!(
 7555        cx.read_from_clipboard()
 7556            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7557        Some(
 7558            "in selections.iter() {
 7559            let mut start = selection.start;
 7560            let mut end = selection.end;
 7561            let is_entire_line = selection.is_empty();
 7562            if is_entire_line {
 7563                start = Point::new(start.row, 0);"
 7564                .to_string()
 7565        ),
 7566        "When selecting past the indent, the copying works as usual",
 7567    );
 7568    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7569    assert_eq!(
 7570        cx.read_from_clipboard()
 7571            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7572        Some(
 7573            "in selections.iter() {
 7574            let mut start = selection.start;
 7575            let mut end = selection.end;
 7576            let is_entire_line = selection.is_empty();
 7577            if is_entire_line {
 7578                start = Point::new(start.row, 0);"
 7579                .to_string()
 7580        ),
 7581        "When selecting past the indent, nothing is trimmed"
 7582    );
 7583
 7584    cx.set_state(
 7585        r#"            «for selection in selections.iter() {
 7586            let mut start = selection.start;
 7587
 7588            let mut end = selection.end;
 7589            let is_entire_line = selection.is_empty();
 7590            if is_entire_line {
 7591                start = Point::new(start.row, 0);
 7592ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7593            }
 7594        "#,
 7595    );
 7596    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7597    assert_eq!(
 7598        cx.read_from_clipboard()
 7599            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7600        Some(
 7601            "for selection in selections.iter() {
 7602let mut start = selection.start;
 7603
 7604let mut end = selection.end;
 7605let is_entire_line = selection.is_empty();
 7606if is_entire_line {
 7607    start = Point::new(start.row, 0);
 7608"
 7609            .to_string()
 7610        ),
 7611        "Copying with stripping should ignore empty lines"
 7612    );
 7613}
 7614
 7615#[gpui::test]
 7616async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 7617    init_test(cx, |_| {});
 7618
 7619    let mut cx = EditorTestContext::new(cx).await;
 7620
 7621    cx.set_state(indoc! {"
 7622        «    a
 7623            bˇ»
 7624    "});
 7625    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 7626    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 7627
 7628    assert_eq!(
 7629        cx.read_from_clipboard().and_then(|item| item.text()),
 7630        Some("a\nb\n".to_string())
 7631    );
 7632}
 7633
 7634#[gpui::test]
 7635async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 7636    init_test(cx, |_| {});
 7637
 7638    let fs = FakeFs::new(cx.executor());
 7639    fs.insert_file(
 7640        path!("/file.txt"),
 7641        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 7642    )
 7643    .await;
 7644
 7645    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 7646
 7647    let buffer = project
 7648        .update(cx, |project, cx| {
 7649            project.open_local_buffer(path!("/file.txt"), cx)
 7650        })
 7651        .await
 7652        .unwrap();
 7653
 7654    let multibuffer = cx.new(|cx| {
 7655        let mut multibuffer = MultiBuffer::new(ReadWrite);
 7656        multibuffer.push_excerpts(
 7657            buffer.clone(),
 7658            [ExcerptRange::new(Point::new(2, 0)..Point::new(5, 0))],
 7659            cx,
 7660        );
 7661        multibuffer
 7662    });
 7663
 7664    let (editor, cx) = cx.add_window_view(|window, cx| {
 7665        build_editor_with_project(project.clone(), multibuffer, window, cx)
 7666    });
 7667
 7668    editor.update_in(cx, |editor, window, cx| {
 7669        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 7670
 7671        editor.select_all(&SelectAll, window, cx);
 7672        editor.copy(&Copy, window, cx);
 7673    });
 7674
 7675    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 7676        .read_from_clipboard()
 7677        .and_then(|item| item.entries().first().cloned())
 7678        .and_then(|entry| match entry {
 7679            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 7680            _ => None,
 7681        });
 7682
 7683    let selections = clipboard_selections.expect("should have clipboard selections");
 7684    assert_eq!(selections.len(), 1);
 7685    let selection = &selections[0];
 7686    assert_eq!(
 7687        selection.line_range,
 7688        Some(2..=5),
 7689        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 7690    );
 7691}
 7692
 7693#[gpui::test]
 7694async fn test_paste_multiline(cx: &mut TestAppContext) {
 7695    init_test(cx, |_| {});
 7696
 7697    let mut cx = EditorTestContext::new(cx).await;
 7698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7699
 7700    // Cut an indented block, without the leading whitespace.
 7701    cx.set_state(indoc! {"
 7702        const a: B = (
 7703            c(),
 7704            «d(
 7705                e,
 7706                f
 7707            )ˇ»
 7708        );
 7709    "});
 7710    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7711    cx.assert_editor_state(indoc! {"
 7712        const a: B = (
 7713            c(),
 7714            ˇ
 7715        );
 7716    "});
 7717
 7718    // Paste it at the same position.
 7719    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7720    cx.assert_editor_state(indoc! {"
 7721        const a: B = (
 7722            c(),
 7723            d(
 7724                e,
 7725                f
 7726 7727        );
 7728    "});
 7729
 7730    // Paste it at a line with a lower indent level.
 7731    cx.set_state(indoc! {"
 7732        ˇ
 7733        const a: B = (
 7734            c(),
 7735        );
 7736    "});
 7737    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7738    cx.assert_editor_state(indoc! {"
 7739        d(
 7740            e,
 7741            f
 7742 7743        const a: B = (
 7744            c(),
 7745        );
 7746    "});
 7747
 7748    // Cut an indented block, with the leading whitespace.
 7749    cx.set_state(indoc! {"
 7750        const a: B = (
 7751            c(),
 7752        «    d(
 7753                e,
 7754                f
 7755            )
 7756        ˇ»);
 7757    "});
 7758    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7759    cx.assert_editor_state(indoc! {"
 7760        const a: B = (
 7761            c(),
 7762        ˇ);
 7763    "});
 7764
 7765    // Paste it at the same position.
 7766    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7767    cx.assert_editor_state(indoc! {"
 7768        const a: B = (
 7769            c(),
 7770            d(
 7771                e,
 7772                f
 7773            )
 7774        ˇ);
 7775    "});
 7776
 7777    // Paste it at a line with a higher indent level.
 7778    cx.set_state(indoc! {"
 7779        const a: B = (
 7780            c(),
 7781            d(
 7782                e,
 7783 7784            )
 7785        );
 7786    "});
 7787    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7788    cx.assert_editor_state(indoc! {"
 7789        const a: B = (
 7790            c(),
 7791            d(
 7792                e,
 7793                f    d(
 7794                    e,
 7795                    f
 7796                )
 7797        ˇ
 7798            )
 7799        );
 7800    "});
 7801
 7802    // Copy an indented block, starting mid-line
 7803    cx.set_state(indoc! {"
 7804        const a: B = (
 7805            c(),
 7806            somethin«g(
 7807                e,
 7808                f
 7809            )ˇ»
 7810        );
 7811    "});
 7812    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7813
 7814    // Paste it on a line with a lower indent level
 7815    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7816    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7817    cx.assert_editor_state(indoc! {"
 7818        const a: B = (
 7819            c(),
 7820            something(
 7821                e,
 7822                f
 7823            )
 7824        );
 7825        g(
 7826            e,
 7827            f
 7828"});
 7829}
 7830
 7831#[gpui::test]
 7832async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7833    init_test(cx, |_| {});
 7834
 7835    cx.write_to_clipboard(ClipboardItem::new_string(
 7836        "    d(\n        e\n    );\n".into(),
 7837    ));
 7838
 7839    let mut cx = EditorTestContext::new(cx).await;
 7840    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7841
 7842    cx.set_state(indoc! {"
 7843        fn a() {
 7844            b();
 7845            if c() {
 7846                ˇ
 7847            }
 7848        }
 7849    "});
 7850
 7851    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7852    cx.assert_editor_state(indoc! {"
 7853        fn a() {
 7854            b();
 7855            if c() {
 7856                d(
 7857                    e
 7858                );
 7859        ˇ
 7860            }
 7861        }
 7862    "});
 7863
 7864    cx.set_state(indoc! {"
 7865        fn a() {
 7866            b();
 7867            ˇ
 7868        }
 7869    "});
 7870
 7871    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7872    cx.assert_editor_state(indoc! {"
 7873        fn a() {
 7874            b();
 7875            d(
 7876                e
 7877            );
 7878        ˇ
 7879        }
 7880    "});
 7881}
 7882
 7883#[gpui::test]
 7884fn test_select_all(cx: &mut TestAppContext) {
 7885    init_test(cx, |_| {});
 7886
 7887    let editor = cx.add_window(|window, cx| {
 7888        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7889        build_editor(buffer, window, cx)
 7890    });
 7891    _ = editor.update(cx, |editor, window, cx| {
 7892        editor.select_all(&SelectAll, window, cx);
 7893        assert_eq!(
 7894            display_ranges(editor, cx),
 7895            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7896        );
 7897    });
 7898}
 7899
 7900#[gpui::test]
 7901fn test_select_line(cx: &mut TestAppContext) {
 7902    init_test(cx, |_| {});
 7903
 7904    let editor = cx.add_window(|window, cx| {
 7905        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7906        build_editor(buffer, window, cx)
 7907    });
 7908    _ = editor.update(cx, |editor, window, cx| {
 7909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7910            s.select_display_ranges([
 7911                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7912                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7913                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7914                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7915            ])
 7916        });
 7917        editor.select_line(&SelectLine, window, cx);
 7918        // Adjacent line selections should NOT merge (only overlapping ones do)
 7919        assert_eq!(
 7920            display_ranges(editor, cx),
 7921            vec![
 7922                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7923                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7924                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7925            ]
 7926        );
 7927    });
 7928
 7929    _ = editor.update(cx, |editor, window, cx| {
 7930        editor.select_line(&SelectLine, window, cx);
 7931        assert_eq!(
 7932            display_ranges(editor, cx),
 7933            vec![
 7934                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7936            ]
 7937        );
 7938    });
 7939
 7940    _ = editor.update(cx, |editor, window, cx| {
 7941        editor.select_line(&SelectLine, window, cx);
 7942        // Adjacent but not overlapping, so they stay separate
 7943        assert_eq!(
 7944            display_ranges(editor, cx),
 7945            vec![
 7946                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7947                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7948            ]
 7949        );
 7950    });
 7951}
 7952
 7953#[gpui::test]
 7954async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7955    init_test(cx, |_| {});
 7956    let mut cx = EditorTestContext::new(cx).await;
 7957
 7958    #[track_caller]
 7959    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7960        cx.set_state(initial_state);
 7961        cx.update_editor(|e, window, cx| {
 7962            e.split_selection_into_lines(&Default::default(), window, cx)
 7963        });
 7964        cx.assert_editor_state(expected_state);
 7965    }
 7966
 7967    // Selection starts and ends at the middle of lines, left-to-right
 7968    test(
 7969        &mut cx,
 7970        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7971        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7972    );
 7973    // Same thing, right-to-left
 7974    test(
 7975        &mut cx,
 7976        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7977        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7978    );
 7979
 7980    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7981    test(
 7982        &mut cx,
 7983        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7984        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7985    );
 7986    // Same thing, right-to-left
 7987    test(
 7988        &mut cx,
 7989        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7990        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7991    );
 7992
 7993    // Whole buffer, left-to-right, last line ends with newline
 7994    test(
 7995        &mut cx,
 7996        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7997        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7998    );
 7999    // Same thing, right-to-left
 8000    test(
 8001        &mut cx,
 8002        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8003        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8004    );
 8005
 8006    // Starts at the end of a line, ends at the start of another
 8007    test(
 8008        &mut cx,
 8009        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8010        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8011    );
 8012}
 8013
 8014#[gpui::test]
 8015async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8016    init_test(cx, |_| {});
 8017
 8018    let editor = cx.add_window(|window, cx| {
 8019        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8020        build_editor(buffer, window, cx)
 8021    });
 8022
 8023    // setup
 8024    _ = editor.update(cx, |editor, window, cx| {
 8025        editor.fold_creases(
 8026            vec![
 8027                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8028                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8029                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8030            ],
 8031            true,
 8032            window,
 8033            cx,
 8034        );
 8035        assert_eq!(
 8036            editor.display_text(cx),
 8037            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8038        );
 8039    });
 8040
 8041    _ = editor.update(cx, |editor, window, cx| {
 8042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8043            s.select_display_ranges([
 8044                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8045                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8046                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8047                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8048            ])
 8049        });
 8050        editor.split_selection_into_lines(&Default::default(), window, cx);
 8051        assert_eq!(
 8052            editor.display_text(cx),
 8053            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8054        );
 8055    });
 8056    EditorTestContext::for_editor(editor, cx)
 8057        .await
 8058        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8059
 8060    _ = editor.update(cx, |editor, window, cx| {
 8061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8062            s.select_display_ranges([
 8063                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8064            ])
 8065        });
 8066        editor.split_selection_into_lines(&Default::default(), window, cx);
 8067        assert_eq!(
 8068            editor.display_text(cx),
 8069            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8070        );
 8071        assert_eq!(
 8072            display_ranges(editor, cx),
 8073            [
 8074                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8075                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8076                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8077                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8078                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8079                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8080                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8081            ]
 8082        );
 8083    });
 8084    EditorTestContext::for_editor(editor, cx)
 8085        .await
 8086        .assert_editor_state(
 8087            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8088        );
 8089}
 8090
 8091#[gpui::test]
 8092async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8093    init_test(cx, |_| {});
 8094
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"abc
 8099           defˇghi
 8100
 8101           jk
 8102           nlmo
 8103           "#
 8104    ));
 8105
 8106    cx.update_editor(|editor, window, cx| {
 8107        editor.add_selection_above(&Default::default(), window, cx);
 8108    });
 8109
 8110    cx.assert_editor_state(indoc!(
 8111        r#"abcˇ
 8112           defˇghi
 8113
 8114           jk
 8115           nlmo
 8116           "#
 8117    ));
 8118
 8119    cx.update_editor(|editor, window, cx| {
 8120        editor.add_selection_above(&Default::default(), window, cx);
 8121    });
 8122
 8123    cx.assert_editor_state(indoc!(
 8124        r#"abcˇ
 8125            defˇghi
 8126
 8127            jk
 8128            nlmo
 8129            "#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_below(&Default::default(), window, cx);
 8134    });
 8135
 8136    cx.assert_editor_state(indoc!(
 8137        r#"abc
 8138           defˇghi
 8139
 8140           jk
 8141           nlmo
 8142           "#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.undo_selection(&Default::default(), window, cx);
 8147    });
 8148
 8149    cx.assert_editor_state(indoc!(
 8150        r#"abcˇ
 8151           defˇghi
 8152
 8153           jk
 8154           nlmo
 8155           "#
 8156    ));
 8157
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.redo_selection(&Default::default(), window, cx);
 8160    });
 8161
 8162    cx.assert_editor_state(indoc!(
 8163        r#"abc
 8164           defˇghi
 8165
 8166           jk
 8167           nlmo
 8168           "#
 8169    ));
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.add_selection_below(&Default::default(), window, cx);
 8173    });
 8174
 8175    cx.assert_editor_state(indoc!(
 8176        r#"abc
 8177           defˇghi
 8178           ˇ
 8179           jk
 8180           nlmo
 8181           "#
 8182    ));
 8183
 8184    cx.update_editor(|editor, window, cx| {
 8185        editor.add_selection_below(&Default::default(), window, cx);
 8186    });
 8187
 8188    cx.assert_editor_state(indoc!(
 8189        r#"abc
 8190           defˇghi
 8191           ˇ
 8192           jkˇ
 8193           nlmo
 8194           "#
 8195    ));
 8196
 8197    cx.update_editor(|editor, window, cx| {
 8198        editor.add_selection_below(&Default::default(), window, cx);
 8199    });
 8200
 8201    cx.assert_editor_state(indoc!(
 8202        r#"abc
 8203           defˇghi
 8204           ˇ
 8205           jkˇ
 8206           nlmˇo
 8207           "#
 8208    ));
 8209
 8210    cx.update_editor(|editor, window, cx| {
 8211        editor.add_selection_below(&Default::default(), window, cx);
 8212    });
 8213
 8214    cx.assert_editor_state(indoc!(
 8215        r#"abc
 8216           defˇghi
 8217           ˇ
 8218           jkˇ
 8219           nlmˇo
 8220           ˇ"#
 8221    ));
 8222
 8223    // change selections
 8224    cx.set_state(indoc!(
 8225        r#"abc
 8226           def«ˇg»hi
 8227
 8228           jk
 8229           nlmo
 8230           "#
 8231    ));
 8232
 8233    cx.update_editor(|editor, window, cx| {
 8234        editor.add_selection_below(&Default::default(), window, cx);
 8235    });
 8236
 8237    cx.assert_editor_state(indoc!(
 8238        r#"abc
 8239           def«ˇg»hi
 8240
 8241           jk
 8242           nlm«ˇo»
 8243           "#
 8244    ));
 8245
 8246    cx.update_editor(|editor, window, cx| {
 8247        editor.add_selection_below(&Default::default(), window, cx);
 8248    });
 8249
 8250    cx.assert_editor_state(indoc!(
 8251        r#"abc
 8252           def«ˇg»hi
 8253
 8254           jk
 8255           nlm«ˇo»
 8256           "#
 8257    ));
 8258
 8259    cx.update_editor(|editor, window, cx| {
 8260        editor.add_selection_above(&Default::default(), window, cx);
 8261    });
 8262
 8263    cx.assert_editor_state(indoc!(
 8264        r#"abc
 8265           def«ˇg»hi
 8266
 8267           jk
 8268           nlmo
 8269           "#
 8270    ));
 8271
 8272    cx.update_editor(|editor, window, cx| {
 8273        editor.add_selection_above(&Default::default(), window, cx);
 8274    });
 8275
 8276    cx.assert_editor_state(indoc!(
 8277        r#"abc
 8278           def«ˇg»hi
 8279
 8280           jk
 8281           nlmo
 8282           "#
 8283    ));
 8284
 8285    // Change selections again
 8286    cx.set_state(indoc!(
 8287        r#"a«bc
 8288           defgˇ»hi
 8289
 8290           jk
 8291           nlmo
 8292           "#
 8293    ));
 8294
 8295    cx.update_editor(|editor, window, cx| {
 8296        editor.add_selection_below(&Default::default(), window, cx);
 8297    });
 8298
 8299    cx.assert_editor_state(indoc!(
 8300        r#"a«bcˇ»
 8301           d«efgˇ»hi
 8302
 8303           j«kˇ»
 8304           nlmo
 8305           "#
 8306    ));
 8307
 8308    cx.update_editor(|editor, window, cx| {
 8309        editor.add_selection_below(&Default::default(), window, cx);
 8310    });
 8311    cx.assert_editor_state(indoc!(
 8312        r#"a«bcˇ»
 8313           d«efgˇ»hi
 8314
 8315           j«kˇ»
 8316           n«lmoˇ»
 8317           "#
 8318    ));
 8319    cx.update_editor(|editor, window, cx| {
 8320        editor.add_selection_above(&Default::default(), window, cx);
 8321    });
 8322
 8323    cx.assert_editor_state(indoc!(
 8324        r#"a«bcˇ»
 8325           d«efgˇ»hi
 8326
 8327           j«kˇ»
 8328           nlmo
 8329           "#
 8330    ));
 8331
 8332    // Change selections again
 8333    cx.set_state(indoc!(
 8334        r#"abc
 8335           d«ˇefghi
 8336
 8337           jk
 8338           nlm»o
 8339           "#
 8340    ));
 8341
 8342    cx.update_editor(|editor, window, cx| {
 8343        editor.add_selection_above(&Default::default(), window, cx);
 8344    });
 8345
 8346    cx.assert_editor_state(indoc!(
 8347        r#"a«ˇbc»
 8348           d«ˇef»ghi
 8349
 8350           j«ˇk»
 8351           n«ˇlm»o
 8352           "#
 8353    ));
 8354
 8355    cx.update_editor(|editor, window, cx| {
 8356        editor.add_selection_below(&Default::default(), window, cx);
 8357    });
 8358
 8359    cx.assert_editor_state(indoc!(
 8360        r#"abc
 8361           d«ˇef»ghi
 8362
 8363           j«ˇk»
 8364           n«ˇlm»o
 8365           "#
 8366    ));
 8367}
 8368
 8369#[gpui::test]
 8370async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8371    init_test(cx, |_| {});
 8372    let mut cx = EditorTestContext::new(cx).await;
 8373
 8374    cx.set_state(indoc!(
 8375        r#"line onˇe
 8376           liˇne two
 8377           line three
 8378           line four"#
 8379    ));
 8380
 8381    cx.update_editor(|editor, window, cx| {
 8382        editor.add_selection_below(&Default::default(), window, cx);
 8383    });
 8384
 8385    // test multiple cursors expand in the same direction
 8386    cx.assert_editor_state(indoc!(
 8387        r#"line onˇe
 8388           liˇne twˇo
 8389           liˇne three
 8390           line four"#
 8391    ));
 8392
 8393    cx.update_editor(|editor, window, cx| {
 8394        editor.add_selection_below(&Default::default(), window, cx);
 8395    });
 8396
 8397    cx.update_editor(|editor, window, cx| {
 8398        editor.add_selection_below(&Default::default(), window, cx);
 8399    });
 8400
 8401    // test multiple cursors expand below overflow
 8402    cx.assert_editor_state(indoc!(
 8403        r#"line onˇe
 8404           liˇne twˇo
 8405           liˇne thˇree
 8406           liˇne foˇur"#
 8407    ));
 8408
 8409    cx.update_editor(|editor, window, cx| {
 8410        editor.add_selection_above(&Default::default(), window, cx);
 8411    });
 8412
 8413    // test multiple cursors retrieves back correctly
 8414    cx.assert_editor_state(indoc!(
 8415        r#"line onˇe
 8416           liˇne twˇo
 8417           liˇne thˇree
 8418           line four"#
 8419    ));
 8420
 8421    cx.update_editor(|editor, window, cx| {
 8422        editor.add_selection_above(&Default::default(), window, cx);
 8423    });
 8424
 8425    cx.update_editor(|editor, window, cx| {
 8426        editor.add_selection_above(&Default::default(), window, cx);
 8427    });
 8428
 8429    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8430    cx.assert_editor_state(indoc!(
 8431        r#"liˇne onˇe
 8432           liˇne two
 8433           line three
 8434           line four"#
 8435    ));
 8436
 8437    cx.update_editor(|editor, window, cx| {
 8438        editor.undo_selection(&Default::default(), window, cx);
 8439    });
 8440
 8441    // test undo
 8442    cx.assert_editor_state(indoc!(
 8443        r#"line onˇe
 8444           liˇne twˇo
 8445           line three
 8446           line four"#
 8447    ));
 8448
 8449    cx.update_editor(|editor, window, cx| {
 8450        editor.redo_selection(&Default::default(), window, cx);
 8451    });
 8452
 8453    // test redo
 8454    cx.assert_editor_state(indoc!(
 8455        r#"liˇne onˇe
 8456           liˇne two
 8457           line three
 8458           line four"#
 8459    ));
 8460
 8461    cx.set_state(indoc!(
 8462        r#"abcd
 8463           ef«ghˇ»
 8464           ijkl
 8465           «mˇ»nop"#
 8466    ));
 8467
 8468    cx.update_editor(|editor, window, cx| {
 8469        editor.add_selection_above(&Default::default(), window, cx);
 8470    });
 8471
 8472    // test multiple selections expand in the same direction
 8473    cx.assert_editor_state(indoc!(
 8474        r#"ab«cdˇ»
 8475           ef«ghˇ»
 8476           «iˇ»jkl
 8477           «mˇ»nop"#
 8478    ));
 8479
 8480    cx.update_editor(|editor, window, cx| {
 8481        editor.add_selection_above(&Default::default(), window, cx);
 8482    });
 8483
 8484    // test multiple selection upward overflow
 8485    cx.assert_editor_state(indoc!(
 8486        r#"ab«cdˇ»
 8487           «eˇ»f«ghˇ»
 8488           «iˇ»jkl
 8489           «mˇ»nop"#
 8490    ));
 8491
 8492    cx.update_editor(|editor, window, cx| {
 8493        editor.add_selection_below(&Default::default(), window, cx);
 8494    });
 8495
 8496    // test multiple selection retrieves back correctly
 8497    cx.assert_editor_state(indoc!(
 8498        r#"abcd
 8499           ef«ghˇ»
 8500           «iˇ»jkl
 8501           «mˇ»nop"#
 8502    ));
 8503
 8504    cx.update_editor(|editor, window, cx| {
 8505        editor.add_selection_below(&Default::default(), window, cx);
 8506    });
 8507
 8508    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8509    cx.assert_editor_state(indoc!(
 8510        r#"abcd
 8511           ef«ghˇ»
 8512           ij«klˇ»
 8513           «mˇ»nop"#
 8514    ));
 8515
 8516    cx.update_editor(|editor, window, cx| {
 8517        editor.undo_selection(&Default::default(), window, cx);
 8518    });
 8519
 8520    // test undo
 8521    cx.assert_editor_state(indoc!(
 8522        r#"abcd
 8523           ef«ghˇ»
 8524           «iˇ»jkl
 8525           «mˇ»nop"#
 8526    ));
 8527
 8528    cx.update_editor(|editor, window, cx| {
 8529        editor.redo_selection(&Default::default(), window, cx);
 8530    });
 8531
 8532    // test redo
 8533    cx.assert_editor_state(indoc!(
 8534        r#"abcd
 8535           ef«ghˇ»
 8536           ij«klˇ»
 8537           «mˇ»nop"#
 8538    ));
 8539}
 8540
 8541#[gpui::test]
 8542async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8543    init_test(cx, |_| {});
 8544    let mut cx = EditorTestContext::new(cx).await;
 8545
 8546    cx.set_state(indoc!(
 8547        r#"line onˇe
 8548           liˇne two
 8549           line three
 8550           line four"#
 8551    ));
 8552
 8553    cx.update_editor(|editor, window, cx| {
 8554        editor.add_selection_below(&Default::default(), window, cx);
 8555        editor.add_selection_below(&Default::default(), window, cx);
 8556        editor.add_selection_below(&Default::default(), window, cx);
 8557    });
 8558
 8559    // initial state with two multi cursor groups
 8560    cx.assert_editor_state(indoc!(
 8561        r#"line onˇe
 8562           liˇne twˇo
 8563           liˇne thˇree
 8564           liˇne foˇur"#
 8565    ));
 8566
 8567    // add single cursor in middle - simulate opt click
 8568    cx.update_editor(|editor, window, cx| {
 8569        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8570        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8571        editor.end_selection(window, cx);
 8572    });
 8573
 8574    cx.assert_editor_state(indoc!(
 8575        r#"line onˇe
 8576           liˇne twˇo
 8577           liˇneˇ thˇree
 8578           liˇne foˇur"#
 8579    ));
 8580
 8581    cx.update_editor(|editor, window, cx| {
 8582        editor.add_selection_above(&Default::default(), window, cx);
 8583    });
 8584
 8585    // test new added selection expands above and existing selection shrinks
 8586    cx.assert_editor_state(indoc!(
 8587        r#"line onˇe
 8588           liˇneˇ twˇo
 8589           liˇneˇ thˇree
 8590           line four"#
 8591    ));
 8592
 8593    cx.update_editor(|editor, window, cx| {
 8594        editor.add_selection_above(&Default::default(), window, cx);
 8595    });
 8596
 8597    // test new added selection expands above and existing selection shrinks
 8598    cx.assert_editor_state(indoc!(
 8599        r#"lineˇ onˇe
 8600           liˇneˇ twˇo
 8601           lineˇ three
 8602           line four"#
 8603    ));
 8604
 8605    // intial state with two selection groups
 8606    cx.set_state(indoc!(
 8607        r#"abcd
 8608           ef«ghˇ»
 8609           ijkl
 8610           «mˇ»nop"#
 8611    ));
 8612
 8613    cx.update_editor(|editor, window, cx| {
 8614        editor.add_selection_above(&Default::default(), window, cx);
 8615        editor.add_selection_above(&Default::default(), window, cx);
 8616    });
 8617
 8618    cx.assert_editor_state(indoc!(
 8619        r#"ab«cdˇ»
 8620           «eˇ»f«ghˇ»
 8621           «iˇ»jkl
 8622           «mˇ»nop"#
 8623    ));
 8624
 8625    // add single selection in middle - simulate opt drag
 8626    cx.update_editor(|editor, window, cx| {
 8627        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8628        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8629        editor.update_selection(
 8630            DisplayPoint::new(DisplayRow(2), 4),
 8631            0,
 8632            gpui::Point::<f32>::default(),
 8633            window,
 8634            cx,
 8635        );
 8636        editor.end_selection(window, cx);
 8637    });
 8638
 8639    cx.assert_editor_state(indoc!(
 8640        r#"ab«cdˇ»
 8641           «eˇ»f«ghˇ»
 8642           «iˇ»jk«lˇ»
 8643           «mˇ»nop"#
 8644    ));
 8645
 8646    cx.update_editor(|editor, window, cx| {
 8647        editor.add_selection_below(&Default::default(), window, cx);
 8648    });
 8649
 8650    // test new added selection expands below, others shrinks from above
 8651    cx.assert_editor_state(indoc!(
 8652        r#"abcd
 8653           ef«ghˇ»
 8654           «iˇ»jk«lˇ»
 8655           «mˇ»no«pˇ»"#
 8656    ));
 8657}
 8658
 8659#[gpui::test]
 8660async fn test_select_next(cx: &mut TestAppContext) {
 8661    init_test(cx, |_| {});
 8662    let mut cx = EditorTestContext::new(cx).await;
 8663
 8664    // Enable case sensitive search.
 8665    update_test_editor_settings(&mut cx, |settings| {
 8666        let mut search_settings = SearchSettingsContent::default();
 8667        search_settings.case_sensitive = Some(true);
 8668        settings.search = Some(search_settings);
 8669    });
 8670
 8671    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8672
 8673    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8674        .unwrap();
 8675    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8676
 8677    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8678        .unwrap();
 8679    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8680
 8681    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8682    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8683
 8684    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8685    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8686
 8687    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8688        .unwrap();
 8689    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8690
 8691    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8692        .unwrap();
 8693    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8694
 8695    // Test selection direction should be preserved
 8696    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8697
 8698    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8699        .unwrap();
 8700    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8701
 8702    // Test case sensitivity
 8703    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8704    cx.update_editor(|e, window, cx| {
 8705        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8706    });
 8707    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8708
 8709    // Disable case sensitive search.
 8710    update_test_editor_settings(&mut cx, |settings| {
 8711        let mut search_settings = SearchSettingsContent::default();
 8712        search_settings.case_sensitive = Some(false);
 8713        settings.search = Some(search_settings);
 8714    });
 8715
 8716    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8717    cx.update_editor(|e, window, cx| {
 8718        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8719        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8720    });
 8721    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8722}
 8723
 8724#[gpui::test]
 8725async fn test_select_all_matches(cx: &mut TestAppContext) {
 8726    init_test(cx, |_| {});
 8727    let mut cx = EditorTestContext::new(cx).await;
 8728
 8729    // Enable case sensitive search.
 8730    update_test_editor_settings(&mut cx, |settings| {
 8731        let mut search_settings = SearchSettingsContent::default();
 8732        search_settings.case_sensitive = Some(true);
 8733        settings.search = Some(search_settings);
 8734    });
 8735
 8736    // Test caret-only selections
 8737    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8738    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8739        .unwrap();
 8740    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8741
 8742    // Test left-to-right selections
 8743    cx.set_state("abc\n«abcˇ»\nabc");
 8744    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8745        .unwrap();
 8746    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8747
 8748    // Test right-to-left selections
 8749    cx.set_state("abc\n«ˇabc»\nabc");
 8750    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8751        .unwrap();
 8752    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8753
 8754    // Test selecting whitespace with caret selection
 8755    cx.set_state("abc\nˇ   abc\nabc");
 8756    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8757        .unwrap();
 8758    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8759
 8760    // Test selecting whitespace with left-to-right selection
 8761    cx.set_state("abc\n«ˇ  »abc\nabc");
 8762    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8763        .unwrap();
 8764    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8765
 8766    // Test no matches with right-to-left selection
 8767    cx.set_state("abc\n«  ˇ»abc\nabc");
 8768    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8769        .unwrap();
 8770    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8771
 8772    // Test with a single word and clip_at_line_ends=true (#29823)
 8773    cx.set_state("aˇbc");
 8774    cx.update_editor(|e, window, cx| {
 8775        e.set_clip_at_line_ends(true, cx);
 8776        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8777        e.set_clip_at_line_ends(false, cx);
 8778    });
 8779    cx.assert_editor_state("«abcˇ»");
 8780
 8781    // Test case sensitivity
 8782    cx.set_state("fˇoo\nFOO\nFoo");
 8783    cx.update_editor(|e, window, cx| {
 8784        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8785    });
 8786    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8787
 8788    // Disable case sensitive search.
 8789    update_test_editor_settings(&mut cx, |settings| {
 8790        let mut search_settings = SearchSettingsContent::default();
 8791        search_settings.case_sensitive = Some(false);
 8792        settings.search = Some(search_settings);
 8793    });
 8794
 8795    cx.set_state("fˇoo\nFOO\nFoo");
 8796    cx.update_editor(|e, window, cx| {
 8797        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8798    });
 8799    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8800}
 8801
 8802#[gpui::test]
 8803async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8804    init_test(cx, |_| {});
 8805
 8806    let mut cx = EditorTestContext::new(cx).await;
 8807
 8808    let large_body_1 = "\nd".repeat(200);
 8809    let large_body_2 = "\ne".repeat(200);
 8810
 8811    cx.set_state(&format!(
 8812        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8813    ));
 8814    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8815        let scroll_position = editor.scroll_position(cx);
 8816        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8817        scroll_position
 8818    });
 8819
 8820    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8821        .unwrap();
 8822    cx.assert_editor_state(&format!(
 8823        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8824    ));
 8825    let scroll_position_after_selection =
 8826        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8827    assert_eq!(
 8828        initial_scroll_position, scroll_position_after_selection,
 8829        "Scroll position should not change after selecting all matches"
 8830    );
 8831}
 8832
 8833#[gpui::test]
 8834async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8835    init_test(cx, |_| {});
 8836
 8837    let mut cx = EditorLspTestContext::new_rust(
 8838        lsp::ServerCapabilities {
 8839            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8840            ..Default::default()
 8841        },
 8842        cx,
 8843    )
 8844    .await;
 8845
 8846    cx.set_state(indoc! {"
 8847        line 1
 8848        line 2
 8849        linˇe 3
 8850        line 4
 8851        line 5
 8852    "});
 8853
 8854    // Make an edit
 8855    cx.update_editor(|editor, window, cx| {
 8856        editor.handle_input("X", window, cx);
 8857    });
 8858
 8859    // Move cursor to a different position
 8860    cx.update_editor(|editor, window, cx| {
 8861        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8862            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8863        });
 8864    });
 8865
 8866    cx.assert_editor_state(indoc! {"
 8867        line 1
 8868        line 2
 8869        linXe 3
 8870        line 4
 8871        liˇne 5
 8872    "});
 8873
 8874    cx.lsp
 8875        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8876            Ok(Some(vec![lsp::TextEdit::new(
 8877                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8878                "PREFIX ".to_string(),
 8879            )]))
 8880        });
 8881
 8882    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8883        .unwrap()
 8884        .await
 8885        .unwrap();
 8886
 8887    cx.assert_editor_state(indoc! {"
 8888        PREFIX line 1
 8889        line 2
 8890        linXe 3
 8891        line 4
 8892        liˇne 5
 8893    "});
 8894
 8895    // Undo formatting
 8896    cx.update_editor(|editor, window, cx| {
 8897        editor.undo(&Default::default(), window, cx);
 8898    });
 8899
 8900    // Verify cursor moved back to position after edit
 8901    cx.assert_editor_state(indoc! {"
 8902        line 1
 8903        line 2
 8904        linXˇe 3
 8905        line 4
 8906        line 5
 8907    "});
 8908}
 8909
 8910#[gpui::test]
 8911async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8912    init_test(cx, |_| {});
 8913
 8914    let mut cx = EditorTestContext::new(cx).await;
 8915
 8916    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8917    cx.update_editor(|editor, window, cx| {
 8918        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8919    });
 8920
 8921    cx.set_state(indoc! {"
 8922        line 1
 8923        line 2
 8924        linˇe 3
 8925        line 4
 8926        line 5
 8927        line 6
 8928        line 7
 8929        line 8
 8930        line 9
 8931        line 10
 8932    "});
 8933
 8934    let snapshot = cx.buffer_snapshot();
 8935    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8936
 8937    cx.update(|_, cx| {
 8938        provider.update(cx, |provider, _| {
 8939            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8940                id: None,
 8941                edits: vec![(edit_position..edit_position, "X".into())],
 8942                edit_preview: None,
 8943            }))
 8944        })
 8945    });
 8946
 8947    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8948    cx.update_editor(|editor, window, cx| {
 8949        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8950    });
 8951
 8952    cx.assert_editor_state(indoc! {"
 8953        line 1
 8954        line 2
 8955        lineXˇ 3
 8956        line 4
 8957        line 5
 8958        line 6
 8959        line 7
 8960        line 8
 8961        line 9
 8962        line 10
 8963    "});
 8964
 8965    cx.update_editor(|editor, window, cx| {
 8966        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8967            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8968        });
 8969    });
 8970
 8971    cx.assert_editor_state(indoc! {"
 8972        line 1
 8973        line 2
 8974        lineX 3
 8975        line 4
 8976        line 5
 8977        line 6
 8978        line 7
 8979        line 8
 8980        line 9
 8981        liˇne 10
 8982    "});
 8983
 8984    cx.update_editor(|editor, window, cx| {
 8985        editor.undo(&Default::default(), window, cx);
 8986    });
 8987
 8988    cx.assert_editor_state(indoc! {"
 8989        line 1
 8990        line 2
 8991        lineˇ 3
 8992        line 4
 8993        line 5
 8994        line 6
 8995        line 7
 8996        line 8
 8997        line 9
 8998        line 10
 8999    "});
 9000}
 9001
 9002#[gpui::test]
 9003async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9004    init_test(cx, |_| {});
 9005
 9006    let mut cx = EditorTestContext::new(cx).await;
 9007    cx.set_state(
 9008        r#"let foo = 2;
 9009lˇet foo = 2;
 9010let fooˇ = 2;
 9011let foo = 2;
 9012let foo = ˇ2;"#,
 9013    );
 9014
 9015    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9016        .unwrap();
 9017    cx.assert_editor_state(
 9018        r#"let foo = 2;
 9019«letˇ» foo = 2;
 9020let «fooˇ» = 2;
 9021let foo = 2;
 9022let foo = «2ˇ»;"#,
 9023    );
 9024
 9025    // noop for multiple selections with different contents
 9026    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9027        .unwrap();
 9028    cx.assert_editor_state(
 9029        r#"let foo = 2;
 9030«letˇ» foo = 2;
 9031let «fooˇ» = 2;
 9032let foo = 2;
 9033let foo = «2ˇ»;"#,
 9034    );
 9035
 9036    // Test last selection direction should be preserved
 9037    cx.set_state(
 9038        r#"let foo = 2;
 9039let foo = 2;
 9040let «fooˇ» = 2;
 9041let «ˇfoo» = 2;
 9042let foo = 2;"#,
 9043    );
 9044
 9045    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9046        .unwrap();
 9047    cx.assert_editor_state(
 9048        r#"let foo = 2;
 9049let foo = 2;
 9050let «fooˇ» = 2;
 9051let «ˇfoo» = 2;
 9052let «ˇfoo» = 2;"#,
 9053    );
 9054}
 9055
 9056#[gpui::test]
 9057async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9058    init_test(cx, |_| {});
 9059
 9060    let mut cx =
 9061        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 9062
 9063    cx.assert_editor_state(indoc! {"
 9064        ˇbbb
 9065        ccc
 9066
 9067        bbb
 9068        ccc
 9069        "});
 9070    cx.dispatch_action(SelectPrevious::default());
 9071    cx.assert_editor_state(indoc! {"
 9072                «bbbˇ»
 9073                ccc
 9074
 9075                bbb
 9076                ccc
 9077                "});
 9078    cx.dispatch_action(SelectPrevious::default());
 9079    cx.assert_editor_state(indoc! {"
 9080                «bbbˇ»
 9081                ccc
 9082
 9083                «bbbˇ»
 9084                ccc
 9085                "});
 9086}
 9087
 9088#[gpui::test]
 9089async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9090    init_test(cx, |_| {});
 9091
 9092    let mut cx = EditorTestContext::new(cx).await;
 9093    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9094
 9095    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9096        .unwrap();
 9097    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9098
 9099    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9100        .unwrap();
 9101    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9102
 9103    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9104    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9105
 9106    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9107    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9108
 9109    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9110        .unwrap();
 9111    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9112
 9113    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9114        .unwrap();
 9115    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9116}
 9117
 9118#[gpui::test]
 9119async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9120    init_test(cx, |_| {});
 9121
 9122    let mut cx = EditorTestContext::new(cx).await;
 9123    cx.set_state("");
 9124
 9125    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9126        .unwrap();
 9127    cx.assert_editor_state("«aˇ»");
 9128    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9129        .unwrap();
 9130    cx.assert_editor_state("«aˇ»");
 9131}
 9132
 9133#[gpui::test]
 9134async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9135    init_test(cx, |_| {});
 9136
 9137    let mut cx = EditorTestContext::new(cx).await;
 9138    cx.set_state(
 9139        r#"let foo = 2;
 9140lˇet foo = 2;
 9141let fooˇ = 2;
 9142let foo = 2;
 9143let foo = ˇ2;"#,
 9144    );
 9145
 9146    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9147        .unwrap();
 9148    cx.assert_editor_state(
 9149        r#"let foo = 2;
 9150«letˇ» foo = 2;
 9151let «fooˇ» = 2;
 9152let foo = 2;
 9153let foo = «2ˇ»;"#,
 9154    );
 9155
 9156    // noop for multiple selections with different contents
 9157    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9158        .unwrap();
 9159    cx.assert_editor_state(
 9160        r#"let foo = 2;
 9161«letˇ» foo = 2;
 9162let «fooˇ» = 2;
 9163let foo = 2;
 9164let foo = «2ˇ»;"#,
 9165    );
 9166}
 9167
 9168#[gpui::test]
 9169async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9170    init_test(cx, |_| {});
 9171    let mut cx = EditorTestContext::new(cx).await;
 9172
 9173    // Enable case sensitive search.
 9174    update_test_editor_settings(&mut cx, |settings| {
 9175        let mut search_settings = SearchSettingsContent::default();
 9176        search_settings.case_sensitive = Some(true);
 9177        settings.search = Some(search_settings);
 9178    });
 9179
 9180    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9181
 9182    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9183        .unwrap();
 9184    // selection direction is preserved
 9185    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9186
 9187    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9188        .unwrap();
 9189    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9190
 9191    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9192    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9193
 9194    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9195    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9196
 9197    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9198        .unwrap();
 9199    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9200
 9201    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9202        .unwrap();
 9203    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9204
 9205    // Test case sensitivity
 9206    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9207    cx.update_editor(|e, window, cx| {
 9208        e.select_previous(&SelectPrevious::default(), window, cx)
 9209            .unwrap();
 9210        e.select_previous(&SelectPrevious::default(), window, cx)
 9211            .unwrap();
 9212    });
 9213    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9214
 9215    // Disable case sensitive search.
 9216    update_test_editor_settings(&mut cx, |settings| {
 9217        let mut search_settings = SearchSettingsContent::default();
 9218        search_settings.case_sensitive = Some(false);
 9219        settings.search = Some(search_settings);
 9220    });
 9221
 9222    cx.set_state("foo\nFOO\n«ˇFoo»");
 9223    cx.update_editor(|e, window, cx| {
 9224        e.select_previous(&SelectPrevious::default(), window, cx)
 9225            .unwrap();
 9226        e.select_previous(&SelectPrevious::default(), window, cx)
 9227            .unwrap();
 9228    });
 9229    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9230}
 9231
 9232#[gpui::test]
 9233async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9234    init_test(cx, |_| {});
 9235
 9236    let language = Arc::new(Language::new(
 9237        LanguageConfig::default(),
 9238        Some(tree_sitter_rust::LANGUAGE.into()),
 9239    ));
 9240
 9241    let text = r#"
 9242        use mod1::mod2::{mod3, mod4};
 9243
 9244        fn fn_1(param1: bool, param2: &str) {
 9245            let var1 = "text";
 9246        }
 9247    "#
 9248    .unindent();
 9249
 9250    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9251    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9252    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9253
 9254    editor
 9255        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9256        .await;
 9257
 9258    editor.update_in(cx, |editor, window, cx| {
 9259        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9260            s.select_display_ranges([
 9261                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9262                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9263                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9264            ]);
 9265        });
 9266        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9267    });
 9268    editor.update(cx, |editor, cx| {
 9269        assert_text_with_selections(
 9270            editor,
 9271            indoc! {r#"
 9272                use mod1::mod2::{mod3, «mod4ˇ»};
 9273
 9274                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9275                    let var1 = "«ˇtext»";
 9276                }
 9277            "#},
 9278            cx,
 9279        );
 9280    });
 9281
 9282    editor.update_in(cx, |editor, window, cx| {
 9283        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9284    });
 9285    editor.update(cx, |editor, cx| {
 9286        assert_text_with_selections(
 9287            editor,
 9288            indoc! {r#"
 9289                use mod1::mod2::«{mod3, mod4}ˇ»;
 9290
 9291                «ˇfn fn_1(param1: bool, param2: &str) {
 9292                    let var1 = "text";
 9293 9294            "#},
 9295            cx,
 9296        );
 9297    });
 9298
 9299    editor.update_in(cx, |editor, window, cx| {
 9300        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9301    });
 9302    assert_eq!(
 9303        editor.update(cx, |editor, cx| editor
 9304            .selections
 9305            .display_ranges(&editor.display_snapshot(cx))),
 9306        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9307    );
 9308
 9309    // Trying to expand the selected syntax node one more time has no effect.
 9310    editor.update_in(cx, |editor, window, cx| {
 9311        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9312    });
 9313    assert_eq!(
 9314        editor.update(cx, |editor, cx| editor
 9315            .selections
 9316            .display_ranges(&editor.display_snapshot(cx))),
 9317        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9318    );
 9319
 9320    editor.update_in(cx, |editor, window, cx| {
 9321        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9322    });
 9323    editor.update(cx, |editor, cx| {
 9324        assert_text_with_selections(
 9325            editor,
 9326            indoc! {r#"
 9327                use mod1::mod2::«{mod3, mod4}ˇ»;
 9328
 9329                «ˇfn fn_1(param1: bool, param2: &str) {
 9330                    let var1 = "text";
 9331 9332            "#},
 9333            cx,
 9334        );
 9335    });
 9336
 9337    editor.update_in(cx, |editor, window, cx| {
 9338        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9339    });
 9340    editor.update(cx, |editor, cx| {
 9341        assert_text_with_selections(
 9342            editor,
 9343            indoc! {r#"
 9344                use mod1::mod2::{mod3, «mod4ˇ»};
 9345
 9346                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9347                    let var1 = "«ˇtext»";
 9348                }
 9349            "#},
 9350            cx,
 9351        );
 9352    });
 9353
 9354    editor.update_in(cx, |editor, window, cx| {
 9355        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9356    });
 9357    editor.update(cx, |editor, cx| {
 9358        assert_text_with_selections(
 9359            editor,
 9360            indoc! {r#"
 9361                use mod1::mod2::{mod3, moˇd4};
 9362
 9363                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9364                    let var1 = "teˇxt";
 9365                }
 9366            "#},
 9367            cx,
 9368        );
 9369    });
 9370
 9371    // Trying to shrink the selected syntax node one more time has no effect.
 9372    editor.update_in(cx, |editor, window, cx| {
 9373        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9374    });
 9375    editor.update_in(cx, |editor, _, cx| {
 9376        assert_text_with_selections(
 9377            editor,
 9378            indoc! {r#"
 9379                use mod1::mod2::{mod3, moˇd4};
 9380
 9381                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9382                    let var1 = "teˇxt";
 9383                }
 9384            "#},
 9385            cx,
 9386        );
 9387    });
 9388
 9389    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9390    // a fold.
 9391    editor.update_in(cx, |editor, window, cx| {
 9392        editor.fold_creases(
 9393            vec![
 9394                Crease::simple(
 9395                    Point::new(0, 21)..Point::new(0, 24),
 9396                    FoldPlaceholder::test(),
 9397                ),
 9398                Crease::simple(
 9399                    Point::new(3, 20)..Point::new(3, 22),
 9400                    FoldPlaceholder::test(),
 9401                ),
 9402            ],
 9403            true,
 9404            window,
 9405            cx,
 9406        );
 9407        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9408    });
 9409    editor.update(cx, |editor, cx| {
 9410        assert_text_with_selections(
 9411            editor,
 9412            indoc! {r#"
 9413                use mod1::mod2::«{mod3, mod4}ˇ»;
 9414
 9415                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9416                    let var1 = "«ˇtext»";
 9417                }
 9418            "#},
 9419            cx,
 9420        );
 9421    });
 9422}
 9423
 9424#[gpui::test]
 9425async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9426    init_test(cx, |_| {});
 9427
 9428    let language = Arc::new(Language::new(
 9429        LanguageConfig::default(),
 9430        Some(tree_sitter_rust::LANGUAGE.into()),
 9431    ));
 9432
 9433    let text = "let a = 2;";
 9434
 9435    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9436    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9437    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9438
 9439    editor
 9440        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9441        .await;
 9442
 9443    // Test case 1: Cursor at end of word
 9444    editor.update_in(cx, |editor, window, cx| {
 9445        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9446            s.select_display_ranges([
 9447                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9448            ]);
 9449        });
 9450    });
 9451    editor.update(cx, |editor, cx| {
 9452        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9453    });
 9454    editor.update_in(cx, |editor, window, cx| {
 9455        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9456    });
 9457    editor.update(cx, |editor, cx| {
 9458        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9459    });
 9460    editor.update_in(cx, |editor, window, cx| {
 9461        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9462    });
 9463    editor.update(cx, |editor, cx| {
 9464        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9465    });
 9466
 9467    // Test case 2: Cursor at end of statement
 9468    editor.update_in(cx, |editor, window, cx| {
 9469        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9470            s.select_display_ranges([
 9471                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9472            ]);
 9473        });
 9474    });
 9475    editor.update(cx, |editor, cx| {
 9476        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9477    });
 9478    editor.update_in(cx, |editor, window, cx| {
 9479        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9480    });
 9481    editor.update(cx, |editor, cx| {
 9482        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9483    });
 9484}
 9485
 9486#[gpui::test]
 9487async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9488    init_test(cx, |_| {});
 9489
 9490    let language = Arc::new(Language::new(
 9491        LanguageConfig {
 9492            name: "JavaScript".into(),
 9493            ..Default::default()
 9494        },
 9495        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9496    ));
 9497
 9498    let text = r#"
 9499        let a = {
 9500            key: "value",
 9501        };
 9502    "#
 9503    .unindent();
 9504
 9505    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9506    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9507    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9508
 9509    editor
 9510        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9511        .await;
 9512
 9513    // Test case 1: Cursor after '{'
 9514    editor.update_in(cx, |editor, window, cx| {
 9515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9516            s.select_display_ranges([
 9517                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9518            ]);
 9519        });
 9520    });
 9521    editor.update(cx, |editor, cx| {
 9522        assert_text_with_selections(
 9523            editor,
 9524            indoc! {r#"
 9525                let a = {ˇ
 9526                    key: "value",
 9527                };
 9528            "#},
 9529            cx,
 9530        );
 9531    });
 9532    editor.update_in(cx, |editor, window, cx| {
 9533        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9534    });
 9535    editor.update(cx, |editor, cx| {
 9536        assert_text_with_selections(
 9537            editor,
 9538            indoc! {r#"
 9539                let a = «ˇ{
 9540                    key: "value",
 9541                }»;
 9542            "#},
 9543            cx,
 9544        );
 9545    });
 9546
 9547    // Test case 2: Cursor after ':'
 9548    editor.update_in(cx, |editor, window, cx| {
 9549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9550            s.select_display_ranges([
 9551                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9552            ]);
 9553        });
 9554    });
 9555    editor.update(cx, |editor, cx| {
 9556        assert_text_with_selections(
 9557            editor,
 9558            indoc! {r#"
 9559                let a = {
 9560                    key:ˇ "value",
 9561                };
 9562            "#},
 9563            cx,
 9564        );
 9565    });
 9566    editor.update_in(cx, |editor, window, cx| {
 9567        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9568    });
 9569    editor.update(cx, |editor, cx| {
 9570        assert_text_with_selections(
 9571            editor,
 9572            indoc! {r#"
 9573                let a = {
 9574                    «ˇkey: "value"»,
 9575                };
 9576            "#},
 9577            cx,
 9578        );
 9579    });
 9580    editor.update_in(cx, |editor, window, cx| {
 9581        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9582    });
 9583    editor.update(cx, |editor, cx| {
 9584        assert_text_with_selections(
 9585            editor,
 9586            indoc! {r#"
 9587                let a = «ˇ{
 9588                    key: "value",
 9589                }»;
 9590            "#},
 9591            cx,
 9592        );
 9593    });
 9594
 9595    // Test case 3: Cursor after ','
 9596    editor.update_in(cx, |editor, window, cx| {
 9597        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9598            s.select_display_ranges([
 9599                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9600            ]);
 9601        });
 9602    });
 9603    editor.update(cx, |editor, cx| {
 9604        assert_text_with_selections(
 9605            editor,
 9606            indoc! {r#"
 9607                let a = {
 9608                    key: "value",ˇ
 9609                };
 9610            "#},
 9611            cx,
 9612        );
 9613    });
 9614    editor.update_in(cx, |editor, window, cx| {
 9615        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9616    });
 9617    editor.update(cx, |editor, cx| {
 9618        assert_text_with_selections(
 9619            editor,
 9620            indoc! {r#"
 9621                let a = «ˇ{
 9622                    key: "value",
 9623                }»;
 9624            "#},
 9625            cx,
 9626        );
 9627    });
 9628
 9629    // Test case 4: Cursor after ';'
 9630    editor.update_in(cx, |editor, window, cx| {
 9631        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9632            s.select_display_ranges([
 9633                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9634            ]);
 9635        });
 9636    });
 9637    editor.update(cx, |editor, cx| {
 9638        assert_text_with_selections(
 9639            editor,
 9640            indoc! {r#"
 9641                let a = {
 9642                    key: "value",
 9643                };ˇ
 9644            "#},
 9645            cx,
 9646        );
 9647    });
 9648    editor.update_in(cx, |editor, window, cx| {
 9649        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9650    });
 9651    editor.update(cx, |editor, cx| {
 9652        assert_text_with_selections(
 9653            editor,
 9654            indoc! {r#"
 9655                «ˇlet a = {
 9656                    key: "value",
 9657                };
 9658                »"#},
 9659            cx,
 9660        );
 9661    });
 9662}
 9663
 9664#[gpui::test]
 9665async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9666    init_test(cx, |_| {});
 9667
 9668    let language = Arc::new(Language::new(
 9669        LanguageConfig::default(),
 9670        Some(tree_sitter_rust::LANGUAGE.into()),
 9671    ));
 9672
 9673    let text = r#"
 9674        use mod1::mod2::{mod3, mod4};
 9675
 9676        fn fn_1(param1: bool, param2: &str) {
 9677            let var1 = "hello world";
 9678        }
 9679    "#
 9680    .unindent();
 9681
 9682    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9683    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9684    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9685
 9686    editor
 9687        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9688        .await;
 9689
 9690    // Test 1: Cursor on a letter of a string word
 9691    editor.update_in(cx, |editor, window, cx| {
 9692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9693            s.select_display_ranges([
 9694                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9695            ]);
 9696        });
 9697    });
 9698    editor.update_in(cx, |editor, window, cx| {
 9699        assert_text_with_selections(
 9700            editor,
 9701            indoc! {r#"
 9702                use mod1::mod2::{mod3, mod4};
 9703
 9704                fn fn_1(param1: bool, param2: &str) {
 9705                    let var1 = "hˇello world";
 9706                }
 9707            "#},
 9708            cx,
 9709        );
 9710        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9711        assert_text_with_selections(
 9712            editor,
 9713            indoc! {r#"
 9714                use mod1::mod2::{mod3, mod4};
 9715
 9716                fn fn_1(param1: bool, param2: &str) {
 9717                    let var1 = "«ˇhello» world";
 9718                }
 9719            "#},
 9720            cx,
 9721        );
 9722    });
 9723
 9724    // Test 2: Partial selection within a word
 9725    editor.update_in(cx, |editor, window, cx| {
 9726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9727            s.select_display_ranges([
 9728                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9729            ]);
 9730        });
 9731    });
 9732    editor.update_in(cx, |editor, window, cx| {
 9733        assert_text_with_selections(
 9734            editor,
 9735            indoc! {r#"
 9736                use mod1::mod2::{mod3, mod4};
 9737
 9738                fn fn_1(param1: bool, param2: &str) {
 9739                    let var1 = "h«elˇ»lo world";
 9740                }
 9741            "#},
 9742            cx,
 9743        );
 9744        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9745        assert_text_with_selections(
 9746            editor,
 9747            indoc! {r#"
 9748                use mod1::mod2::{mod3, mod4};
 9749
 9750                fn fn_1(param1: bool, param2: &str) {
 9751                    let var1 = "«ˇhello» world";
 9752                }
 9753            "#},
 9754            cx,
 9755        );
 9756    });
 9757
 9758    // Test 3: Complete word already selected
 9759    editor.update_in(cx, |editor, window, cx| {
 9760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9761            s.select_display_ranges([
 9762                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9763            ]);
 9764        });
 9765    });
 9766    editor.update_in(cx, |editor, window, cx| {
 9767        assert_text_with_selections(
 9768            editor,
 9769            indoc! {r#"
 9770                use mod1::mod2::{mod3, mod4};
 9771
 9772                fn fn_1(param1: bool, param2: &str) {
 9773                    let var1 = "«helloˇ» world";
 9774                }
 9775            "#},
 9776            cx,
 9777        );
 9778        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9779        assert_text_with_selections(
 9780            editor,
 9781            indoc! {r#"
 9782                use mod1::mod2::{mod3, mod4};
 9783
 9784                fn fn_1(param1: bool, param2: &str) {
 9785                    let var1 = "«hello worldˇ»";
 9786                }
 9787            "#},
 9788            cx,
 9789        );
 9790    });
 9791
 9792    // Test 4: Selection spanning across words
 9793    editor.update_in(cx, |editor, window, cx| {
 9794        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9795            s.select_display_ranges([
 9796                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9797            ]);
 9798        });
 9799    });
 9800    editor.update_in(cx, |editor, window, cx| {
 9801        assert_text_with_selections(
 9802            editor,
 9803            indoc! {r#"
 9804                use mod1::mod2::{mod3, mod4};
 9805
 9806                fn fn_1(param1: bool, param2: &str) {
 9807                    let var1 = "hel«lo woˇ»rld";
 9808                }
 9809            "#},
 9810            cx,
 9811        );
 9812        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9813        assert_text_with_selections(
 9814            editor,
 9815            indoc! {r#"
 9816                use mod1::mod2::{mod3, mod4};
 9817
 9818                fn fn_1(param1: bool, param2: &str) {
 9819                    let var1 = "«ˇhello world»";
 9820                }
 9821            "#},
 9822            cx,
 9823        );
 9824    });
 9825
 9826    // Test 5: Expansion beyond string
 9827    editor.update_in(cx, |editor, window, cx| {
 9828        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9829        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9830        assert_text_with_selections(
 9831            editor,
 9832            indoc! {r#"
 9833                use mod1::mod2::{mod3, mod4};
 9834
 9835                fn fn_1(param1: bool, param2: &str) {
 9836                    «ˇlet var1 = "hello world";»
 9837                }
 9838            "#},
 9839            cx,
 9840        );
 9841    });
 9842}
 9843
 9844#[gpui::test]
 9845async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9846    init_test(cx, |_| {});
 9847
 9848    let mut cx = EditorTestContext::new(cx).await;
 9849
 9850    let language = Arc::new(Language::new(
 9851        LanguageConfig::default(),
 9852        Some(tree_sitter_rust::LANGUAGE.into()),
 9853    ));
 9854
 9855    cx.update_buffer(|buffer, cx| {
 9856        buffer.set_language(Some(language), cx);
 9857    });
 9858
 9859    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9860    cx.update_editor(|editor, window, cx| {
 9861        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9862    });
 9863
 9864    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9865
 9866    cx.set_state(indoc! { r#"fn a() {
 9867          // what
 9868          // a
 9869          // ˇlong
 9870          // method
 9871          // I
 9872          // sure
 9873          // hope
 9874          // it
 9875          // works
 9876    }"# });
 9877
 9878    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9879    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9880    cx.update(|_, cx| {
 9881        multi_buffer.update(cx, |multi_buffer, cx| {
 9882            multi_buffer.set_excerpts_for_path(
 9883                PathKey::for_buffer(&buffer, cx),
 9884                buffer,
 9885                [Point::new(1, 0)..Point::new(1, 0)],
 9886                3,
 9887                cx,
 9888            );
 9889        });
 9890    });
 9891
 9892    let editor2 = cx.new_window_entity(|window, cx| {
 9893        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9894    });
 9895
 9896    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9897    cx.update_editor(|editor, window, cx| {
 9898        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9899            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9900        })
 9901    });
 9902
 9903    cx.assert_editor_state(indoc! { "
 9904        fn a() {
 9905              // what
 9906              // a
 9907        ˇ      // long
 9908              // method"});
 9909
 9910    cx.update_editor(|editor, window, cx| {
 9911        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9912    });
 9913
 9914    // Although we could potentially make the action work when the syntax node
 9915    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9916    // did. Maybe we could also expand the excerpt to contain the range?
 9917    cx.assert_editor_state(indoc! { "
 9918        fn a() {
 9919              // what
 9920              // a
 9921        ˇ      // long
 9922              // method"});
 9923}
 9924
 9925#[gpui::test]
 9926async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9927    init_test(cx, |_| {});
 9928
 9929    let base_text = r#"
 9930        impl A {
 9931            // this is an uncommitted comment
 9932
 9933            fn b() {
 9934                c();
 9935            }
 9936
 9937            // this is another uncommitted comment
 9938
 9939            fn d() {
 9940                // e
 9941                // f
 9942            }
 9943        }
 9944
 9945        fn g() {
 9946            // h
 9947        }
 9948    "#
 9949    .unindent();
 9950
 9951    let text = r#"
 9952        ˇimpl A {
 9953
 9954            fn b() {
 9955                c();
 9956            }
 9957
 9958            fn d() {
 9959                // e
 9960                // f
 9961            }
 9962        }
 9963
 9964        fn g() {
 9965            // h
 9966        }
 9967    "#
 9968    .unindent();
 9969
 9970    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9971    cx.set_state(&text);
 9972    cx.set_head_text(&base_text);
 9973    cx.update_editor(|editor, window, cx| {
 9974        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9975    });
 9976
 9977    cx.assert_state_with_diff(
 9978        "
 9979        ˇimpl A {
 9980      -     // this is an uncommitted comment
 9981
 9982            fn b() {
 9983                c();
 9984            }
 9985
 9986      -     // this is another uncommitted comment
 9987      -
 9988            fn d() {
 9989                // e
 9990                // f
 9991            }
 9992        }
 9993
 9994        fn g() {
 9995            // h
 9996        }
 9997    "
 9998        .unindent(),
 9999    );
10000
10001    let expected_display_text = "
10002        impl A {
10003            // this is an uncommitted comment
10004
10005            fn b() {
1000610007            }
10008
10009            // this is another uncommitted comment
10010
10011            fn d() {
1001210013            }
10014        }
10015
10016        fn g() {
1001710018        }
10019        "
10020    .unindent();
10021
10022    cx.update_editor(|editor, window, cx| {
10023        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10024        assert_eq!(editor.display_text(cx), expected_display_text);
10025    });
10026}
10027
10028#[gpui::test]
10029async fn test_autoindent(cx: &mut TestAppContext) {
10030    init_test(cx, |_| {});
10031
10032    let language = Arc::new(
10033        Language::new(
10034            LanguageConfig {
10035                brackets: BracketPairConfig {
10036                    pairs: vec![
10037                        BracketPair {
10038                            start: "{".to_string(),
10039                            end: "}".to_string(),
10040                            close: false,
10041                            surround: false,
10042                            newline: true,
10043                        },
10044                        BracketPair {
10045                            start: "(".to_string(),
10046                            end: ")".to_string(),
10047                            close: false,
10048                            surround: false,
10049                            newline: true,
10050                        },
10051                    ],
10052                    ..Default::default()
10053                },
10054                ..Default::default()
10055            },
10056            Some(tree_sitter_rust::LANGUAGE.into()),
10057        )
10058        .with_indents_query(
10059            r#"
10060                (_ "(" ")" @end) @indent
10061                (_ "{" "}" @end) @indent
10062            "#,
10063        )
10064        .unwrap(),
10065    );
10066
10067    let text = "fn a() {}";
10068
10069    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10070    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10071    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10072    editor
10073        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10074        .await;
10075
10076    editor.update_in(cx, |editor, window, cx| {
10077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10078            s.select_ranges([
10079                MultiBufferOffset(5)..MultiBufferOffset(5),
10080                MultiBufferOffset(8)..MultiBufferOffset(8),
10081                MultiBufferOffset(9)..MultiBufferOffset(9),
10082            ])
10083        });
10084        editor.newline(&Newline, window, cx);
10085        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10086        assert_eq!(
10087            editor.selections.ranges(&editor.display_snapshot(cx)),
10088            &[
10089                Point::new(1, 4)..Point::new(1, 4),
10090                Point::new(3, 4)..Point::new(3, 4),
10091                Point::new(5, 0)..Point::new(5, 0)
10092            ]
10093        );
10094    });
10095}
10096
10097#[gpui::test]
10098async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10099    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
10100
10101    let language = Arc::new(
10102        Language::new(
10103            LanguageConfig {
10104                brackets: BracketPairConfig {
10105                    pairs: vec![
10106                        BracketPair {
10107                            start: "{".to_string(),
10108                            end: "}".to_string(),
10109                            close: false,
10110                            surround: false,
10111                            newline: true,
10112                        },
10113                        BracketPair {
10114                            start: "(".to_string(),
10115                            end: ")".to_string(),
10116                            close: false,
10117                            surround: false,
10118                            newline: true,
10119                        },
10120                    ],
10121                    ..Default::default()
10122                },
10123                ..Default::default()
10124            },
10125            Some(tree_sitter_rust::LANGUAGE.into()),
10126        )
10127        .with_indents_query(
10128            r#"
10129                (_ "(" ")" @end) @indent
10130                (_ "{" "}" @end) @indent
10131            "#,
10132        )
10133        .unwrap(),
10134    );
10135
10136    let text = "fn a() {}";
10137
10138    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10139    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10140    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10141    editor
10142        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10143        .await;
10144
10145    editor.update_in(cx, |editor, window, cx| {
10146        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10147            s.select_ranges([
10148                MultiBufferOffset(5)..MultiBufferOffset(5),
10149                MultiBufferOffset(8)..MultiBufferOffset(8),
10150                MultiBufferOffset(9)..MultiBufferOffset(9),
10151            ])
10152        });
10153        editor.newline(&Newline, window, cx);
10154        assert_eq!(
10155            editor.text(cx),
10156            indoc!(
10157                "
10158                fn a(
10159
10160                ) {
10161
10162                }
10163                "
10164            )
10165        );
10166        assert_eq!(
10167            editor.selections.ranges(&editor.display_snapshot(cx)),
10168            &[
10169                Point::new(1, 0)..Point::new(1, 0),
10170                Point::new(3, 0)..Point::new(3, 0),
10171                Point::new(5, 0)..Point::new(5, 0)
10172            ]
10173        );
10174    });
10175}
10176
10177#[gpui::test]
10178async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10179    init_test(cx, |settings| {
10180        settings.defaults.auto_indent = Some(true);
10181        settings.languages.0.insert(
10182            "python".into(),
10183            LanguageSettingsContent {
10184                auto_indent: Some(false),
10185                ..Default::default()
10186            },
10187        );
10188    });
10189
10190    let mut cx = EditorTestContext::new(cx).await;
10191
10192    let injected_language = Arc::new(
10193        Language::new(
10194            LanguageConfig {
10195                brackets: BracketPairConfig {
10196                    pairs: vec![
10197                        BracketPair {
10198                            start: "{".to_string(),
10199                            end: "}".to_string(),
10200                            close: false,
10201                            surround: false,
10202                            newline: true,
10203                        },
10204                        BracketPair {
10205                            start: "(".to_string(),
10206                            end: ")".to_string(),
10207                            close: true,
10208                            surround: false,
10209                            newline: true,
10210                        },
10211                    ],
10212                    ..Default::default()
10213                },
10214                name: "python".into(),
10215                ..Default::default()
10216            },
10217            Some(tree_sitter_python::LANGUAGE.into()),
10218        )
10219        .with_indents_query(
10220            r#"
10221                (_ "(" ")" @end) @indent
10222                (_ "{" "}" @end) @indent
10223            "#,
10224        )
10225        .unwrap(),
10226    );
10227
10228    let language = Arc::new(
10229        Language::new(
10230            LanguageConfig {
10231                brackets: BracketPairConfig {
10232                    pairs: vec![
10233                        BracketPair {
10234                            start: "{".to_string(),
10235                            end: "}".to_string(),
10236                            close: false,
10237                            surround: false,
10238                            newline: true,
10239                        },
10240                        BracketPair {
10241                            start: "(".to_string(),
10242                            end: ")".to_string(),
10243                            close: true,
10244                            surround: false,
10245                            newline: true,
10246                        },
10247                    ],
10248                    ..Default::default()
10249                },
10250                name: LanguageName::new_static("rust"),
10251                ..Default::default()
10252            },
10253            Some(tree_sitter_rust::LANGUAGE.into()),
10254        )
10255        .with_indents_query(
10256            r#"
10257                (_ "(" ")" @end) @indent
10258                (_ "{" "}" @end) @indent
10259            "#,
10260        )
10261        .unwrap()
10262        .with_injection_query(
10263            r#"
10264            (macro_invocation
10265                macro: (identifier) @_macro_name
10266                (token_tree) @injection.content
10267                (#set! injection.language "python"))
10268           "#,
10269        )
10270        .unwrap(),
10271    );
10272
10273    cx.language_registry().add(injected_language);
10274    cx.language_registry().add(language.clone());
10275
10276    cx.update_buffer(|buffer, cx| {
10277        buffer.set_language(Some(language), cx);
10278    });
10279
10280    cx.set_state(r#"struct A {ˇ}"#);
10281
10282    cx.update_editor(|editor, window, cx| {
10283        editor.newline(&Default::default(), window, cx);
10284    });
10285
10286    cx.assert_editor_state(indoc!(
10287        "struct A {
10288            ˇ
10289        }"
10290    ));
10291
10292    cx.set_state(r#"select_biased!(ˇ)"#);
10293
10294    cx.update_editor(|editor, window, cx| {
10295        editor.newline(&Default::default(), window, cx);
10296        editor.handle_input("def ", window, cx);
10297        editor.handle_input("(", window, cx);
10298        editor.newline(&Default::default(), window, cx);
10299        editor.handle_input("a", window, cx);
10300    });
10301
10302    cx.assert_editor_state(indoc!(
10303        "select_biased!(
10304        def (
1030510306        )
10307        )"
10308    ));
10309}
10310
10311#[gpui::test]
10312async fn test_autoindent_selections(cx: &mut TestAppContext) {
10313    init_test(cx, |_| {});
10314
10315    {
10316        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10317        cx.set_state(indoc! {"
10318            impl A {
10319
10320                fn b() {}
10321
10322            «fn c() {
10323
10324            }ˇ»
10325            }
10326        "});
10327
10328        cx.update_editor(|editor, window, cx| {
10329            editor.autoindent(&Default::default(), window, cx);
10330        });
10331        cx.wait_for_autoindent_applied().await;
10332
10333        cx.assert_editor_state(indoc! {"
10334            impl A {
10335
10336                fn b() {}
10337
10338                «fn c() {
10339
10340                }ˇ»
10341            }
10342        "});
10343    }
10344
10345    {
10346        let mut cx = EditorTestContext::new_multibuffer(
10347            cx,
10348            [indoc! { "
10349                impl A {
10350                «
10351                // a
10352                fn b(){}
10353                »
10354                «
10355                    }
10356                    fn c(){}
10357                »
10358            "}],
10359        );
10360
10361        let buffer = cx.update_editor(|editor, _, cx| {
10362            let buffer = editor.buffer().update(cx, |buffer, _| {
10363                buffer.all_buffers().iter().next().unwrap().clone()
10364            });
10365            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10366            buffer
10367        });
10368
10369        cx.run_until_parked();
10370        cx.update_editor(|editor, window, cx| {
10371            editor.select_all(&Default::default(), window, cx);
10372            editor.autoindent(&Default::default(), window, cx)
10373        });
10374        cx.run_until_parked();
10375
10376        cx.update(|_, cx| {
10377            assert_eq!(
10378                buffer.read(cx).text(),
10379                indoc! { "
10380                    impl A {
10381
10382                        // a
10383                        fn b(){}
10384
10385
10386                    }
10387                    fn c(){}
10388
10389                " }
10390            )
10391        });
10392    }
10393}
10394
10395#[gpui::test]
10396async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10397    init_test(cx, |_| {});
10398
10399    let mut cx = EditorTestContext::new(cx).await;
10400
10401    let language = Arc::new(Language::new(
10402        LanguageConfig {
10403            brackets: BracketPairConfig {
10404                pairs: vec![
10405                    BracketPair {
10406                        start: "{".to_string(),
10407                        end: "}".to_string(),
10408                        close: true,
10409                        surround: true,
10410                        newline: true,
10411                    },
10412                    BracketPair {
10413                        start: "(".to_string(),
10414                        end: ")".to_string(),
10415                        close: true,
10416                        surround: true,
10417                        newline: true,
10418                    },
10419                    BracketPair {
10420                        start: "/*".to_string(),
10421                        end: " */".to_string(),
10422                        close: true,
10423                        surround: true,
10424                        newline: true,
10425                    },
10426                    BracketPair {
10427                        start: "[".to_string(),
10428                        end: "]".to_string(),
10429                        close: false,
10430                        surround: false,
10431                        newline: true,
10432                    },
10433                    BracketPair {
10434                        start: "\"".to_string(),
10435                        end: "\"".to_string(),
10436                        close: true,
10437                        surround: true,
10438                        newline: false,
10439                    },
10440                    BracketPair {
10441                        start: "<".to_string(),
10442                        end: ">".to_string(),
10443                        close: false,
10444                        surround: true,
10445                        newline: true,
10446                    },
10447                ],
10448                ..Default::default()
10449            },
10450            autoclose_before: "})]".to_string(),
10451            ..Default::default()
10452        },
10453        Some(tree_sitter_rust::LANGUAGE.into()),
10454    ));
10455
10456    cx.language_registry().add(language.clone());
10457    cx.update_buffer(|buffer, cx| {
10458        buffer.set_language(Some(language), cx);
10459    });
10460
10461    cx.set_state(
10462        &r#"
10463            🏀ˇ
10464            εˇ
10465            ❤️ˇ
10466        "#
10467        .unindent(),
10468    );
10469
10470    // autoclose multiple nested brackets at multiple cursors
10471    cx.update_editor(|editor, window, cx| {
10472        editor.handle_input("{", window, cx);
10473        editor.handle_input("{", window, cx);
10474        editor.handle_input("{", window, cx);
10475    });
10476    cx.assert_editor_state(
10477        &"
10478            🏀{{{ˇ}}}
10479            ε{{{ˇ}}}
10480            ❤️{{{ˇ}}}
10481        "
10482        .unindent(),
10483    );
10484
10485    // insert a different closing bracket
10486    cx.update_editor(|editor, window, cx| {
10487        editor.handle_input(")", window, cx);
10488    });
10489    cx.assert_editor_state(
10490        &"
10491            🏀{{{)ˇ}}}
10492            ε{{{)ˇ}}}
10493            ❤️{{{)ˇ}}}
10494        "
10495        .unindent(),
10496    );
10497
10498    // skip over the auto-closed brackets when typing a closing bracket
10499    cx.update_editor(|editor, window, cx| {
10500        editor.move_right(&MoveRight, window, cx);
10501        editor.handle_input("}", window, cx);
10502        editor.handle_input("}", window, cx);
10503        editor.handle_input("}", window, cx);
10504    });
10505    cx.assert_editor_state(
10506        &"
10507            🏀{{{)}}}}ˇ
10508            ε{{{)}}}}ˇ
10509            ❤️{{{)}}}}ˇ
10510        "
10511        .unindent(),
10512    );
10513
10514    // autoclose multi-character pairs
10515    cx.set_state(
10516        &"
10517            ˇ
10518            ˇ
10519        "
10520        .unindent(),
10521    );
10522    cx.update_editor(|editor, window, cx| {
10523        editor.handle_input("/", window, cx);
10524        editor.handle_input("*", window, cx);
10525    });
10526    cx.assert_editor_state(
10527        &"
10528            /*ˇ */
10529            /*ˇ */
10530        "
10531        .unindent(),
10532    );
10533
10534    // one cursor autocloses a multi-character pair, one cursor
10535    // does not autoclose.
10536    cx.set_state(
10537        &"
1053810539            ˇ
10540        "
10541        .unindent(),
10542    );
10543    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10544    cx.assert_editor_state(
10545        &"
10546            /*ˇ */
1054710548        "
10549        .unindent(),
10550    );
10551
10552    // Don't autoclose if the next character isn't whitespace and isn't
10553    // listed in the language's "autoclose_before" section.
10554    cx.set_state("ˇa b");
10555    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10556    cx.assert_editor_state("{ˇa b");
10557
10558    // Don't autoclose if `close` is false for the bracket pair
10559    cx.set_state("ˇ");
10560    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10561    cx.assert_editor_state("");
10562
10563    // Surround with brackets if text is selected
10564    cx.set_state("«aˇ» b");
10565    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10566    cx.assert_editor_state("{«aˇ»} b");
10567
10568    // Autoclose when not immediately after a word character
10569    cx.set_state("a ˇ");
10570    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10571    cx.assert_editor_state("a \"ˇ\"");
10572
10573    // Autoclose pair where the start and end characters are the same
10574    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10575    cx.assert_editor_state("a \"\"ˇ");
10576
10577    // Don't autoclose when immediately after a word character
10578    cx.set_state("");
10579    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10580    cx.assert_editor_state("a\"ˇ");
10581
10582    // Do autoclose when after a non-word character
10583    cx.set_state("");
10584    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10585    cx.assert_editor_state("{\"ˇ\"");
10586
10587    // Non identical pairs autoclose regardless of preceding character
10588    cx.set_state("");
10589    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10590    cx.assert_editor_state("a{ˇ}");
10591
10592    // Don't autoclose pair if autoclose is disabled
10593    cx.set_state("ˇ");
10594    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10595    cx.assert_editor_state("");
10596
10597    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10598    cx.set_state("«aˇ» b");
10599    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10600    cx.assert_editor_state("<«aˇ»> b");
10601}
10602
10603#[gpui::test]
10604async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10605    init_test(cx, |settings| {
10606        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10607    });
10608
10609    let mut cx = EditorTestContext::new(cx).await;
10610
10611    let language = Arc::new(Language::new(
10612        LanguageConfig {
10613            brackets: BracketPairConfig {
10614                pairs: vec![
10615                    BracketPair {
10616                        start: "{".to_string(),
10617                        end: "}".to_string(),
10618                        close: true,
10619                        surround: true,
10620                        newline: true,
10621                    },
10622                    BracketPair {
10623                        start: "(".to_string(),
10624                        end: ")".to_string(),
10625                        close: true,
10626                        surround: true,
10627                        newline: true,
10628                    },
10629                    BracketPair {
10630                        start: "[".to_string(),
10631                        end: "]".to_string(),
10632                        close: false,
10633                        surround: false,
10634                        newline: true,
10635                    },
10636                ],
10637                ..Default::default()
10638            },
10639            autoclose_before: "})]".to_string(),
10640            ..Default::default()
10641        },
10642        Some(tree_sitter_rust::LANGUAGE.into()),
10643    ));
10644
10645    cx.language_registry().add(language.clone());
10646    cx.update_buffer(|buffer, cx| {
10647        buffer.set_language(Some(language), cx);
10648    });
10649
10650    cx.set_state(
10651        &"
10652            ˇ
10653            ˇ
10654            ˇ
10655        "
10656        .unindent(),
10657    );
10658
10659    // ensure only matching closing brackets are skipped over
10660    cx.update_editor(|editor, window, cx| {
10661        editor.handle_input("}", window, cx);
10662        editor.move_left(&MoveLeft, window, cx);
10663        editor.handle_input(")", window, cx);
10664        editor.move_left(&MoveLeft, window, cx);
10665    });
10666    cx.assert_editor_state(
10667        &"
10668            ˇ)}
10669            ˇ)}
10670            ˇ)}
10671        "
10672        .unindent(),
10673    );
10674
10675    // skip-over closing brackets at multiple cursors
10676    cx.update_editor(|editor, window, cx| {
10677        editor.handle_input(")", window, cx);
10678        editor.handle_input("}", window, cx);
10679    });
10680    cx.assert_editor_state(
10681        &"
10682            )}ˇ
10683            )}ˇ
10684            )}ˇ
10685        "
10686        .unindent(),
10687    );
10688
10689    // ignore non-close brackets
10690    cx.update_editor(|editor, window, cx| {
10691        editor.handle_input("]", window, cx);
10692        editor.move_left(&MoveLeft, window, cx);
10693        editor.handle_input("]", window, cx);
10694    });
10695    cx.assert_editor_state(
10696        &"
10697            )}]ˇ]
10698            )}]ˇ]
10699            )}]ˇ]
10700        "
10701        .unindent(),
10702    );
10703}
10704
10705#[gpui::test]
10706async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10707    init_test(cx, |_| {});
10708
10709    let mut cx = EditorTestContext::new(cx).await;
10710
10711    let html_language = Arc::new(
10712        Language::new(
10713            LanguageConfig {
10714                name: "HTML".into(),
10715                brackets: BracketPairConfig {
10716                    pairs: vec![
10717                        BracketPair {
10718                            start: "<".into(),
10719                            end: ">".into(),
10720                            close: true,
10721                            ..Default::default()
10722                        },
10723                        BracketPair {
10724                            start: "{".into(),
10725                            end: "}".into(),
10726                            close: true,
10727                            ..Default::default()
10728                        },
10729                        BracketPair {
10730                            start: "(".into(),
10731                            end: ")".into(),
10732                            close: true,
10733                            ..Default::default()
10734                        },
10735                    ],
10736                    ..Default::default()
10737                },
10738                autoclose_before: "})]>".into(),
10739                ..Default::default()
10740            },
10741            Some(tree_sitter_html::LANGUAGE.into()),
10742        )
10743        .with_injection_query(
10744            r#"
10745            (script_element
10746                (raw_text) @injection.content
10747                (#set! injection.language "javascript"))
10748            "#,
10749        )
10750        .unwrap(),
10751    );
10752
10753    let javascript_language = Arc::new(Language::new(
10754        LanguageConfig {
10755            name: "JavaScript".into(),
10756            brackets: BracketPairConfig {
10757                pairs: vec![
10758                    BracketPair {
10759                        start: "/*".into(),
10760                        end: " */".into(),
10761                        close: true,
10762                        ..Default::default()
10763                    },
10764                    BracketPair {
10765                        start: "{".into(),
10766                        end: "}".into(),
10767                        close: true,
10768                        ..Default::default()
10769                    },
10770                    BracketPair {
10771                        start: "(".into(),
10772                        end: ")".into(),
10773                        close: true,
10774                        ..Default::default()
10775                    },
10776                ],
10777                ..Default::default()
10778            },
10779            autoclose_before: "})]>".into(),
10780            ..Default::default()
10781        },
10782        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10783    ));
10784
10785    cx.language_registry().add(html_language.clone());
10786    cx.language_registry().add(javascript_language);
10787    cx.executor().run_until_parked();
10788
10789    cx.update_buffer(|buffer, cx| {
10790        buffer.set_language(Some(html_language), cx);
10791    });
10792
10793    cx.set_state(
10794        &r#"
10795            <body>ˇ
10796                <script>
10797                    var x = 1;ˇ
10798                </script>
10799            </body>ˇ
10800        "#
10801        .unindent(),
10802    );
10803
10804    // Precondition: different languages are active at different locations.
10805    cx.update_editor(|editor, window, cx| {
10806        let snapshot = editor.snapshot(window, cx);
10807        let cursors = editor
10808            .selections
10809            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10810        let languages = cursors
10811            .iter()
10812            .map(|c| snapshot.language_at(c.start).unwrap().name())
10813            .collect::<Vec<_>>();
10814        assert_eq!(
10815            languages,
10816            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10817        );
10818    });
10819
10820    // Angle brackets autoclose in HTML, but not JavaScript.
10821    cx.update_editor(|editor, window, cx| {
10822        editor.handle_input("<", window, cx);
10823        editor.handle_input("a", window, cx);
10824    });
10825    cx.assert_editor_state(
10826        &r#"
10827            <body><aˇ>
10828                <script>
10829                    var x = 1;<aˇ
10830                </script>
10831            </body><aˇ>
10832        "#
10833        .unindent(),
10834    );
10835
10836    // Curly braces and parens autoclose in both HTML and JavaScript.
10837    cx.update_editor(|editor, window, cx| {
10838        editor.handle_input(" b=", window, cx);
10839        editor.handle_input("{", window, cx);
10840        editor.handle_input("c", window, cx);
10841        editor.handle_input("(", window, cx);
10842    });
10843    cx.assert_editor_state(
10844        &r#"
10845            <body><a b={c(ˇ)}>
10846                <script>
10847                    var x = 1;<a b={c(ˇ)}
10848                </script>
10849            </body><a b={c(ˇ)}>
10850        "#
10851        .unindent(),
10852    );
10853
10854    // Brackets that were already autoclosed are skipped.
10855    cx.update_editor(|editor, window, cx| {
10856        editor.handle_input(")", window, cx);
10857        editor.handle_input("d", window, cx);
10858        editor.handle_input("}", window, cx);
10859    });
10860    cx.assert_editor_state(
10861        &r#"
10862            <body><a b={c()d}ˇ>
10863                <script>
10864                    var x = 1;<a b={c()d}ˇ
10865                </script>
10866            </body><a b={c()d}ˇ>
10867        "#
10868        .unindent(),
10869    );
10870    cx.update_editor(|editor, window, cx| {
10871        editor.handle_input(">", window, cx);
10872    });
10873    cx.assert_editor_state(
10874        &r#"
10875            <body><a b={c()d}>ˇ
10876                <script>
10877                    var x = 1;<a b={c()d}>ˇ
10878                </script>
10879            </body><a b={c()d}>ˇ
10880        "#
10881        .unindent(),
10882    );
10883
10884    // Reset
10885    cx.set_state(
10886        &r#"
10887            <body>ˇ
10888                <script>
10889                    var x = 1;ˇ
10890                </script>
10891            </body>ˇ
10892        "#
10893        .unindent(),
10894    );
10895
10896    cx.update_editor(|editor, window, cx| {
10897        editor.handle_input("<", window, cx);
10898    });
10899    cx.assert_editor_state(
10900        &r#"
10901            <body><ˇ>
10902                <script>
10903                    var x = 1;<ˇ
10904                </script>
10905            </body><ˇ>
10906        "#
10907        .unindent(),
10908    );
10909
10910    // When backspacing, the closing angle brackets are removed.
10911    cx.update_editor(|editor, window, cx| {
10912        editor.backspace(&Backspace, window, cx);
10913    });
10914    cx.assert_editor_state(
10915        &r#"
10916            <body>ˇ
10917                <script>
10918                    var x = 1;ˇ
10919                </script>
10920            </body>ˇ
10921        "#
10922        .unindent(),
10923    );
10924
10925    // Block comments autoclose in JavaScript, but not HTML.
10926    cx.update_editor(|editor, window, cx| {
10927        editor.handle_input("/", window, cx);
10928        editor.handle_input("*", window, cx);
10929    });
10930    cx.assert_editor_state(
10931        &r#"
10932            <body>/*ˇ
10933                <script>
10934                    var x = 1;/*ˇ */
10935                </script>
10936            </body>/*ˇ
10937        "#
10938        .unindent(),
10939    );
10940}
10941
10942#[gpui::test]
10943async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10944    init_test(cx, |_| {});
10945
10946    let mut cx = EditorTestContext::new(cx).await;
10947
10948    let rust_language = Arc::new(
10949        Language::new(
10950            LanguageConfig {
10951                name: "Rust".into(),
10952                brackets: serde_json::from_value(json!([
10953                    { "start": "{", "end": "}", "close": true, "newline": true },
10954                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10955                ]))
10956                .unwrap(),
10957                autoclose_before: "})]>".into(),
10958                ..Default::default()
10959            },
10960            Some(tree_sitter_rust::LANGUAGE.into()),
10961        )
10962        .with_override_query("(string_literal) @string")
10963        .unwrap(),
10964    );
10965
10966    cx.language_registry().add(rust_language.clone());
10967    cx.update_buffer(|buffer, cx| {
10968        buffer.set_language(Some(rust_language), cx);
10969    });
10970
10971    cx.set_state(
10972        &r#"
10973            let x = ˇ
10974        "#
10975        .unindent(),
10976    );
10977
10978    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10979    cx.update_editor(|editor, window, cx| {
10980        editor.handle_input("\"", window, cx);
10981    });
10982    cx.assert_editor_state(
10983        &r#"
10984            let x = "ˇ"
10985        "#
10986        .unindent(),
10987    );
10988
10989    // Inserting another quotation mark. The cursor moves across the existing
10990    // automatically-inserted quotation mark.
10991    cx.update_editor(|editor, window, cx| {
10992        editor.handle_input("\"", window, cx);
10993    });
10994    cx.assert_editor_state(
10995        &r#"
10996            let x = ""ˇ
10997        "#
10998        .unindent(),
10999    );
11000
11001    // Reset
11002    cx.set_state(
11003        &r#"
11004            let x = ˇ
11005        "#
11006        .unindent(),
11007    );
11008
11009    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11010    cx.update_editor(|editor, window, cx| {
11011        editor.handle_input("\"", window, cx);
11012        editor.handle_input(" ", window, cx);
11013        editor.move_left(&Default::default(), window, cx);
11014        editor.handle_input("\\", window, cx);
11015        editor.handle_input("\"", window, cx);
11016    });
11017    cx.assert_editor_state(
11018        &r#"
11019            let x = "\"ˇ "
11020        "#
11021        .unindent(),
11022    );
11023
11024    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11025    // mark. Nothing is inserted.
11026    cx.update_editor(|editor, window, cx| {
11027        editor.move_right(&Default::default(), window, cx);
11028        editor.handle_input("\"", window, cx);
11029    });
11030    cx.assert_editor_state(
11031        &r#"
11032            let x = "\" "ˇ
11033        "#
11034        .unindent(),
11035    );
11036}
11037
11038#[gpui::test]
11039async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11040    init_test(cx, |_| {});
11041
11042    let mut cx = EditorTestContext::new(cx).await;
11043    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11044
11045    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11046
11047    // Double quote inside single-quoted string
11048    cx.set_state(indoc! {r#"
11049        def main():
11050            items = ['"', ˇ]
11051    "#});
11052    cx.update_editor(|editor, window, cx| {
11053        editor.handle_input("\"", window, cx);
11054    });
11055    cx.assert_editor_state(indoc! {r#"
11056        def main():
11057            items = ['"', "ˇ"]
11058    "#});
11059
11060    // Two double quotes inside single-quoted string
11061    cx.set_state(indoc! {r#"
11062        def main():
11063            items = ['""', ˇ]
11064    "#});
11065    cx.update_editor(|editor, window, cx| {
11066        editor.handle_input("\"", window, cx);
11067    });
11068    cx.assert_editor_state(indoc! {r#"
11069        def main():
11070            items = ['""', "ˇ"]
11071    "#});
11072
11073    // Single quote inside double-quoted string
11074    cx.set_state(indoc! {r#"
11075        def main():
11076            items = ["'", ˇ]
11077    "#});
11078    cx.update_editor(|editor, window, cx| {
11079        editor.handle_input("'", window, cx);
11080    });
11081    cx.assert_editor_state(indoc! {r#"
11082        def main():
11083            items = ["'", 'ˇ']
11084    "#});
11085
11086    // Two single quotes inside double-quoted string
11087    cx.set_state(indoc! {r#"
11088        def main():
11089            items = ["''", ˇ]
11090    "#});
11091    cx.update_editor(|editor, window, cx| {
11092        editor.handle_input("'", window, cx);
11093    });
11094    cx.assert_editor_state(indoc! {r#"
11095        def main():
11096            items = ["''", 'ˇ']
11097    "#});
11098
11099    // Mixed quotes on same line
11100    cx.set_state(indoc! {r#"
11101        def main():
11102            items = ['"""', "'''''", ˇ]
11103    "#});
11104    cx.update_editor(|editor, window, cx| {
11105        editor.handle_input("\"", window, cx);
11106    });
11107    cx.assert_editor_state(indoc! {r#"
11108        def main():
11109            items = ['"""', "'''''", "ˇ"]
11110    "#});
11111    cx.update_editor(|editor, window, cx| {
11112        editor.move_right(&MoveRight, window, cx);
11113    });
11114    cx.update_editor(|editor, window, cx| {
11115        editor.handle_input(", ", window, cx);
11116    });
11117    cx.update_editor(|editor, window, cx| {
11118        editor.handle_input("'", window, cx);
11119    });
11120    cx.assert_editor_state(indoc! {r#"
11121        def main():
11122            items = ['"""', "'''''", "", 'ˇ']
11123    "#});
11124}
11125
11126#[gpui::test]
11127async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
11128    init_test(cx, |_| {});
11129
11130    let mut cx = EditorTestContext::new(cx).await;
11131    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11132    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11133
11134    cx.set_state(indoc! {r#"
11135        def main():
11136            items = ["🎉", ˇ]
11137    "#});
11138    cx.update_editor(|editor, window, cx| {
11139        editor.handle_input("\"", window, cx);
11140    });
11141    cx.assert_editor_state(indoc! {r#"
11142        def main():
11143            items = ["🎉", "ˇ"]
11144    "#});
11145}
11146
11147#[gpui::test]
11148async fn test_surround_with_pair(cx: &mut TestAppContext) {
11149    init_test(cx, |_| {});
11150
11151    let language = Arc::new(Language::new(
11152        LanguageConfig {
11153            brackets: BracketPairConfig {
11154                pairs: vec![
11155                    BracketPair {
11156                        start: "{".to_string(),
11157                        end: "}".to_string(),
11158                        close: true,
11159                        surround: true,
11160                        newline: true,
11161                    },
11162                    BracketPair {
11163                        start: "/* ".to_string(),
11164                        end: "*/".to_string(),
11165                        close: true,
11166                        surround: true,
11167                        ..Default::default()
11168                    },
11169                ],
11170                ..Default::default()
11171            },
11172            ..Default::default()
11173        },
11174        Some(tree_sitter_rust::LANGUAGE.into()),
11175    ));
11176
11177    let text = r#"
11178        a
11179        b
11180        c
11181    "#
11182    .unindent();
11183
11184    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11185    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11186    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11187    editor
11188        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11189        .await;
11190
11191    editor.update_in(cx, |editor, window, cx| {
11192        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11193            s.select_display_ranges([
11194                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11195                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11196                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11197            ])
11198        });
11199
11200        editor.handle_input("{", window, cx);
11201        editor.handle_input("{", window, cx);
11202        editor.handle_input("{", window, cx);
11203        assert_eq!(
11204            editor.text(cx),
11205            "
11206                {{{a}}}
11207                {{{b}}}
11208                {{{c}}}
11209            "
11210            .unindent()
11211        );
11212        assert_eq!(
11213            display_ranges(editor, cx),
11214            [
11215                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11216                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11217                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11218            ]
11219        );
11220
11221        editor.undo(&Undo, window, cx);
11222        editor.undo(&Undo, window, cx);
11223        editor.undo(&Undo, window, cx);
11224        assert_eq!(
11225            editor.text(cx),
11226            "
11227                a
11228                b
11229                c
11230            "
11231            .unindent()
11232        );
11233        assert_eq!(
11234            display_ranges(editor, cx),
11235            [
11236                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11237                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11238                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11239            ]
11240        );
11241
11242        // Ensure inserting the first character of a multi-byte bracket pair
11243        // doesn't surround the selections with the bracket.
11244        editor.handle_input("/", window, cx);
11245        assert_eq!(
11246            editor.text(cx),
11247            "
11248                /
11249                /
11250                /
11251            "
11252            .unindent()
11253        );
11254        assert_eq!(
11255            display_ranges(editor, cx),
11256            [
11257                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11258                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11259                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11260            ]
11261        );
11262
11263        editor.undo(&Undo, window, cx);
11264        assert_eq!(
11265            editor.text(cx),
11266            "
11267                a
11268                b
11269                c
11270            "
11271            .unindent()
11272        );
11273        assert_eq!(
11274            display_ranges(editor, cx),
11275            [
11276                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11277                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11278                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11279            ]
11280        );
11281
11282        // Ensure inserting the last character of a multi-byte bracket pair
11283        // doesn't surround the selections with the bracket.
11284        editor.handle_input("*", window, cx);
11285        assert_eq!(
11286            editor.text(cx),
11287            "
11288                *
11289                *
11290                *
11291            "
11292            .unindent()
11293        );
11294        assert_eq!(
11295            display_ranges(editor, cx),
11296            [
11297                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11298                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11299                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11300            ]
11301        );
11302    });
11303}
11304
11305#[gpui::test]
11306async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11307    init_test(cx, |_| {});
11308
11309    let language = Arc::new(Language::new(
11310        LanguageConfig {
11311            brackets: BracketPairConfig {
11312                pairs: vec![BracketPair {
11313                    start: "{".to_string(),
11314                    end: "}".to_string(),
11315                    close: true,
11316                    surround: true,
11317                    newline: true,
11318                }],
11319                ..Default::default()
11320            },
11321            autoclose_before: "}".to_string(),
11322            ..Default::default()
11323        },
11324        Some(tree_sitter_rust::LANGUAGE.into()),
11325    ));
11326
11327    let text = r#"
11328        a
11329        b
11330        c
11331    "#
11332    .unindent();
11333
11334    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11335    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11336    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11337    editor
11338        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11339        .await;
11340
11341    editor.update_in(cx, |editor, window, cx| {
11342        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11343            s.select_ranges([
11344                Point::new(0, 1)..Point::new(0, 1),
11345                Point::new(1, 1)..Point::new(1, 1),
11346                Point::new(2, 1)..Point::new(2, 1),
11347            ])
11348        });
11349
11350        editor.handle_input("{", window, cx);
11351        editor.handle_input("{", window, cx);
11352        editor.handle_input("_", window, cx);
11353        assert_eq!(
11354            editor.text(cx),
11355            "
11356                a{{_}}
11357                b{{_}}
11358                c{{_}}
11359            "
11360            .unindent()
11361        );
11362        assert_eq!(
11363            editor
11364                .selections
11365                .ranges::<Point>(&editor.display_snapshot(cx)),
11366            [
11367                Point::new(0, 4)..Point::new(0, 4),
11368                Point::new(1, 4)..Point::new(1, 4),
11369                Point::new(2, 4)..Point::new(2, 4)
11370            ]
11371        );
11372
11373        editor.backspace(&Default::default(), window, cx);
11374        editor.backspace(&Default::default(), window, cx);
11375        assert_eq!(
11376            editor.text(cx),
11377            "
11378                a{}
11379                b{}
11380                c{}
11381            "
11382            .unindent()
11383        );
11384        assert_eq!(
11385            editor
11386                .selections
11387                .ranges::<Point>(&editor.display_snapshot(cx)),
11388            [
11389                Point::new(0, 2)..Point::new(0, 2),
11390                Point::new(1, 2)..Point::new(1, 2),
11391                Point::new(2, 2)..Point::new(2, 2)
11392            ]
11393        );
11394
11395        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11396        assert_eq!(
11397            editor.text(cx),
11398            "
11399                a
11400                b
11401                c
11402            "
11403            .unindent()
11404        );
11405        assert_eq!(
11406            editor
11407                .selections
11408                .ranges::<Point>(&editor.display_snapshot(cx)),
11409            [
11410                Point::new(0, 1)..Point::new(0, 1),
11411                Point::new(1, 1)..Point::new(1, 1),
11412                Point::new(2, 1)..Point::new(2, 1)
11413            ]
11414        );
11415    });
11416}
11417
11418#[gpui::test]
11419async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11420    init_test(cx, |settings| {
11421        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11422    });
11423
11424    let mut cx = EditorTestContext::new(cx).await;
11425
11426    let language = Arc::new(Language::new(
11427        LanguageConfig {
11428            brackets: BracketPairConfig {
11429                pairs: vec![
11430                    BracketPair {
11431                        start: "{".to_string(),
11432                        end: "}".to_string(),
11433                        close: true,
11434                        surround: true,
11435                        newline: true,
11436                    },
11437                    BracketPair {
11438                        start: "(".to_string(),
11439                        end: ")".to_string(),
11440                        close: true,
11441                        surround: true,
11442                        newline: true,
11443                    },
11444                    BracketPair {
11445                        start: "[".to_string(),
11446                        end: "]".to_string(),
11447                        close: false,
11448                        surround: true,
11449                        newline: true,
11450                    },
11451                ],
11452                ..Default::default()
11453            },
11454            autoclose_before: "})]".to_string(),
11455            ..Default::default()
11456        },
11457        Some(tree_sitter_rust::LANGUAGE.into()),
11458    ));
11459
11460    cx.language_registry().add(language.clone());
11461    cx.update_buffer(|buffer, cx| {
11462        buffer.set_language(Some(language), cx);
11463    });
11464
11465    cx.set_state(
11466        &"
11467            {(ˇ)}
11468            [[ˇ]]
11469            {(ˇ)}
11470        "
11471        .unindent(),
11472    );
11473
11474    cx.update_editor(|editor, window, cx| {
11475        editor.backspace(&Default::default(), window, cx);
11476        editor.backspace(&Default::default(), window, cx);
11477    });
11478
11479    cx.assert_editor_state(
11480        &"
11481            ˇ
11482            ˇ]]
11483            ˇ
11484        "
11485        .unindent(),
11486    );
11487
11488    cx.update_editor(|editor, window, cx| {
11489        editor.handle_input("{", window, cx);
11490        editor.handle_input("{", window, cx);
11491        editor.move_right(&MoveRight, window, cx);
11492        editor.move_right(&MoveRight, window, cx);
11493        editor.move_left(&MoveLeft, window, cx);
11494        editor.move_left(&MoveLeft, window, cx);
11495        editor.backspace(&Default::default(), window, cx);
11496    });
11497
11498    cx.assert_editor_state(
11499        &"
11500            {ˇ}
11501            {ˇ}]]
11502            {ˇ}
11503        "
11504        .unindent(),
11505    );
11506
11507    cx.update_editor(|editor, window, cx| {
11508        editor.backspace(&Default::default(), window, cx);
11509    });
11510
11511    cx.assert_editor_state(
11512        &"
11513            ˇ
11514            ˇ]]
11515            ˇ
11516        "
11517        .unindent(),
11518    );
11519}
11520
11521#[gpui::test]
11522async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11523    init_test(cx, |_| {});
11524
11525    let language = Arc::new(Language::new(
11526        LanguageConfig::default(),
11527        Some(tree_sitter_rust::LANGUAGE.into()),
11528    ));
11529
11530    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11531    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11532    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11533    editor
11534        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11535        .await;
11536
11537    editor.update_in(cx, |editor, window, cx| {
11538        editor.set_auto_replace_emoji_shortcode(true);
11539
11540        editor.handle_input("Hello ", window, cx);
11541        editor.handle_input(":wave", window, cx);
11542        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11543
11544        editor.handle_input(":", window, cx);
11545        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11546
11547        editor.handle_input(" :smile", window, cx);
11548        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11549
11550        editor.handle_input(":", window, cx);
11551        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11552
11553        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11554        editor.handle_input(":wave", window, cx);
11555        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11556
11557        editor.handle_input(":", window, cx);
11558        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11559
11560        editor.handle_input(":1", window, cx);
11561        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11562
11563        editor.handle_input(":", window, cx);
11564        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11565
11566        // Ensure shortcode does not get replaced when it is part of a word
11567        editor.handle_input(" Test:wave", window, cx);
11568        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11569
11570        editor.handle_input(":", window, cx);
11571        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11572
11573        editor.set_auto_replace_emoji_shortcode(false);
11574
11575        // Ensure shortcode does not get replaced when auto replace is off
11576        editor.handle_input(" :wave", window, cx);
11577        assert_eq!(
11578            editor.text(cx),
11579            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11580        );
11581
11582        editor.handle_input(":", window, cx);
11583        assert_eq!(
11584            editor.text(cx),
11585            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11586        );
11587    });
11588}
11589
11590#[gpui::test]
11591async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11592    init_test(cx, |_| {});
11593
11594    let (text, insertion_ranges) = marked_text_ranges(
11595        indoc! {"
11596            ˇ
11597        "},
11598        false,
11599    );
11600
11601    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11602    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11603
11604    _ = editor.update_in(cx, |editor, window, cx| {
11605        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11606
11607        editor
11608            .insert_snippet(
11609                &insertion_ranges
11610                    .iter()
11611                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11612                    .collect::<Vec<_>>(),
11613                snippet,
11614                window,
11615                cx,
11616            )
11617            .unwrap();
11618
11619        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11620            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11621            assert_eq!(editor.text(cx), expected_text);
11622            assert_eq!(
11623                editor.selections.ranges(&editor.display_snapshot(cx)),
11624                selection_ranges
11625                    .iter()
11626                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11627                    .collect::<Vec<_>>()
11628            );
11629        }
11630
11631        assert(
11632            editor,
11633            cx,
11634            indoc! {"
11635            type «» =•
11636            "},
11637        );
11638
11639        assert!(editor.context_menu_visible(), "There should be a matches");
11640    });
11641}
11642
11643#[gpui::test]
11644async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11645    init_test(cx, |_| {});
11646
11647    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11648        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11649        assert_eq!(editor.text(cx), expected_text);
11650        assert_eq!(
11651            editor.selections.ranges(&editor.display_snapshot(cx)),
11652            selection_ranges
11653                .iter()
11654                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11655                .collect::<Vec<_>>()
11656        );
11657    }
11658
11659    let (text, insertion_ranges) = marked_text_ranges(
11660        indoc! {"
11661            ˇ
11662        "},
11663        false,
11664    );
11665
11666    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11667    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11668
11669    _ = editor.update_in(cx, |editor, window, cx| {
11670        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11671
11672        editor
11673            .insert_snippet(
11674                &insertion_ranges
11675                    .iter()
11676                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11677                    .collect::<Vec<_>>(),
11678                snippet,
11679                window,
11680                cx,
11681            )
11682            .unwrap();
11683
11684        assert_state(
11685            editor,
11686            cx,
11687            indoc! {"
11688            type «» = ;•
11689            "},
11690        );
11691
11692        assert!(
11693            editor.context_menu_visible(),
11694            "Context menu should be visible for placeholder choices"
11695        );
11696
11697        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11698
11699        assert_state(
11700            editor,
11701            cx,
11702            indoc! {"
11703            type  = «»;•
11704            "},
11705        );
11706
11707        assert!(
11708            !editor.context_menu_visible(),
11709            "Context menu should be hidden after moving to next tabstop"
11710        );
11711
11712        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11713
11714        assert_state(
11715            editor,
11716            cx,
11717            indoc! {"
11718            type  = ; ˇ
11719            "},
11720        );
11721
11722        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11723
11724        assert_state(
11725            editor,
11726            cx,
11727            indoc! {"
11728            type  = ; ˇ
11729            "},
11730        );
11731    });
11732
11733    _ = editor.update_in(cx, |editor, window, cx| {
11734        editor.select_all(&SelectAll, window, cx);
11735        editor.backspace(&Backspace, window, cx);
11736
11737        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11738        let insertion_ranges = editor
11739            .selections
11740            .all(&editor.display_snapshot(cx))
11741            .iter()
11742            .map(|s| s.range())
11743            .collect::<Vec<_>>();
11744
11745        editor
11746            .insert_snippet(&insertion_ranges, snippet, window, cx)
11747            .unwrap();
11748
11749        assert_state(editor, cx, "fn «» = value;•");
11750
11751        assert!(
11752            editor.context_menu_visible(),
11753            "Context menu should be visible for placeholder choices"
11754        );
11755
11756        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11757
11758        assert_state(editor, cx, "fn  = «valueˇ»;•");
11759
11760        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11761
11762        assert_state(editor, cx, "fn «» = value;•");
11763
11764        assert!(
11765            editor.context_menu_visible(),
11766            "Context menu should be visible again after returning to first tabstop"
11767        );
11768
11769        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11770
11771        assert_state(editor, cx, "fn «» = value;•");
11772    });
11773}
11774
11775#[gpui::test]
11776async fn test_snippets(cx: &mut TestAppContext) {
11777    init_test(cx, |_| {});
11778
11779    let mut cx = EditorTestContext::new(cx).await;
11780
11781    cx.set_state(indoc! {"
11782        a.ˇ b
11783        a.ˇ b
11784        a.ˇ b
11785    "});
11786
11787    cx.update_editor(|editor, window, cx| {
11788        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11789        let insertion_ranges = editor
11790            .selections
11791            .all(&editor.display_snapshot(cx))
11792            .iter()
11793            .map(|s| s.range())
11794            .collect::<Vec<_>>();
11795        editor
11796            .insert_snippet(&insertion_ranges, snippet, window, cx)
11797            .unwrap();
11798    });
11799
11800    cx.assert_editor_state(indoc! {"
11801        a.f(«oneˇ», two, «threeˇ») b
11802        a.f(«oneˇ», two, «threeˇ») b
11803        a.f(«oneˇ», two, «threeˇ») b
11804    "});
11805
11806    // Can't move earlier than the first tab stop
11807    cx.update_editor(|editor, window, cx| {
11808        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11809    });
11810    cx.assert_editor_state(indoc! {"
11811        a.f(«oneˇ», two, «threeˇ») b
11812        a.f(«oneˇ», two, «threeˇ») b
11813        a.f(«oneˇ», two, «threeˇ») b
11814    "});
11815
11816    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11817    cx.assert_editor_state(indoc! {"
11818        a.f(one, «twoˇ», three) b
11819        a.f(one, «twoˇ», three) b
11820        a.f(one, «twoˇ», three) b
11821    "});
11822
11823    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11824    cx.assert_editor_state(indoc! {"
11825        a.f(«oneˇ», two, «threeˇ») b
11826        a.f(«oneˇ», two, «threeˇ») b
11827        a.f(«oneˇ», two, «threeˇ») b
11828    "});
11829
11830    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11831    cx.assert_editor_state(indoc! {"
11832        a.f(one, «twoˇ», three) b
11833        a.f(one, «twoˇ», three) b
11834        a.f(one, «twoˇ», three) b
11835    "});
11836    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11837    cx.assert_editor_state(indoc! {"
11838        a.f(one, two, three)ˇ b
11839        a.f(one, two, three)ˇ b
11840        a.f(one, two, three)ˇ b
11841    "});
11842
11843    // As soon as the last tab stop is reached, snippet state is gone
11844    cx.update_editor(|editor, window, cx| {
11845        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11846    });
11847    cx.assert_editor_state(indoc! {"
11848        a.f(one, two, three)ˇ b
11849        a.f(one, two, three)ˇ b
11850        a.f(one, two, three)ˇ b
11851    "});
11852}
11853
11854#[gpui::test]
11855async fn test_snippet_indentation(cx: &mut TestAppContext) {
11856    init_test(cx, |_| {});
11857
11858    let mut cx = EditorTestContext::new(cx).await;
11859
11860    cx.update_editor(|editor, window, cx| {
11861        let snippet = Snippet::parse(indoc! {"
11862            /*
11863             * Multiline comment with leading indentation
11864             *
11865             * $1
11866             */
11867            $0"})
11868        .unwrap();
11869        let insertion_ranges = editor
11870            .selections
11871            .all(&editor.display_snapshot(cx))
11872            .iter()
11873            .map(|s| s.range())
11874            .collect::<Vec<_>>();
11875        editor
11876            .insert_snippet(&insertion_ranges, snippet, window, cx)
11877            .unwrap();
11878    });
11879
11880    cx.assert_editor_state(indoc! {"
11881        /*
11882         * Multiline comment with leading indentation
11883         *
11884         * ˇ
11885         */
11886    "});
11887
11888    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11889    cx.assert_editor_state(indoc! {"
11890        /*
11891         * Multiline comment with leading indentation
11892         *
11893         *•
11894         */
11895        ˇ"});
11896}
11897
11898#[gpui::test]
11899async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11900    init_test(cx, |_| {});
11901
11902    let mut cx = EditorTestContext::new(cx).await;
11903    cx.update_editor(|editor, _, cx| {
11904        editor.project().unwrap().update(cx, |project, cx| {
11905            project.snippets().update(cx, |snippets, _cx| {
11906                let snippet = project::snippet_provider::Snippet {
11907                    prefix: vec!["multi word".to_string()],
11908                    body: "this is many words".to_string(),
11909                    description: Some("description".to_string()),
11910                    name: "multi-word snippet test".to_string(),
11911                };
11912                snippets.add_snippet_for_test(
11913                    None,
11914                    PathBuf::from("test_snippets.json"),
11915                    vec![Arc::new(snippet)],
11916                );
11917            });
11918        })
11919    });
11920
11921    for (input_to_simulate, should_match_snippet) in [
11922        ("m", true),
11923        ("m ", true),
11924        ("m w", true),
11925        ("aa m w", true),
11926        ("aa m g", false),
11927    ] {
11928        cx.set_state("ˇ");
11929        cx.simulate_input(input_to_simulate); // fails correctly
11930
11931        cx.update_editor(|editor, _, _| {
11932            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11933            else {
11934                assert!(!should_match_snippet); // no completions! don't even show the menu
11935                return;
11936            };
11937            assert!(context_menu.visible());
11938            let completions = context_menu.completions.borrow();
11939
11940            assert_eq!(!completions.is_empty(), should_match_snippet);
11941        });
11942    }
11943}
11944
11945#[gpui::test]
11946async fn test_document_format_during_save(cx: &mut TestAppContext) {
11947    init_test(cx, |_| {});
11948
11949    let fs = FakeFs::new(cx.executor());
11950    fs.insert_file(path!("/file.rs"), Default::default()).await;
11951
11952    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11953
11954    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11955    language_registry.add(rust_lang());
11956    let mut fake_servers = language_registry.register_fake_lsp(
11957        "Rust",
11958        FakeLspAdapter {
11959            capabilities: lsp::ServerCapabilities {
11960                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11961                ..Default::default()
11962            },
11963            ..Default::default()
11964        },
11965    );
11966
11967    let buffer = project
11968        .update(cx, |project, cx| {
11969            project.open_local_buffer(path!("/file.rs"), cx)
11970        })
11971        .await
11972        .unwrap();
11973
11974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11975    let (editor, cx) = cx.add_window_view(|window, cx| {
11976        build_editor_with_project(project.clone(), buffer, window, cx)
11977    });
11978    editor.update_in(cx, |editor, window, cx| {
11979        editor.set_text("one\ntwo\nthree\n", window, cx)
11980    });
11981    assert!(cx.read(|cx| editor.is_dirty(cx)));
11982
11983    let fake_server = fake_servers.next().await.unwrap();
11984
11985    {
11986        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11987            move |params, _| async move {
11988                assert_eq!(
11989                    params.text_document.uri,
11990                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11991                );
11992                assert_eq!(params.options.tab_size, 4);
11993                Ok(Some(vec![lsp::TextEdit::new(
11994                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11995                    ", ".to_string(),
11996                )]))
11997            },
11998        );
11999        let save = editor
12000            .update_in(cx, |editor, window, cx| {
12001                editor.save(
12002                    SaveOptions {
12003                        format: true,
12004                        autosave: false,
12005                    },
12006                    project.clone(),
12007                    window,
12008                    cx,
12009                )
12010            })
12011            .unwrap();
12012        save.await;
12013
12014        assert_eq!(
12015            editor.update(cx, |editor, cx| editor.text(cx)),
12016            "one, two\nthree\n"
12017        );
12018        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12019    }
12020
12021    {
12022        editor.update_in(cx, |editor, window, cx| {
12023            editor.set_text("one\ntwo\nthree\n", window, cx)
12024        });
12025        assert!(cx.read(|cx| editor.is_dirty(cx)));
12026
12027        // Ensure we can still save even if formatting hangs.
12028        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12029            move |params, _| async move {
12030                assert_eq!(
12031                    params.text_document.uri,
12032                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12033                );
12034                futures::future::pending::<()>().await;
12035                unreachable!()
12036            },
12037        );
12038        let save = editor
12039            .update_in(cx, |editor, window, cx| {
12040                editor.save(
12041                    SaveOptions {
12042                        format: true,
12043                        autosave: false,
12044                    },
12045                    project.clone(),
12046                    window,
12047                    cx,
12048                )
12049            })
12050            .unwrap();
12051        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12052        save.await;
12053        assert_eq!(
12054            editor.update(cx, |editor, cx| editor.text(cx)),
12055            "one\ntwo\nthree\n"
12056        );
12057    }
12058
12059    // Set rust language override and assert overridden tabsize is sent to language server
12060    update_test_language_settings(cx, |settings| {
12061        settings.languages.0.insert(
12062            "Rust".into(),
12063            LanguageSettingsContent {
12064                tab_size: NonZeroU32::new(8),
12065                ..Default::default()
12066            },
12067        );
12068    });
12069
12070    {
12071        editor.update_in(cx, |editor, window, cx| {
12072            editor.set_text("somehting_new\n", window, cx)
12073        });
12074        assert!(cx.read(|cx| editor.is_dirty(cx)));
12075        let _formatting_request_signal = fake_server
12076            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12077                assert_eq!(
12078                    params.text_document.uri,
12079                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12080                );
12081                assert_eq!(params.options.tab_size, 8);
12082                Ok(Some(vec![]))
12083            });
12084        let save = editor
12085            .update_in(cx, |editor, window, cx| {
12086                editor.save(
12087                    SaveOptions {
12088                        format: true,
12089                        autosave: false,
12090                    },
12091                    project.clone(),
12092                    window,
12093                    cx,
12094                )
12095            })
12096            .unwrap();
12097        save.await;
12098    }
12099}
12100
12101#[gpui::test]
12102async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
12103    init_test(cx, |settings| {
12104        settings.defaults.ensure_final_newline_on_save = Some(false);
12105    });
12106
12107    let fs = FakeFs::new(cx.executor());
12108    fs.insert_file(path!("/file.txt"), "foo".into()).await;
12109
12110    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
12111
12112    let buffer = project
12113        .update(cx, |project, cx| {
12114            project.open_local_buffer(path!("/file.txt"), cx)
12115        })
12116        .await
12117        .unwrap();
12118
12119    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12120    let (editor, cx) = cx.add_window_view(|window, cx| {
12121        build_editor_with_project(project.clone(), buffer, window, cx)
12122    });
12123    editor.update_in(cx, |editor, window, cx| {
12124        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
12125            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
12126        });
12127    });
12128    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12129
12130    editor.update_in(cx, |editor, window, cx| {
12131        editor.handle_input("\n", window, cx)
12132    });
12133    cx.run_until_parked();
12134    save(&editor, &project, cx).await;
12135    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12136
12137    editor.update_in(cx, |editor, window, cx| {
12138        editor.undo(&Default::default(), window, cx);
12139    });
12140    save(&editor, &project, cx).await;
12141    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12142
12143    editor.update_in(cx, |editor, window, cx| {
12144        editor.redo(&Default::default(), window, cx);
12145    });
12146    cx.run_until_parked();
12147    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
12148
12149    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
12150        let save = editor
12151            .update_in(cx, |editor, window, cx| {
12152                editor.save(
12153                    SaveOptions {
12154                        format: true,
12155                        autosave: false,
12156                    },
12157                    project.clone(),
12158                    window,
12159                    cx,
12160                )
12161            })
12162            .unwrap();
12163        save.await;
12164        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12165    }
12166}
12167
12168#[gpui::test]
12169async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12170    init_test(cx, |_| {});
12171
12172    let cols = 4;
12173    let rows = 10;
12174    let sample_text_1 = sample_text(rows, cols, 'a');
12175    assert_eq!(
12176        sample_text_1,
12177        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12178    );
12179    let sample_text_2 = sample_text(rows, cols, 'l');
12180    assert_eq!(
12181        sample_text_2,
12182        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12183    );
12184    let sample_text_3 = sample_text(rows, cols, 'v');
12185    assert_eq!(
12186        sample_text_3,
12187        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12188    );
12189
12190    let fs = FakeFs::new(cx.executor());
12191    fs.insert_tree(
12192        path!("/a"),
12193        json!({
12194            "main.rs": sample_text_1,
12195            "other.rs": sample_text_2,
12196            "lib.rs": sample_text_3,
12197        }),
12198    )
12199    .await;
12200
12201    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12202    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12203    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12204
12205    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12206    language_registry.add(rust_lang());
12207    let mut fake_servers = language_registry.register_fake_lsp(
12208        "Rust",
12209        FakeLspAdapter {
12210            capabilities: lsp::ServerCapabilities {
12211                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12212                ..Default::default()
12213            },
12214            ..Default::default()
12215        },
12216    );
12217
12218    let worktree = project.update(cx, |project, cx| {
12219        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12220        assert_eq!(worktrees.len(), 1);
12221        worktrees.pop().unwrap()
12222    });
12223    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12224
12225    let buffer_1 = project
12226        .update(cx, |project, cx| {
12227            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12228        })
12229        .await
12230        .unwrap();
12231    let buffer_2 = project
12232        .update(cx, |project, cx| {
12233            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12234        })
12235        .await
12236        .unwrap();
12237    let buffer_3 = project
12238        .update(cx, |project, cx| {
12239            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12240        })
12241        .await
12242        .unwrap();
12243
12244    let multi_buffer = cx.new(|cx| {
12245        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12246        multi_buffer.push_excerpts(
12247            buffer_1.clone(),
12248            [
12249                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12250                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12251                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12252            ],
12253            cx,
12254        );
12255        multi_buffer.push_excerpts(
12256            buffer_2.clone(),
12257            [
12258                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12259                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12260                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12261            ],
12262            cx,
12263        );
12264        multi_buffer.push_excerpts(
12265            buffer_3.clone(),
12266            [
12267                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12268                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12269                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12270            ],
12271            cx,
12272        );
12273        multi_buffer
12274    });
12275    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12276        Editor::new(
12277            EditorMode::full(),
12278            multi_buffer,
12279            Some(project.clone()),
12280            window,
12281            cx,
12282        )
12283    });
12284
12285    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12286        editor.change_selections(
12287            SelectionEffects::scroll(Autoscroll::Next),
12288            window,
12289            cx,
12290            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12291        );
12292        editor.insert("|one|two|three|", window, cx);
12293    });
12294    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12295    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12296        editor.change_selections(
12297            SelectionEffects::scroll(Autoscroll::Next),
12298            window,
12299            cx,
12300            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12301        );
12302        editor.insert("|four|five|six|", window, cx);
12303    });
12304    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12305
12306    // First two buffers should be edited, but not the third one.
12307    assert_eq!(
12308        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12309        "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}",
12310    );
12311    buffer_1.update(cx, |buffer, _| {
12312        assert!(buffer.is_dirty());
12313        assert_eq!(
12314            buffer.text(),
12315            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12316        )
12317    });
12318    buffer_2.update(cx, |buffer, _| {
12319        assert!(buffer.is_dirty());
12320        assert_eq!(
12321            buffer.text(),
12322            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12323        )
12324    });
12325    buffer_3.update(cx, |buffer, _| {
12326        assert!(!buffer.is_dirty());
12327        assert_eq!(buffer.text(), sample_text_3,)
12328    });
12329    cx.executor().run_until_parked();
12330
12331    let save = multi_buffer_editor
12332        .update_in(cx, |editor, window, cx| {
12333            editor.save(
12334                SaveOptions {
12335                    format: true,
12336                    autosave: false,
12337                },
12338                project.clone(),
12339                window,
12340                cx,
12341            )
12342        })
12343        .unwrap();
12344
12345    let fake_server = fake_servers.next().await.unwrap();
12346    fake_server
12347        .server
12348        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12349            Ok(Some(vec![lsp::TextEdit::new(
12350                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12351                format!("[{} formatted]", params.text_document.uri),
12352            )]))
12353        })
12354        .detach();
12355    save.await;
12356
12357    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12358    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12359    assert_eq!(
12360        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12361        uri!(
12362            "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}"
12363        ),
12364    );
12365    buffer_1.update(cx, |buffer, _| {
12366        assert!(!buffer.is_dirty());
12367        assert_eq!(
12368            buffer.text(),
12369            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12370        )
12371    });
12372    buffer_2.update(cx, |buffer, _| {
12373        assert!(!buffer.is_dirty());
12374        assert_eq!(
12375            buffer.text(),
12376            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12377        )
12378    });
12379    buffer_3.update(cx, |buffer, _| {
12380        assert!(!buffer.is_dirty());
12381        assert_eq!(buffer.text(), sample_text_3,)
12382    });
12383}
12384
12385#[gpui::test]
12386async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12387    init_test(cx, |_| {});
12388
12389    let fs = FakeFs::new(cx.executor());
12390    fs.insert_tree(
12391        path!("/dir"),
12392        json!({
12393            "file1.rs": "fn main() { println!(\"hello\"); }",
12394            "file2.rs": "fn test() { println!(\"test\"); }",
12395            "file3.rs": "fn other() { println!(\"other\"); }\n",
12396        }),
12397    )
12398    .await;
12399
12400    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12401    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12402    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12403
12404    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12405    language_registry.add(rust_lang());
12406
12407    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12408    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12409
12410    // Open three buffers
12411    let buffer_1 = project
12412        .update(cx, |project, cx| {
12413            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12414        })
12415        .await
12416        .unwrap();
12417    let buffer_2 = project
12418        .update(cx, |project, cx| {
12419            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12420        })
12421        .await
12422        .unwrap();
12423    let buffer_3 = project
12424        .update(cx, |project, cx| {
12425            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12426        })
12427        .await
12428        .unwrap();
12429
12430    // Create a multi-buffer with all three buffers
12431    let multi_buffer = cx.new(|cx| {
12432        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12433        multi_buffer.push_excerpts(
12434            buffer_1.clone(),
12435            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12436            cx,
12437        );
12438        multi_buffer.push_excerpts(
12439            buffer_2.clone(),
12440            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12441            cx,
12442        );
12443        multi_buffer.push_excerpts(
12444            buffer_3.clone(),
12445            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12446            cx,
12447        );
12448        multi_buffer
12449    });
12450
12451    let editor = cx.new_window_entity(|window, cx| {
12452        Editor::new(
12453            EditorMode::full(),
12454            multi_buffer,
12455            Some(project.clone()),
12456            window,
12457            cx,
12458        )
12459    });
12460
12461    // Edit only the first buffer
12462    editor.update_in(cx, |editor, window, cx| {
12463        editor.change_selections(
12464            SelectionEffects::scroll(Autoscroll::Next),
12465            window,
12466            cx,
12467            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12468        );
12469        editor.insert("// edited", window, cx);
12470    });
12471
12472    // Verify that only buffer 1 is dirty
12473    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12474    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12475    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12476
12477    // Get write counts after file creation (files were created with initial content)
12478    // We expect each file to have been written once during creation
12479    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12480    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12481    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12482
12483    // Perform autosave
12484    let save_task = editor.update_in(cx, |editor, window, cx| {
12485        editor.save(
12486            SaveOptions {
12487                format: true,
12488                autosave: true,
12489            },
12490            project.clone(),
12491            window,
12492            cx,
12493        )
12494    });
12495    save_task.await.unwrap();
12496
12497    // Only the dirty buffer should have been saved
12498    assert_eq!(
12499        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12500        1,
12501        "Buffer 1 was dirty, so it should have been written once during autosave"
12502    );
12503    assert_eq!(
12504        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12505        0,
12506        "Buffer 2 was clean, so it should not have been written during autosave"
12507    );
12508    assert_eq!(
12509        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12510        0,
12511        "Buffer 3 was clean, so it should not have been written during autosave"
12512    );
12513
12514    // Verify buffer states after autosave
12515    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12516    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12517    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12518
12519    // Now perform a manual save (format = true)
12520    let save_task = editor.update_in(cx, |editor, window, cx| {
12521        editor.save(
12522            SaveOptions {
12523                format: true,
12524                autosave: false,
12525            },
12526            project.clone(),
12527            window,
12528            cx,
12529        )
12530    });
12531    save_task.await.unwrap();
12532
12533    // During manual save, clean buffers don't get written to disk
12534    // They just get did_save called for language server notifications
12535    assert_eq!(
12536        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12537        1,
12538        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12539    );
12540    assert_eq!(
12541        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12542        0,
12543        "Buffer 2 should not have been written at all"
12544    );
12545    assert_eq!(
12546        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12547        0,
12548        "Buffer 3 should not have been written at all"
12549    );
12550}
12551
12552async fn setup_range_format_test(
12553    cx: &mut TestAppContext,
12554) -> (
12555    Entity<Project>,
12556    Entity<Editor>,
12557    &mut gpui::VisualTestContext,
12558    lsp::FakeLanguageServer,
12559) {
12560    init_test(cx, |_| {});
12561
12562    let fs = FakeFs::new(cx.executor());
12563    fs.insert_file(path!("/file.rs"), Default::default()).await;
12564
12565    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12566
12567    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12568    language_registry.add(rust_lang());
12569    let mut fake_servers = language_registry.register_fake_lsp(
12570        "Rust",
12571        FakeLspAdapter {
12572            capabilities: lsp::ServerCapabilities {
12573                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12574                ..lsp::ServerCapabilities::default()
12575            },
12576            ..FakeLspAdapter::default()
12577        },
12578    );
12579
12580    let buffer = project
12581        .update(cx, |project, cx| {
12582            project.open_local_buffer(path!("/file.rs"), cx)
12583        })
12584        .await
12585        .unwrap();
12586
12587    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12588    let (editor, cx) = cx.add_window_view(|window, cx| {
12589        build_editor_with_project(project.clone(), buffer, window, cx)
12590    });
12591
12592    let fake_server = fake_servers.next().await.unwrap();
12593
12594    (project, editor, cx, fake_server)
12595}
12596
12597#[gpui::test]
12598async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12599    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12600
12601    editor.update_in(cx, |editor, window, cx| {
12602        editor.set_text("one\ntwo\nthree\n", window, cx)
12603    });
12604    assert!(cx.read(|cx| editor.is_dirty(cx)));
12605
12606    let save = editor
12607        .update_in(cx, |editor, window, cx| {
12608            editor.save(
12609                SaveOptions {
12610                    format: true,
12611                    autosave: false,
12612                },
12613                project.clone(),
12614                window,
12615                cx,
12616            )
12617        })
12618        .unwrap();
12619    fake_server
12620        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12621            assert_eq!(
12622                params.text_document.uri,
12623                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12624            );
12625            assert_eq!(params.options.tab_size, 4);
12626            Ok(Some(vec![lsp::TextEdit::new(
12627                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12628                ", ".to_string(),
12629            )]))
12630        })
12631        .next()
12632        .await;
12633    save.await;
12634    assert_eq!(
12635        editor.update(cx, |editor, cx| editor.text(cx)),
12636        "one, two\nthree\n"
12637    );
12638    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12639}
12640
12641#[gpui::test]
12642async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12643    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12644
12645    editor.update_in(cx, |editor, window, cx| {
12646        editor.set_text("one\ntwo\nthree\n", window, cx)
12647    });
12648    assert!(cx.read(|cx| editor.is_dirty(cx)));
12649
12650    // Test that save still works when formatting hangs
12651    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12652        move |params, _| async move {
12653            assert_eq!(
12654                params.text_document.uri,
12655                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12656            );
12657            futures::future::pending::<()>().await;
12658            unreachable!()
12659        },
12660    );
12661    let save = editor
12662        .update_in(cx, |editor, window, cx| {
12663            editor.save(
12664                SaveOptions {
12665                    format: true,
12666                    autosave: false,
12667                },
12668                project.clone(),
12669                window,
12670                cx,
12671            )
12672        })
12673        .unwrap();
12674    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12675    save.await;
12676    assert_eq!(
12677        editor.update(cx, |editor, cx| editor.text(cx)),
12678        "one\ntwo\nthree\n"
12679    );
12680    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12681}
12682
12683#[gpui::test]
12684async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12685    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12686
12687    // Buffer starts clean, no formatting should be requested
12688    let save = editor
12689        .update_in(cx, |editor, window, cx| {
12690            editor.save(
12691                SaveOptions {
12692                    format: false,
12693                    autosave: false,
12694                },
12695                project.clone(),
12696                window,
12697                cx,
12698            )
12699        })
12700        .unwrap();
12701    let _pending_format_request = fake_server
12702        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12703            panic!("Should not be invoked");
12704        })
12705        .next();
12706    save.await;
12707    cx.run_until_parked();
12708}
12709
12710#[gpui::test]
12711async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12712    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12713
12714    // Set Rust language override and assert overridden tabsize is sent to language server
12715    update_test_language_settings(cx, |settings| {
12716        settings.languages.0.insert(
12717            "Rust".into(),
12718            LanguageSettingsContent {
12719                tab_size: NonZeroU32::new(8),
12720                ..Default::default()
12721            },
12722        );
12723    });
12724
12725    editor.update_in(cx, |editor, window, cx| {
12726        editor.set_text("something_new\n", window, cx)
12727    });
12728    assert!(cx.read(|cx| editor.is_dirty(cx)));
12729    let save = editor
12730        .update_in(cx, |editor, window, cx| {
12731            editor.save(
12732                SaveOptions {
12733                    format: true,
12734                    autosave: false,
12735                },
12736                project.clone(),
12737                window,
12738                cx,
12739            )
12740        })
12741        .unwrap();
12742    fake_server
12743        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12744            assert_eq!(
12745                params.text_document.uri,
12746                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12747            );
12748            assert_eq!(params.options.tab_size, 8);
12749            Ok(Some(Vec::new()))
12750        })
12751        .next()
12752        .await;
12753    save.await;
12754}
12755
12756#[gpui::test]
12757async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12758    init_test(cx, |settings| {
12759        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12760            settings::LanguageServerFormatterSpecifier::Current,
12761        )))
12762    });
12763
12764    let fs = FakeFs::new(cx.executor());
12765    fs.insert_file(path!("/file.rs"), Default::default()).await;
12766
12767    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12768
12769    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12770    language_registry.add(Arc::new(Language::new(
12771        LanguageConfig {
12772            name: "Rust".into(),
12773            matcher: LanguageMatcher {
12774                path_suffixes: vec!["rs".to_string()],
12775                ..Default::default()
12776            },
12777            ..LanguageConfig::default()
12778        },
12779        Some(tree_sitter_rust::LANGUAGE.into()),
12780    )));
12781    update_test_language_settings(cx, |settings| {
12782        // Enable Prettier formatting for the same buffer, and ensure
12783        // LSP is called instead of Prettier.
12784        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12785    });
12786    let mut fake_servers = language_registry.register_fake_lsp(
12787        "Rust",
12788        FakeLspAdapter {
12789            capabilities: lsp::ServerCapabilities {
12790                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12791                ..Default::default()
12792            },
12793            ..Default::default()
12794        },
12795    );
12796
12797    let buffer = project
12798        .update(cx, |project, cx| {
12799            project.open_local_buffer(path!("/file.rs"), cx)
12800        })
12801        .await
12802        .unwrap();
12803
12804    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12805    let (editor, cx) = cx.add_window_view(|window, cx| {
12806        build_editor_with_project(project.clone(), buffer, window, cx)
12807    });
12808    editor.update_in(cx, |editor, window, cx| {
12809        editor.set_text("one\ntwo\nthree\n", window, cx)
12810    });
12811
12812    let fake_server = fake_servers.next().await.unwrap();
12813
12814    let format = editor
12815        .update_in(cx, |editor, window, cx| {
12816            editor.perform_format(
12817                project.clone(),
12818                FormatTrigger::Manual,
12819                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12820                window,
12821                cx,
12822            )
12823        })
12824        .unwrap();
12825    fake_server
12826        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12827            assert_eq!(
12828                params.text_document.uri,
12829                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12830            );
12831            assert_eq!(params.options.tab_size, 4);
12832            Ok(Some(vec![lsp::TextEdit::new(
12833                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12834                ", ".to_string(),
12835            )]))
12836        })
12837        .next()
12838        .await;
12839    format.await;
12840    assert_eq!(
12841        editor.update(cx, |editor, cx| editor.text(cx)),
12842        "one, two\nthree\n"
12843    );
12844
12845    editor.update_in(cx, |editor, window, cx| {
12846        editor.set_text("one\ntwo\nthree\n", window, cx)
12847    });
12848    // Ensure we don't lock if formatting hangs.
12849    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12850        move |params, _| async move {
12851            assert_eq!(
12852                params.text_document.uri,
12853                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12854            );
12855            futures::future::pending::<()>().await;
12856            unreachable!()
12857        },
12858    );
12859    let format = editor
12860        .update_in(cx, |editor, window, cx| {
12861            editor.perform_format(
12862                project,
12863                FormatTrigger::Manual,
12864                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12865                window,
12866                cx,
12867            )
12868        })
12869        .unwrap();
12870    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12871    format.await;
12872    assert_eq!(
12873        editor.update(cx, |editor, cx| editor.text(cx)),
12874        "one\ntwo\nthree\n"
12875    );
12876}
12877
12878#[gpui::test]
12879async fn test_multiple_formatters(cx: &mut TestAppContext) {
12880    init_test(cx, |settings| {
12881        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12882        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12883            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12884            Formatter::CodeAction("code-action-1".into()),
12885            Formatter::CodeAction("code-action-2".into()),
12886        ]))
12887    });
12888
12889    let fs = FakeFs::new(cx.executor());
12890    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12891        .await;
12892
12893    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12894    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12895    language_registry.add(rust_lang());
12896
12897    let mut fake_servers = language_registry.register_fake_lsp(
12898        "Rust",
12899        FakeLspAdapter {
12900            capabilities: lsp::ServerCapabilities {
12901                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12902                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12903                    commands: vec!["the-command-for-code-action-1".into()],
12904                    ..Default::default()
12905                }),
12906                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12907                ..Default::default()
12908            },
12909            ..Default::default()
12910        },
12911    );
12912
12913    let buffer = project
12914        .update(cx, |project, cx| {
12915            project.open_local_buffer(path!("/file.rs"), cx)
12916        })
12917        .await
12918        .unwrap();
12919
12920    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12921    let (editor, cx) = cx.add_window_view(|window, cx| {
12922        build_editor_with_project(project.clone(), buffer, window, cx)
12923    });
12924
12925    let fake_server = fake_servers.next().await.unwrap();
12926    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12927        move |_params, _| async move {
12928            Ok(Some(vec![lsp::TextEdit::new(
12929                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12930                "applied-formatting\n".to_string(),
12931            )]))
12932        },
12933    );
12934    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12935        move |params, _| async move {
12936            let requested_code_actions = params.context.only.expect("Expected code action request");
12937            assert_eq!(requested_code_actions.len(), 1);
12938
12939            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12940            let code_action = match requested_code_actions[0].as_str() {
12941                "code-action-1" => lsp::CodeAction {
12942                    kind: Some("code-action-1".into()),
12943                    edit: Some(lsp::WorkspaceEdit::new(
12944                        [(
12945                            uri,
12946                            vec![lsp::TextEdit::new(
12947                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12948                                "applied-code-action-1-edit\n".to_string(),
12949                            )],
12950                        )]
12951                        .into_iter()
12952                        .collect(),
12953                    )),
12954                    command: Some(lsp::Command {
12955                        command: "the-command-for-code-action-1".into(),
12956                        ..Default::default()
12957                    }),
12958                    ..Default::default()
12959                },
12960                "code-action-2" => lsp::CodeAction {
12961                    kind: Some("code-action-2".into()),
12962                    edit: Some(lsp::WorkspaceEdit::new(
12963                        [(
12964                            uri,
12965                            vec![lsp::TextEdit::new(
12966                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12967                                "applied-code-action-2-edit\n".to_string(),
12968                            )],
12969                        )]
12970                        .into_iter()
12971                        .collect(),
12972                    )),
12973                    ..Default::default()
12974                },
12975                req => panic!("Unexpected code action request: {:?}", req),
12976            };
12977            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12978                code_action,
12979            )]))
12980        },
12981    );
12982
12983    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12984        move |params, _| async move { Ok(params) }
12985    });
12986
12987    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12988    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12989        let fake = fake_server.clone();
12990        let lock = command_lock.clone();
12991        move |params, _| {
12992            assert_eq!(params.command, "the-command-for-code-action-1");
12993            let fake = fake.clone();
12994            let lock = lock.clone();
12995            async move {
12996                lock.lock().await;
12997                fake.server
12998                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12999                        label: None,
13000                        edit: lsp::WorkspaceEdit {
13001                            changes: Some(
13002                                [(
13003                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
13004                                    vec![lsp::TextEdit {
13005                                        range: lsp::Range::new(
13006                                            lsp::Position::new(0, 0),
13007                                            lsp::Position::new(0, 0),
13008                                        ),
13009                                        new_text: "applied-code-action-1-command\n".into(),
13010                                    }],
13011                                )]
13012                                .into_iter()
13013                                .collect(),
13014                            ),
13015                            ..Default::default()
13016                        },
13017                    })
13018                    .await
13019                    .into_response()
13020                    .unwrap();
13021                Ok(Some(json!(null)))
13022            }
13023        }
13024    });
13025
13026    editor
13027        .update_in(cx, |editor, window, cx| {
13028            editor.perform_format(
13029                project.clone(),
13030                FormatTrigger::Manual,
13031                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13032                window,
13033                cx,
13034            )
13035        })
13036        .unwrap()
13037        .await;
13038    editor.update(cx, |editor, cx| {
13039        assert_eq!(
13040            editor.text(cx),
13041            r#"
13042                applied-code-action-2-edit
13043                applied-code-action-1-command
13044                applied-code-action-1-edit
13045                applied-formatting
13046                one
13047                two
13048                three
13049            "#
13050            .unindent()
13051        );
13052    });
13053
13054    editor.update_in(cx, |editor, window, cx| {
13055        editor.undo(&Default::default(), window, cx);
13056        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13057    });
13058
13059    // Perform a manual edit while waiting for an LSP command
13060    // that's being run as part of a formatting code action.
13061    let lock_guard = command_lock.lock().await;
13062    let format = editor
13063        .update_in(cx, |editor, window, cx| {
13064            editor.perform_format(
13065                project.clone(),
13066                FormatTrigger::Manual,
13067                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13068                window,
13069                cx,
13070            )
13071        })
13072        .unwrap();
13073    cx.run_until_parked();
13074    editor.update(cx, |editor, cx| {
13075        assert_eq!(
13076            editor.text(cx),
13077            r#"
13078                applied-code-action-1-edit
13079                applied-formatting
13080                one
13081                two
13082                three
13083            "#
13084            .unindent()
13085        );
13086
13087        editor.buffer.update(cx, |buffer, cx| {
13088            let ix = buffer.len(cx);
13089            buffer.edit([(ix..ix, "edited\n")], None, cx);
13090        });
13091    });
13092
13093    // Allow the LSP command to proceed. Because the buffer was edited,
13094    // the second code action will not be run.
13095    drop(lock_guard);
13096    format.await;
13097    editor.update_in(cx, |editor, window, cx| {
13098        assert_eq!(
13099            editor.text(cx),
13100            r#"
13101                applied-code-action-1-command
13102                applied-code-action-1-edit
13103                applied-formatting
13104                one
13105                two
13106                three
13107                edited
13108            "#
13109            .unindent()
13110        );
13111
13112        // The manual edit is undone first, because it is the last thing the user did
13113        // (even though the command completed afterwards).
13114        editor.undo(&Default::default(), window, cx);
13115        assert_eq!(
13116            editor.text(cx),
13117            r#"
13118                applied-code-action-1-command
13119                applied-code-action-1-edit
13120                applied-formatting
13121                one
13122                two
13123                three
13124            "#
13125            .unindent()
13126        );
13127
13128        // All the formatting (including the command, which completed after the manual edit)
13129        // is undone together.
13130        editor.undo(&Default::default(), window, cx);
13131        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
13132    });
13133}
13134
13135#[gpui::test]
13136async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
13137    init_test(cx, |settings| {
13138        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
13139            settings::LanguageServerFormatterSpecifier::Current,
13140        )]))
13141    });
13142
13143    let fs = FakeFs::new(cx.executor());
13144    fs.insert_file(path!("/file.ts"), Default::default()).await;
13145
13146    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13147
13148    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13149    language_registry.add(Arc::new(Language::new(
13150        LanguageConfig {
13151            name: "TypeScript".into(),
13152            matcher: LanguageMatcher {
13153                path_suffixes: vec!["ts".to_string()],
13154                ..Default::default()
13155            },
13156            ..LanguageConfig::default()
13157        },
13158        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13159    )));
13160    update_test_language_settings(cx, |settings| {
13161        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13162    });
13163    let mut fake_servers = language_registry.register_fake_lsp(
13164        "TypeScript",
13165        FakeLspAdapter {
13166            capabilities: lsp::ServerCapabilities {
13167                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13168                ..Default::default()
13169            },
13170            ..Default::default()
13171        },
13172    );
13173
13174    let buffer = project
13175        .update(cx, |project, cx| {
13176            project.open_local_buffer(path!("/file.ts"), cx)
13177        })
13178        .await
13179        .unwrap();
13180
13181    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13182    let (editor, cx) = cx.add_window_view(|window, cx| {
13183        build_editor_with_project(project.clone(), buffer, window, cx)
13184    });
13185    editor.update_in(cx, |editor, window, cx| {
13186        editor.set_text(
13187            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13188            window,
13189            cx,
13190        )
13191    });
13192
13193    let fake_server = fake_servers.next().await.unwrap();
13194
13195    let format = editor
13196        .update_in(cx, |editor, window, cx| {
13197            editor.perform_code_action_kind(
13198                project.clone(),
13199                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13200                window,
13201                cx,
13202            )
13203        })
13204        .unwrap();
13205    fake_server
13206        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13207            assert_eq!(
13208                params.text_document.uri,
13209                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13210            );
13211            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13212                lsp::CodeAction {
13213                    title: "Organize Imports".to_string(),
13214                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13215                    edit: Some(lsp::WorkspaceEdit {
13216                        changes: Some(
13217                            [(
13218                                params.text_document.uri.clone(),
13219                                vec![lsp::TextEdit::new(
13220                                    lsp::Range::new(
13221                                        lsp::Position::new(1, 0),
13222                                        lsp::Position::new(2, 0),
13223                                    ),
13224                                    "".to_string(),
13225                                )],
13226                            )]
13227                            .into_iter()
13228                            .collect(),
13229                        ),
13230                        ..Default::default()
13231                    }),
13232                    ..Default::default()
13233                },
13234            )]))
13235        })
13236        .next()
13237        .await;
13238    format.await;
13239    assert_eq!(
13240        editor.update(cx, |editor, cx| editor.text(cx)),
13241        "import { a } from 'module';\n\nconst x = a;\n"
13242    );
13243
13244    editor.update_in(cx, |editor, window, cx| {
13245        editor.set_text(
13246            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13247            window,
13248            cx,
13249        )
13250    });
13251    // Ensure we don't lock if code action hangs.
13252    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13253        move |params, _| async move {
13254            assert_eq!(
13255                params.text_document.uri,
13256                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13257            );
13258            futures::future::pending::<()>().await;
13259            unreachable!()
13260        },
13261    );
13262    let format = editor
13263        .update_in(cx, |editor, window, cx| {
13264            editor.perform_code_action_kind(
13265                project,
13266                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13267                window,
13268                cx,
13269            )
13270        })
13271        .unwrap();
13272    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13273    format.await;
13274    assert_eq!(
13275        editor.update(cx, |editor, cx| editor.text(cx)),
13276        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13277    );
13278}
13279
13280#[gpui::test]
13281async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13282    init_test(cx, |_| {});
13283
13284    let mut cx = EditorLspTestContext::new_rust(
13285        lsp::ServerCapabilities {
13286            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13287            ..Default::default()
13288        },
13289        cx,
13290    )
13291    .await;
13292
13293    cx.set_state(indoc! {"
13294        one.twoˇ
13295    "});
13296
13297    // The format request takes a long time. When it completes, it inserts
13298    // a newline and an indent before the `.`
13299    cx.lsp
13300        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13301            let executor = cx.background_executor().clone();
13302            async move {
13303                executor.timer(Duration::from_millis(100)).await;
13304                Ok(Some(vec![lsp::TextEdit {
13305                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13306                    new_text: "\n    ".into(),
13307                }]))
13308            }
13309        });
13310
13311    // Submit a format request.
13312    let format_1 = cx
13313        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13314        .unwrap();
13315    cx.executor().run_until_parked();
13316
13317    // Submit a second format request.
13318    let format_2 = cx
13319        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13320        .unwrap();
13321    cx.executor().run_until_parked();
13322
13323    // Wait for both format requests to complete
13324    cx.executor().advance_clock(Duration::from_millis(200));
13325    format_1.await.unwrap();
13326    format_2.await.unwrap();
13327
13328    // The formatting edits only happens once.
13329    cx.assert_editor_state(indoc! {"
13330        one
13331            .twoˇ
13332    "});
13333}
13334
13335#[gpui::test]
13336async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13337    init_test(cx, |settings| {
13338        settings.defaults.formatter = Some(FormatterList::default())
13339    });
13340
13341    let mut cx = EditorLspTestContext::new_rust(
13342        lsp::ServerCapabilities {
13343            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13344            ..Default::default()
13345        },
13346        cx,
13347    )
13348    .await;
13349
13350    // Record which buffer changes have been sent to the language server
13351    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13352    cx.lsp
13353        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13354            let buffer_changes = buffer_changes.clone();
13355            move |params, _| {
13356                buffer_changes.lock().extend(
13357                    params
13358                        .content_changes
13359                        .into_iter()
13360                        .map(|e| (e.range.unwrap(), e.text)),
13361                );
13362            }
13363        });
13364    // Handle formatting requests to the language server.
13365    cx.lsp
13366        .set_request_handler::<lsp::request::Formatting, _, _>({
13367            move |_, _| {
13368                // Insert blank lines between each line of the buffer.
13369                async move {
13370                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
13371                    // DidChangedTextDocument to the LSP before sending the formatting request.
13372                    // assert_eq!(
13373                    //     &buffer_changes.lock()[1..],
13374                    //     &[
13375                    //         (
13376                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13377                    //             "".into()
13378                    //         ),
13379                    //         (
13380                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13381                    //             "".into()
13382                    //         ),
13383                    //         (
13384                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13385                    //             "\n".into()
13386                    //         ),
13387                    //     ]
13388                    // );
13389
13390                    Ok(Some(vec![
13391                        lsp::TextEdit {
13392                            range: lsp::Range::new(
13393                                lsp::Position::new(1, 0),
13394                                lsp::Position::new(1, 0),
13395                            ),
13396                            new_text: "\n".into(),
13397                        },
13398                        lsp::TextEdit {
13399                            range: lsp::Range::new(
13400                                lsp::Position::new(2, 0),
13401                                lsp::Position::new(2, 0),
13402                            ),
13403                            new_text: "\n".into(),
13404                        },
13405                    ]))
13406                }
13407            }
13408        });
13409
13410    // Set up a buffer white some trailing whitespace and no trailing newline.
13411    cx.set_state(
13412        &[
13413            "one ",   //
13414            "twoˇ",   //
13415            "three ", //
13416            "four",   //
13417        ]
13418        .join("\n"),
13419    );
13420
13421    // Submit a format request.
13422    let format = cx
13423        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13424        .unwrap();
13425
13426    cx.run_until_parked();
13427    // After formatting the buffer, the trailing whitespace is stripped,
13428    // a newline is appended, and the edits provided by the language server
13429    // have been applied.
13430    format.await.unwrap();
13431
13432    cx.assert_editor_state(
13433        &[
13434            "one",   //
13435            "",      //
13436            "twoˇ",  //
13437            "",      //
13438            "three", //
13439            "four",  //
13440            "",      //
13441        ]
13442        .join("\n"),
13443    );
13444
13445    // Undoing the formatting undoes the trailing whitespace removal, the
13446    // trailing newline, and the LSP edits.
13447    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13448    cx.assert_editor_state(
13449        &[
13450            "one ",   //
13451            "twoˇ",   //
13452            "three ", //
13453            "four",   //
13454        ]
13455        .join("\n"),
13456    );
13457}
13458
13459#[gpui::test]
13460async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13461    cx: &mut TestAppContext,
13462) {
13463    init_test(cx, |_| {});
13464
13465    cx.update(|cx| {
13466        cx.update_global::<SettingsStore, _>(|settings, cx| {
13467            settings.update_user_settings(cx, |settings| {
13468                settings.editor.auto_signature_help = Some(true);
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    cx.set_state(
13547        &r#"
13548            fn main() {
13549                sampleˇ
13550            }
13551        "#
13552        .unindent(),
13553    );
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
13567    let mocked_response = lsp::SignatureHelp {
13568        signatures: vec![lsp::SignatureInformation {
13569            label: "fn sample(param1: u8, param2: u8)".to_string(),
13570            documentation: None,
13571            parameters: Some(vec![
13572                lsp::ParameterInformation {
13573                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13574                    documentation: None,
13575                },
13576                lsp::ParameterInformation {
13577                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13578                    documentation: None,
13579                },
13580            ]),
13581            active_parameter: None,
13582        }],
13583        active_signature: Some(0),
13584        active_parameter: Some(0),
13585    };
13586    handle_signature_help_request(&mut cx, mocked_response).await;
13587
13588    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13589        .await;
13590
13591    cx.editor(|editor, _, _| {
13592        let signature_help_state = editor.signature_help_state.popover().cloned();
13593        let signature = signature_help_state.unwrap();
13594        assert_eq!(
13595            signature.signatures[signature.current_signature].label,
13596            "fn sample(param1: u8, param2: u8)"
13597        );
13598    });
13599}
13600
13601#[gpui::test]
13602async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604
13605    cx.update(|cx| {
13606        cx.update_global::<SettingsStore, _>(|settings, cx| {
13607            settings.update_user_settings(cx, |settings| {
13608                settings.editor.auto_signature_help = Some(false);
13609                settings.editor.show_signature_help_after_edits = Some(false);
13610            });
13611        });
13612    });
13613
13614    let mut cx = EditorLspTestContext::new_rust(
13615        lsp::ServerCapabilities {
13616            signature_help_provider: Some(lsp::SignatureHelpOptions {
13617                ..Default::default()
13618            }),
13619            ..Default::default()
13620        },
13621        cx,
13622    )
13623    .await;
13624
13625    let language = Language::new(
13626        LanguageConfig {
13627            name: "Rust".into(),
13628            brackets: BracketPairConfig {
13629                pairs: vec![
13630                    BracketPair {
13631                        start: "{".to_string(),
13632                        end: "}".to_string(),
13633                        close: true,
13634                        surround: true,
13635                        newline: true,
13636                    },
13637                    BracketPair {
13638                        start: "(".to_string(),
13639                        end: ")".to_string(),
13640                        close: true,
13641                        surround: true,
13642                        newline: true,
13643                    },
13644                    BracketPair {
13645                        start: "/*".to_string(),
13646                        end: " */".to_string(),
13647                        close: true,
13648                        surround: true,
13649                        newline: true,
13650                    },
13651                    BracketPair {
13652                        start: "[".to_string(),
13653                        end: "]".to_string(),
13654                        close: false,
13655                        surround: false,
13656                        newline: true,
13657                    },
13658                    BracketPair {
13659                        start: "\"".to_string(),
13660                        end: "\"".to_string(),
13661                        close: true,
13662                        surround: true,
13663                        newline: false,
13664                    },
13665                    BracketPair {
13666                        start: "<".to_string(),
13667                        end: ">".to_string(),
13668                        close: false,
13669                        surround: true,
13670                        newline: true,
13671                    },
13672                ],
13673                ..Default::default()
13674            },
13675            autoclose_before: "})]".to_string(),
13676            ..Default::default()
13677        },
13678        Some(tree_sitter_rust::LANGUAGE.into()),
13679    );
13680    let language = Arc::new(language);
13681
13682    cx.language_registry().add(language.clone());
13683    cx.update_buffer(|buffer, cx| {
13684        buffer.set_language(Some(language), cx);
13685    });
13686
13687    // Ensure that signature_help is not called when no signature help is enabled.
13688    cx.set_state(
13689        &r#"
13690            fn main() {
13691                sampleˇ
13692            }
13693        "#
13694        .unindent(),
13695    );
13696    cx.update_editor(|editor, window, cx| {
13697        editor.handle_input("(", window, cx);
13698    });
13699    cx.assert_editor_state(
13700        &"
13701            fn main() {
13702                sample(ˇ)
13703            }
13704        "
13705        .unindent(),
13706    );
13707    cx.editor(|editor, _, _| {
13708        assert!(editor.signature_help_state.task().is_none());
13709    });
13710
13711    let mocked_response = lsp::SignatureHelp {
13712        signatures: vec![lsp::SignatureInformation {
13713            label: "fn sample(param1: u8, param2: u8)".to_string(),
13714            documentation: None,
13715            parameters: Some(vec![
13716                lsp::ParameterInformation {
13717                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13718                    documentation: None,
13719                },
13720                lsp::ParameterInformation {
13721                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13722                    documentation: None,
13723                },
13724            ]),
13725            active_parameter: None,
13726        }],
13727        active_signature: Some(0),
13728        active_parameter: Some(0),
13729    };
13730
13731    // Ensure that signature_help is called when enabled afte edits
13732    cx.update(|_, cx| {
13733        cx.update_global::<SettingsStore, _>(|settings, cx| {
13734            settings.update_user_settings(cx, |settings| {
13735                settings.editor.auto_signature_help = Some(false);
13736                settings.editor.show_signature_help_after_edits = Some(true);
13737            });
13738        });
13739    });
13740    cx.set_state(
13741        &r#"
13742            fn main() {
13743                sampleˇ
13744            }
13745        "#
13746        .unindent(),
13747    );
13748    cx.update_editor(|editor, window, cx| {
13749        editor.handle_input("(", window, cx);
13750    });
13751    cx.assert_editor_state(
13752        &"
13753            fn main() {
13754                sample(ˇ)
13755            }
13756        "
13757        .unindent(),
13758    );
13759    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13760    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13761        .await;
13762    cx.update_editor(|editor, _, _| {
13763        let signature_help_state = editor.signature_help_state.popover().cloned();
13764        assert!(signature_help_state.is_some());
13765        let signature = signature_help_state.unwrap();
13766        assert_eq!(
13767            signature.signatures[signature.current_signature].label,
13768            "fn sample(param1: u8, param2: u8)"
13769        );
13770        editor.signature_help_state = SignatureHelpState::default();
13771    });
13772
13773    // Ensure that signature_help is called when auto signature help override is enabled
13774    cx.update(|_, cx| {
13775        cx.update_global::<SettingsStore, _>(|settings, cx| {
13776            settings.update_user_settings(cx, |settings| {
13777                settings.editor.auto_signature_help = Some(true);
13778                settings.editor.show_signature_help_after_edits = Some(false);
13779            });
13780        });
13781    });
13782    cx.set_state(
13783        &r#"
13784            fn main() {
13785                sampleˇ
13786            }
13787        "#
13788        .unindent(),
13789    );
13790    cx.update_editor(|editor, window, cx| {
13791        editor.handle_input("(", window, cx);
13792    });
13793    cx.assert_editor_state(
13794        &"
13795            fn main() {
13796                sample(ˇ)
13797            }
13798        "
13799        .unindent(),
13800    );
13801    handle_signature_help_request(&mut cx, mocked_response).await;
13802    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13803        .await;
13804    cx.editor(|editor, _, _| {
13805        let signature_help_state = editor.signature_help_state.popover().cloned();
13806        assert!(signature_help_state.is_some());
13807        let signature = signature_help_state.unwrap();
13808        assert_eq!(
13809            signature.signatures[signature.current_signature].label,
13810            "fn sample(param1: u8, param2: u8)"
13811        );
13812    });
13813}
13814
13815#[gpui::test]
13816async fn test_signature_help(cx: &mut TestAppContext) {
13817    init_test(cx, |_| {});
13818    cx.update(|cx| {
13819        cx.update_global::<SettingsStore, _>(|settings, cx| {
13820            settings.update_user_settings(cx, |settings| {
13821                settings.editor.auto_signature_help = Some(true);
13822            });
13823        });
13824    });
13825
13826    let mut cx = EditorLspTestContext::new_rust(
13827        lsp::ServerCapabilities {
13828            signature_help_provider: Some(lsp::SignatureHelpOptions {
13829                ..Default::default()
13830            }),
13831            ..Default::default()
13832        },
13833        cx,
13834    )
13835    .await;
13836
13837    // A test that directly calls `show_signature_help`
13838    cx.update_editor(|editor, window, cx| {
13839        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13840    });
13841
13842    let mocked_response = lsp::SignatureHelp {
13843        signatures: vec![lsp::SignatureInformation {
13844            label: "fn sample(param1: u8, param2: u8)".to_string(),
13845            documentation: None,
13846            parameters: Some(vec![
13847                lsp::ParameterInformation {
13848                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13849                    documentation: None,
13850                },
13851                lsp::ParameterInformation {
13852                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13853                    documentation: None,
13854                },
13855            ]),
13856            active_parameter: None,
13857        }],
13858        active_signature: Some(0),
13859        active_parameter: Some(0),
13860    };
13861    handle_signature_help_request(&mut cx, mocked_response).await;
13862
13863    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13864        .await;
13865
13866    cx.editor(|editor, _, _| {
13867        let signature_help_state = editor.signature_help_state.popover().cloned();
13868        assert!(signature_help_state.is_some());
13869        let signature = signature_help_state.unwrap();
13870        assert_eq!(
13871            signature.signatures[signature.current_signature].label,
13872            "fn sample(param1: u8, param2: u8)"
13873        );
13874    });
13875
13876    // When exiting outside from inside the brackets, `signature_help` is closed.
13877    cx.set_state(indoc! {"
13878        fn main() {
13879            sample(ˇ);
13880        }
13881
13882        fn sample(param1: u8, param2: u8) {}
13883    "});
13884
13885    cx.update_editor(|editor, window, cx| {
13886        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13887            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13888        });
13889    });
13890
13891    let mocked_response = lsp::SignatureHelp {
13892        signatures: Vec::new(),
13893        active_signature: None,
13894        active_parameter: None,
13895    };
13896    handle_signature_help_request(&mut cx, mocked_response).await;
13897
13898    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13899        .await;
13900
13901    cx.editor(|editor, _, _| {
13902        assert!(!editor.signature_help_state.is_shown());
13903    });
13904
13905    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13906    cx.set_state(indoc! {"
13907        fn main() {
13908            sample(ˇ);
13909        }
13910
13911        fn sample(param1: u8, param2: u8) {}
13912    "});
13913
13914    let mocked_response = lsp::SignatureHelp {
13915        signatures: vec![lsp::SignatureInformation {
13916            label: "fn sample(param1: u8, param2: u8)".to_string(),
13917            documentation: None,
13918            parameters: Some(vec![
13919                lsp::ParameterInformation {
13920                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13921                    documentation: None,
13922                },
13923                lsp::ParameterInformation {
13924                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13925                    documentation: None,
13926                },
13927            ]),
13928            active_parameter: None,
13929        }],
13930        active_signature: Some(0),
13931        active_parameter: Some(0),
13932    };
13933    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13934    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13935        .await;
13936    cx.editor(|editor, _, _| {
13937        assert!(editor.signature_help_state.is_shown());
13938    });
13939
13940    // Restore the popover with more parameter input
13941    cx.set_state(indoc! {"
13942        fn main() {
13943            sample(param1, param2ˇ);
13944        }
13945
13946        fn sample(param1: u8, param2: u8) {}
13947    "});
13948
13949    let mocked_response = lsp::SignatureHelp {
13950        signatures: vec![lsp::SignatureInformation {
13951            label: "fn sample(param1: u8, param2: u8)".to_string(),
13952            documentation: None,
13953            parameters: Some(vec![
13954                lsp::ParameterInformation {
13955                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13956                    documentation: None,
13957                },
13958                lsp::ParameterInformation {
13959                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13960                    documentation: None,
13961                },
13962            ]),
13963            active_parameter: None,
13964        }],
13965        active_signature: Some(0),
13966        active_parameter: Some(1),
13967    };
13968    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13969    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13970        .await;
13971
13972    // When selecting a range, the popover is gone.
13973    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13974    cx.update_editor(|editor, window, cx| {
13975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13976            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13977        })
13978    });
13979    cx.assert_editor_state(indoc! {"
13980        fn main() {
13981            sample(param1, «ˇparam2»);
13982        }
13983
13984        fn sample(param1: u8, param2: u8) {}
13985    "});
13986    cx.editor(|editor, _, _| {
13987        assert!(!editor.signature_help_state.is_shown());
13988    });
13989
13990    // When unselecting again, the popover is back if within the brackets.
13991    cx.update_editor(|editor, window, cx| {
13992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13993            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13994        })
13995    });
13996    cx.assert_editor_state(indoc! {"
13997        fn main() {
13998            sample(param1, ˇparam2);
13999        }
14000
14001        fn sample(param1: u8, param2: u8) {}
14002    "});
14003    handle_signature_help_request(&mut cx, mocked_response).await;
14004    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14005        .await;
14006    cx.editor(|editor, _, _| {
14007        assert!(editor.signature_help_state.is_shown());
14008    });
14009
14010    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14011    cx.update_editor(|editor, window, cx| {
14012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14013            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14014            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14015        })
14016    });
14017    cx.assert_editor_state(indoc! {"
14018        fn main() {
14019            sample(param1, ˇparam2);
14020        }
14021
14022        fn sample(param1: u8, param2: u8) {}
14023    "});
14024
14025    let mocked_response = lsp::SignatureHelp {
14026        signatures: vec![lsp::SignatureInformation {
14027            label: "fn sample(param1: u8, param2: u8)".to_string(),
14028            documentation: None,
14029            parameters: Some(vec![
14030                lsp::ParameterInformation {
14031                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14032                    documentation: None,
14033                },
14034                lsp::ParameterInformation {
14035                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14036                    documentation: None,
14037                },
14038            ]),
14039            active_parameter: None,
14040        }],
14041        active_signature: Some(0),
14042        active_parameter: Some(1),
14043    };
14044    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14045    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14046        .await;
14047    cx.update_editor(|editor, _, cx| {
14048        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14049    });
14050    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14051        .await;
14052    cx.update_editor(|editor, window, cx| {
14053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14054            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14055        })
14056    });
14057    cx.assert_editor_state(indoc! {"
14058        fn main() {
14059            sample(param1, «ˇparam2»);
14060        }
14061
14062        fn sample(param1: u8, param2: u8) {}
14063    "});
14064    cx.update_editor(|editor, window, cx| {
14065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14066            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14067        })
14068    });
14069    cx.assert_editor_state(indoc! {"
14070        fn main() {
14071            sample(param1, ˇparam2);
14072        }
14073
14074        fn sample(param1: u8, param2: u8) {}
14075    "});
14076    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14077        .await;
14078}
14079
14080#[gpui::test]
14081async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14082    init_test(cx, |_| {});
14083
14084    let mut cx = EditorLspTestContext::new_rust(
14085        lsp::ServerCapabilities {
14086            signature_help_provider: Some(lsp::SignatureHelpOptions {
14087                ..Default::default()
14088            }),
14089            ..Default::default()
14090        },
14091        cx,
14092    )
14093    .await;
14094
14095    cx.set_state(indoc! {"
14096        fn main() {
14097            overloadedˇ
14098        }
14099    "});
14100
14101    cx.update_editor(|editor, window, cx| {
14102        editor.handle_input("(", window, cx);
14103        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14104    });
14105
14106    // Mock response with 3 signatures
14107    let mocked_response = lsp::SignatureHelp {
14108        signatures: vec![
14109            lsp::SignatureInformation {
14110                label: "fn overloaded(x: i32)".to_string(),
14111                documentation: None,
14112                parameters: Some(vec![lsp::ParameterInformation {
14113                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14114                    documentation: None,
14115                }]),
14116                active_parameter: None,
14117            },
14118            lsp::SignatureInformation {
14119                label: "fn overloaded(x: i32, y: i32)".to_string(),
14120                documentation: None,
14121                parameters: Some(vec![
14122                    lsp::ParameterInformation {
14123                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14124                        documentation: None,
14125                    },
14126                    lsp::ParameterInformation {
14127                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14128                        documentation: None,
14129                    },
14130                ]),
14131                active_parameter: None,
14132            },
14133            lsp::SignatureInformation {
14134                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14135                documentation: None,
14136                parameters: Some(vec![
14137                    lsp::ParameterInformation {
14138                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14139                        documentation: None,
14140                    },
14141                    lsp::ParameterInformation {
14142                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14143                        documentation: None,
14144                    },
14145                    lsp::ParameterInformation {
14146                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14147                        documentation: None,
14148                    },
14149                ]),
14150                active_parameter: None,
14151            },
14152        ],
14153        active_signature: Some(1),
14154        active_parameter: Some(0),
14155    };
14156    handle_signature_help_request(&mut cx, mocked_response).await;
14157
14158    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14159        .await;
14160
14161    // Verify we have multiple signatures and the right one is selected
14162    cx.editor(|editor, _, _| {
14163        let popover = editor.signature_help_state.popover().cloned().unwrap();
14164        assert_eq!(popover.signatures.len(), 3);
14165        // active_signature was 1, so that should be the current
14166        assert_eq!(popover.current_signature, 1);
14167        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14168        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14169        assert_eq!(
14170            popover.signatures[2].label,
14171            "fn overloaded(x: i32, y: i32, z: i32)"
14172        );
14173    });
14174
14175    // Test navigation functionality
14176    cx.update_editor(|editor, window, cx| {
14177        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14178    });
14179
14180    cx.editor(|editor, _, _| {
14181        let popover = editor.signature_help_state.popover().cloned().unwrap();
14182        assert_eq!(popover.current_signature, 2);
14183    });
14184
14185    // Test wrap around
14186    cx.update_editor(|editor, window, cx| {
14187        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14188    });
14189
14190    cx.editor(|editor, _, _| {
14191        let popover = editor.signature_help_state.popover().cloned().unwrap();
14192        assert_eq!(popover.current_signature, 0);
14193    });
14194
14195    // Test previous navigation
14196    cx.update_editor(|editor, window, cx| {
14197        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14198    });
14199
14200    cx.editor(|editor, _, _| {
14201        let popover = editor.signature_help_state.popover().cloned().unwrap();
14202        assert_eq!(popover.current_signature, 2);
14203    });
14204}
14205
14206#[gpui::test]
14207async fn test_completion_mode(cx: &mut TestAppContext) {
14208    init_test(cx, |_| {});
14209    let mut cx = EditorLspTestContext::new_rust(
14210        lsp::ServerCapabilities {
14211            completion_provider: Some(lsp::CompletionOptions {
14212                resolve_provider: Some(true),
14213                ..Default::default()
14214            }),
14215            ..Default::default()
14216        },
14217        cx,
14218    )
14219    .await;
14220
14221    struct Run {
14222        run_description: &'static str,
14223        initial_state: String,
14224        buffer_marked_text: String,
14225        completion_label: &'static str,
14226        completion_text: &'static str,
14227        expected_with_insert_mode: String,
14228        expected_with_replace_mode: String,
14229        expected_with_replace_subsequence_mode: String,
14230        expected_with_replace_suffix_mode: String,
14231    }
14232
14233    let runs = [
14234        Run {
14235            run_description: "Start of word matches completion text",
14236            initial_state: "before ediˇ after".into(),
14237            buffer_marked_text: "before <edi|> after".into(),
14238            completion_label: "editor",
14239            completion_text: "editor",
14240            expected_with_insert_mode: "before editorˇ after".into(),
14241            expected_with_replace_mode: "before editorˇ after".into(),
14242            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14243            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14244        },
14245        Run {
14246            run_description: "Accept same text at the middle of the word",
14247            initial_state: "before ediˇtor after".into(),
14248            buffer_marked_text: "before <edi|tor> after".into(),
14249            completion_label: "editor",
14250            completion_text: "editor",
14251            expected_with_insert_mode: "before editorˇtor after".into(),
14252            expected_with_replace_mode: "before editorˇ after".into(),
14253            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14254            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14255        },
14256        Run {
14257            run_description: "End of word matches completion text -- cursor at end",
14258            initial_state: "before torˇ after".into(),
14259            buffer_marked_text: "before <tor|> after".into(),
14260            completion_label: "editor",
14261            completion_text: "editor",
14262            expected_with_insert_mode: "before editorˇ after".into(),
14263            expected_with_replace_mode: "before editorˇ after".into(),
14264            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14265            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14266        },
14267        Run {
14268            run_description: "End of word matches completion text -- cursor at start",
14269            initial_state: "before ˇtor after".into(),
14270            buffer_marked_text: "before <|tor> after".into(),
14271            completion_label: "editor",
14272            completion_text: "editor",
14273            expected_with_insert_mode: "before editorˇtor after".into(),
14274            expected_with_replace_mode: "before editorˇ after".into(),
14275            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14276            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14277        },
14278        Run {
14279            run_description: "Prepend text containing whitespace",
14280            initial_state: "pˇfield: bool".into(),
14281            buffer_marked_text: "<p|field>: bool".into(),
14282            completion_label: "pub ",
14283            completion_text: "pub ",
14284            expected_with_insert_mode: "pub ˇfield: bool".into(),
14285            expected_with_replace_mode: "pub ˇ: bool".into(),
14286            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14287            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14288        },
14289        Run {
14290            run_description: "Add element to start of list",
14291            initial_state: "[element_ˇelement_2]".into(),
14292            buffer_marked_text: "[<element_|element_2>]".into(),
14293            completion_label: "element_1",
14294            completion_text: "element_1",
14295            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14296            expected_with_replace_mode: "[element_1ˇ]".into(),
14297            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14298            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14299        },
14300        Run {
14301            run_description: "Add element to start of list -- first and second elements are equal",
14302            initial_state: "[elˇelement]".into(),
14303            buffer_marked_text: "[<el|element>]".into(),
14304            completion_label: "element",
14305            completion_text: "element",
14306            expected_with_insert_mode: "[elementˇelement]".into(),
14307            expected_with_replace_mode: "[elementˇ]".into(),
14308            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14309            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14310        },
14311        Run {
14312            run_description: "Ends with matching suffix",
14313            initial_state: "SubˇError".into(),
14314            buffer_marked_text: "<Sub|Error>".into(),
14315            completion_label: "SubscriptionError",
14316            completion_text: "SubscriptionError",
14317            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14318            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14319            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14320            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14321        },
14322        Run {
14323            run_description: "Suffix is a subsequence -- contiguous",
14324            initial_state: "SubˇErr".into(),
14325            buffer_marked_text: "<Sub|Err>".into(),
14326            completion_label: "SubscriptionError",
14327            completion_text: "SubscriptionError",
14328            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14329            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14330            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14331            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14332        },
14333        Run {
14334            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14335            initial_state: "Suˇscrirr".into(),
14336            buffer_marked_text: "<Su|scrirr>".into(),
14337            completion_label: "SubscriptionError",
14338            completion_text: "SubscriptionError",
14339            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14340            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14341            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14342            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14343        },
14344        Run {
14345            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14346            initial_state: "foo(indˇix)".into(),
14347            buffer_marked_text: "foo(<ind|ix>)".into(),
14348            completion_label: "node_index",
14349            completion_text: "node_index",
14350            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14351            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14352            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14353            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14354        },
14355        Run {
14356            run_description: "Replace range ends before cursor - should extend to cursor",
14357            initial_state: "before editˇo after".into(),
14358            buffer_marked_text: "before <{ed}>it|o after".into(),
14359            completion_label: "editor",
14360            completion_text: "editor",
14361            expected_with_insert_mode: "before editorˇo after".into(),
14362            expected_with_replace_mode: "before editorˇo after".into(),
14363            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14364            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14365        },
14366        Run {
14367            run_description: "Uses label for suffix matching",
14368            initial_state: "before ediˇtor after".into(),
14369            buffer_marked_text: "before <edi|tor> after".into(),
14370            completion_label: "editor",
14371            completion_text: "editor()",
14372            expected_with_insert_mode: "before editor()ˇtor after".into(),
14373            expected_with_replace_mode: "before editor()ˇ after".into(),
14374            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14375            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14376        },
14377        Run {
14378            run_description: "Case insensitive subsequence and suffix matching",
14379            initial_state: "before EDiˇtoR after".into(),
14380            buffer_marked_text: "before <EDi|toR> after".into(),
14381            completion_label: "editor",
14382            completion_text: "editor",
14383            expected_with_insert_mode: "before editorˇtoR after".into(),
14384            expected_with_replace_mode: "before editorˇ after".into(),
14385            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14386            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14387        },
14388    ];
14389
14390    for run in runs {
14391        let run_variations = [
14392            (LspInsertMode::Insert, run.expected_with_insert_mode),
14393            (LspInsertMode::Replace, run.expected_with_replace_mode),
14394            (
14395                LspInsertMode::ReplaceSubsequence,
14396                run.expected_with_replace_subsequence_mode,
14397            ),
14398            (
14399                LspInsertMode::ReplaceSuffix,
14400                run.expected_with_replace_suffix_mode,
14401            ),
14402        ];
14403
14404        for (lsp_insert_mode, expected_text) in run_variations {
14405            eprintln!(
14406                "run = {:?}, mode = {lsp_insert_mode:.?}",
14407                run.run_description,
14408            );
14409
14410            update_test_language_settings(&mut cx, |settings| {
14411                settings.defaults.completions = Some(CompletionSettingsContent {
14412                    lsp_insert_mode: Some(lsp_insert_mode),
14413                    words: Some(WordsCompletionMode::Disabled),
14414                    words_min_length: Some(0),
14415                    ..Default::default()
14416                });
14417            });
14418
14419            cx.set_state(&run.initial_state);
14420
14421            // Set up resolve handler before showing completions, since resolve may be
14422            // triggered when menu becomes visible (for documentation), not just on confirm.
14423            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14424                move |_, _, _| async move {
14425                    Ok(lsp::CompletionItem {
14426                        additional_text_edits: None,
14427                        ..Default::default()
14428                    })
14429                },
14430            );
14431
14432            cx.update_editor(|editor, window, cx| {
14433                editor.show_completions(&ShowCompletions, window, cx);
14434            });
14435
14436            let counter = Arc::new(AtomicUsize::new(0));
14437            handle_completion_request_with_insert_and_replace(
14438                &mut cx,
14439                &run.buffer_marked_text,
14440                vec![(run.completion_label, run.completion_text)],
14441                counter.clone(),
14442            )
14443            .await;
14444            cx.condition(|editor, _| editor.context_menu_visible())
14445                .await;
14446            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14447
14448            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14449                editor
14450                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14451                    .unwrap()
14452            });
14453            cx.assert_editor_state(&expected_text);
14454            apply_additional_edits.await.unwrap();
14455        }
14456    }
14457}
14458
14459#[gpui::test]
14460async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14461    init_test(cx, |_| {});
14462    let mut cx = EditorLspTestContext::new_rust(
14463        lsp::ServerCapabilities {
14464            completion_provider: Some(lsp::CompletionOptions {
14465                resolve_provider: Some(true),
14466                ..Default::default()
14467            }),
14468            ..Default::default()
14469        },
14470        cx,
14471    )
14472    .await;
14473
14474    let initial_state = "SubˇError";
14475    let buffer_marked_text = "<Sub|Error>";
14476    let completion_text = "SubscriptionError";
14477    let expected_with_insert_mode = "SubscriptionErrorˇError";
14478    let expected_with_replace_mode = "SubscriptionErrorˇ";
14479
14480    update_test_language_settings(&mut cx, |settings| {
14481        settings.defaults.completions = Some(CompletionSettingsContent {
14482            words: Some(WordsCompletionMode::Disabled),
14483            words_min_length: Some(0),
14484            // set the opposite here to ensure that the action is overriding the default behavior
14485            lsp_insert_mode: Some(LspInsertMode::Insert),
14486            ..Default::default()
14487        });
14488    });
14489
14490    cx.set_state(initial_state);
14491    cx.update_editor(|editor, window, cx| {
14492        editor.show_completions(&ShowCompletions, window, cx);
14493    });
14494
14495    let counter = Arc::new(AtomicUsize::new(0));
14496    handle_completion_request_with_insert_and_replace(
14497        &mut cx,
14498        buffer_marked_text,
14499        vec![(completion_text, completion_text)],
14500        counter.clone(),
14501    )
14502    .await;
14503    cx.condition(|editor, _| editor.context_menu_visible())
14504        .await;
14505    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14506
14507    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14508        editor
14509            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14510            .unwrap()
14511    });
14512    cx.assert_editor_state(expected_with_replace_mode);
14513    handle_resolve_completion_request(&mut cx, None).await;
14514    apply_additional_edits.await.unwrap();
14515
14516    update_test_language_settings(&mut cx, |settings| {
14517        settings.defaults.completions = Some(CompletionSettingsContent {
14518            words: Some(WordsCompletionMode::Disabled),
14519            words_min_length: Some(0),
14520            // set the opposite here to ensure that the action is overriding the default behavior
14521            lsp_insert_mode: Some(LspInsertMode::Replace),
14522            ..Default::default()
14523        });
14524    });
14525
14526    cx.set_state(initial_state);
14527    cx.update_editor(|editor, window, cx| {
14528        editor.show_completions(&ShowCompletions, window, cx);
14529    });
14530    handle_completion_request_with_insert_and_replace(
14531        &mut cx,
14532        buffer_marked_text,
14533        vec![(completion_text, completion_text)],
14534        counter.clone(),
14535    )
14536    .await;
14537    cx.condition(|editor, _| editor.context_menu_visible())
14538        .await;
14539    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14540
14541    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14542        editor
14543            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14544            .unwrap()
14545    });
14546    cx.assert_editor_state(expected_with_insert_mode);
14547    handle_resolve_completion_request(&mut cx, None).await;
14548    apply_additional_edits.await.unwrap();
14549}
14550
14551#[gpui::test]
14552async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14553    init_test(cx, |_| {});
14554    let mut cx = EditorLspTestContext::new_rust(
14555        lsp::ServerCapabilities {
14556            completion_provider: Some(lsp::CompletionOptions {
14557                resolve_provider: Some(true),
14558                ..Default::default()
14559            }),
14560            ..Default::default()
14561        },
14562        cx,
14563    )
14564    .await;
14565
14566    // scenario: surrounding text matches completion text
14567    let completion_text = "to_offset";
14568    let initial_state = indoc! {"
14569        1. buf.to_offˇsuffix
14570        2. buf.to_offˇsuf
14571        3. buf.to_offˇfix
14572        4. buf.to_offˇ
14573        5. into_offˇensive
14574        6. ˇsuffix
14575        7. let ˇ //
14576        8. aaˇzz
14577        9. buf.to_off«zzzzzˇ»suffix
14578        10. buf.«ˇzzzzz»suffix
14579        11. to_off«ˇzzzzz»
14580
14581        buf.to_offˇsuffix  // newest cursor
14582    "};
14583    let completion_marked_buffer = indoc! {"
14584        1. buf.to_offsuffix
14585        2. buf.to_offsuf
14586        3. buf.to_offfix
14587        4. buf.to_off
14588        5. into_offensive
14589        6. suffix
14590        7. let  //
14591        8. aazz
14592        9. buf.to_offzzzzzsuffix
14593        10. buf.zzzzzsuffix
14594        11. to_offzzzzz
14595
14596        buf.<to_off|suffix>  // newest cursor
14597    "};
14598    let expected = indoc! {"
14599        1. buf.to_offsetˇ
14600        2. buf.to_offsetˇsuf
14601        3. buf.to_offsetˇfix
14602        4. buf.to_offsetˇ
14603        5. into_offsetˇensive
14604        6. to_offsetˇsuffix
14605        7. let to_offsetˇ //
14606        8. aato_offsetˇzz
14607        9. buf.to_offsetˇ
14608        10. buf.to_offsetˇsuffix
14609        11. to_offsetˇ
14610
14611        buf.to_offsetˇ  // newest cursor
14612    "};
14613    cx.set_state(initial_state);
14614    cx.update_editor(|editor, window, cx| {
14615        editor.show_completions(&ShowCompletions, window, cx);
14616    });
14617    handle_completion_request_with_insert_and_replace(
14618        &mut cx,
14619        completion_marked_buffer,
14620        vec![(completion_text, completion_text)],
14621        Arc::new(AtomicUsize::new(0)),
14622    )
14623    .await;
14624    cx.condition(|editor, _| editor.context_menu_visible())
14625        .await;
14626    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14627        editor
14628            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14629            .unwrap()
14630    });
14631    cx.assert_editor_state(expected);
14632    handle_resolve_completion_request(&mut cx, None).await;
14633    apply_additional_edits.await.unwrap();
14634
14635    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14636    let completion_text = "foo_and_bar";
14637    let initial_state = indoc! {"
14638        1. ooanbˇ
14639        2. zooanbˇ
14640        3. ooanbˇz
14641        4. zooanbˇz
14642        5. ooanˇ
14643        6. oanbˇ
14644
14645        ooanbˇ
14646    "};
14647    let completion_marked_buffer = indoc! {"
14648        1. ooanb
14649        2. zooanb
14650        3. ooanbz
14651        4. zooanbz
14652        5. ooan
14653        6. oanb
14654
14655        <ooanb|>
14656    "};
14657    let expected = indoc! {"
14658        1. foo_and_barˇ
14659        2. zfoo_and_barˇ
14660        3. foo_and_barˇz
14661        4. zfoo_and_barˇz
14662        5. ooanfoo_and_barˇ
14663        6. oanbfoo_and_barˇ
14664
14665        foo_and_barˇ
14666    "};
14667    cx.set_state(initial_state);
14668    cx.update_editor(|editor, window, cx| {
14669        editor.show_completions(&ShowCompletions, window, cx);
14670    });
14671    handle_completion_request_with_insert_and_replace(
14672        &mut cx,
14673        completion_marked_buffer,
14674        vec![(completion_text, completion_text)],
14675        Arc::new(AtomicUsize::new(0)),
14676    )
14677    .await;
14678    cx.condition(|editor, _| editor.context_menu_visible())
14679        .await;
14680    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14681        editor
14682            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14683            .unwrap()
14684    });
14685    cx.assert_editor_state(expected);
14686    handle_resolve_completion_request(&mut cx, None).await;
14687    apply_additional_edits.await.unwrap();
14688
14689    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14690    // (expects the same as if it was inserted at the end)
14691    let completion_text = "foo_and_bar";
14692    let initial_state = indoc! {"
14693        1. ooˇanb
14694        2. zooˇanb
14695        3. ooˇanbz
14696        4. zooˇanbz
14697
14698        ooˇanb
14699    "};
14700    let completion_marked_buffer = indoc! {"
14701        1. ooanb
14702        2. zooanb
14703        3. ooanbz
14704        4. zooanbz
14705
14706        <oo|anb>
14707    "};
14708    let expected = indoc! {"
14709        1. foo_and_barˇ
14710        2. zfoo_and_barˇ
14711        3. foo_and_barˇz
14712        4. zfoo_and_barˇz
14713
14714        foo_and_barˇ
14715    "};
14716    cx.set_state(initial_state);
14717    cx.update_editor(|editor, window, cx| {
14718        editor.show_completions(&ShowCompletions, window, cx);
14719    });
14720    handle_completion_request_with_insert_and_replace(
14721        &mut cx,
14722        completion_marked_buffer,
14723        vec![(completion_text, completion_text)],
14724        Arc::new(AtomicUsize::new(0)),
14725    )
14726    .await;
14727    cx.condition(|editor, _| editor.context_menu_visible())
14728        .await;
14729    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14730        editor
14731            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14732            .unwrap()
14733    });
14734    cx.assert_editor_state(expected);
14735    handle_resolve_completion_request(&mut cx, None).await;
14736    apply_additional_edits.await.unwrap();
14737}
14738
14739// This used to crash
14740#[gpui::test]
14741async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14742    init_test(cx, |_| {});
14743
14744    let buffer_text = indoc! {"
14745        fn main() {
14746            10.satu;
14747
14748            //
14749            // separate cursors so they open in different excerpts (manually reproducible)
14750            //
14751
14752            10.satu20;
14753        }
14754    "};
14755    let multibuffer_text_with_selections = indoc! {"
14756        fn main() {
14757            10.satuˇ;
14758
14759            //
14760
14761            //
14762
14763            10.satuˇ20;
14764        }
14765    "};
14766    let expected_multibuffer = indoc! {"
14767        fn main() {
14768            10.saturating_sub()ˇ;
14769
14770            //
14771
14772            //
14773
14774            10.saturating_sub()ˇ;
14775        }
14776    "};
14777
14778    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14779    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14780
14781    let fs = FakeFs::new(cx.executor());
14782    fs.insert_tree(
14783        path!("/a"),
14784        json!({
14785            "main.rs": buffer_text,
14786        }),
14787    )
14788    .await;
14789
14790    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14791    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14792    language_registry.add(rust_lang());
14793    let mut fake_servers = language_registry.register_fake_lsp(
14794        "Rust",
14795        FakeLspAdapter {
14796            capabilities: lsp::ServerCapabilities {
14797                completion_provider: Some(lsp::CompletionOptions {
14798                    resolve_provider: None,
14799                    ..lsp::CompletionOptions::default()
14800                }),
14801                ..lsp::ServerCapabilities::default()
14802            },
14803            ..FakeLspAdapter::default()
14804        },
14805    );
14806    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14807    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14808    let buffer = project
14809        .update(cx, |project, cx| {
14810            project.open_local_buffer(path!("/a/main.rs"), cx)
14811        })
14812        .await
14813        .unwrap();
14814
14815    let multi_buffer = cx.new(|cx| {
14816        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14817        multi_buffer.push_excerpts(
14818            buffer.clone(),
14819            [ExcerptRange::new(0..first_excerpt_end)],
14820            cx,
14821        );
14822        multi_buffer.push_excerpts(
14823            buffer.clone(),
14824            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14825            cx,
14826        );
14827        multi_buffer
14828    });
14829
14830    let editor = workspace
14831        .update(cx, |_, window, cx| {
14832            cx.new(|cx| {
14833                Editor::new(
14834                    EditorMode::Full {
14835                        scale_ui_elements_with_buffer_font_size: false,
14836                        show_active_line_background: false,
14837                        sizing_behavior: SizingBehavior::Default,
14838                    },
14839                    multi_buffer.clone(),
14840                    Some(project.clone()),
14841                    window,
14842                    cx,
14843                )
14844            })
14845        })
14846        .unwrap();
14847
14848    let pane = workspace
14849        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14850        .unwrap();
14851    pane.update_in(cx, |pane, window, cx| {
14852        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14853    });
14854
14855    let fake_server = fake_servers.next().await.unwrap();
14856    cx.run_until_parked();
14857
14858    editor.update_in(cx, |editor, window, cx| {
14859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14860            s.select_ranges([
14861                Point::new(1, 11)..Point::new(1, 11),
14862                Point::new(7, 11)..Point::new(7, 11),
14863            ])
14864        });
14865
14866        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14867    });
14868
14869    editor.update_in(cx, |editor, window, cx| {
14870        editor.show_completions(&ShowCompletions, window, cx);
14871    });
14872
14873    fake_server
14874        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14875            let completion_item = lsp::CompletionItem {
14876                label: "saturating_sub()".into(),
14877                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14878                    lsp::InsertReplaceEdit {
14879                        new_text: "saturating_sub()".to_owned(),
14880                        insert: lsp::Range::new(
14881                            lsp::Position::new(7, 7),
14882                            lsp::Position::new(7, 11),
14883                        ),
14884                        replace: lsp::Range::new(
14885                            lsp::Position::new(7, 7),
14886                            lsp::Position::new(7, 13),
14887                        ),
14888                    },
14889                )),
14890                ..lsp::CompletionItem::default()
14891            };
14892
14893            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14894        })
14895        .next()
14896        .await
14897        .unwrap();
14898
14899    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14900        .await;
14901
14902    editor
14903        .update_in(cx, |editor, window, cx| {
14904            editor
14905                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14906                .unwrap()
14907        })
14908        .await
14909        .unwrap();
14910
14911    editor.update(cx, |editor, cx| {
14912        assert_text_with_selections(editor, expected_multibuffer, cx);
14913    })
14914}
14915
14916#[gpui::test]
14917async fn test_completion(cx: &mut TestAppContext) {
14918    init_test(cx, |_| {});
14919
14920    let mut cx = EditorLspTestContext::new_rust(
14921        lsp::ServerCapabilities {
14922            completion_provider: Some(lsp::CompletionOptions {
14923                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14924                resolve_provider: Some(true),
14925                ..Default::default()
14926            }),
14927            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14928            ..Default::default()
14929        },
14930        cx,
14931    )
14932    .await;
14933    let counter = Arc::new(AtomicUsize::new(0));
14934
14935    cx.set_state(indoc! {"
14936        oneˇ
14937        two
14938        three
14939    "});
14940    cx.simulate_keystroke(".");
14941    handle_completion_request(
14942        indoc! {"
14943            one.|<>
14944            two
14945            three
14946        "},
14947        vec!["first_completion", "second_completion"],
14948        true,
14949        counter.clone(),
14950        &mut cx,
14951    )
14952    .await;
14953    cx.condition(|editor, _| editor.context_menu_visible())
14954        .await;
14955    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14956
14957    let _handler = handle_signature_help_request(
14958        &mut cx,
14959        lsp::SignatureHelp {
14960            signatures: vec![lsp::SignatureInformation {
14961                label: "test signature".to_string(),
14962                documentation: None,
14963                parameters: Some(vec![lsp::ParameterInformation {
14964                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14965                    documentation: None,
14966                }]),
14967                active_parameter: None,
14968            }],
14969            active_signature: None,
14970            active_parameter: None,
14971        },
14972    );
14973    cx.update_editor(|editor, window, cx| {
14974        assert!(
14975            !editor.signature_help_state.is_shown(),
14976            "No signature help was called for"
14977        );
14978        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14979    });
14980    cx.run_until_parked();
14981    cx.update_editor(|editor, _, _| {
14982        assert!(
14983            !editor.signature_help_state.is_shown(),
14984            "No signature help should be shown when completions menu is open"
14985        );
14986    });
14987
14988    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14989        editor.context_menu_next(&Default::default(), window, cx);
14990        editor
14991            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14992            .unwrap()
14993    });
14994    cx.assert_editor_state(indoc! {"
14995        one.second_completionˇ
14996        two
14997        three
14998    "});
14999
15000    handle_resolve_completion_request(
15001        &mut cx,
15002        Some(vec![
15003            (
15004                //This overlaps with the primary completion edit which is
15005                //misbehavior from the LSP spec, test that we filter it out
15006                indoc! {"
15007                    one.second_ˇcompletion
15008                    two
15009                    threeˇ
15010                "},
15011                "overlapping additional edit",
15012            ),
15013            (
15014                indoc! {"
15015                    one.second_completion
15016                    two
15017                    threeˇ
15018                "},
15019                "\nadditional edit",
15020            ),
15021        ]),
15022    )
15023    .await;
15024    apply_additional_edits.await.unwrap();
15025    cx.assert_editor_state(indoc! {"
15026        one.second_completionˇ
15027        two
15028        three
15029        additional edit
15030    "});
15031
15032    cx.set_state(indoc! {"
15033        one.second_completion
15034        twoˇ
15035        threeˇ
15036        additional edit
15037    "});
15038    cx.simulate_keystroke(" ");
15039    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15040    cx.simulate_keystroke("s");
15041    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15042
15043    cx.assert_editor_state(indoc! {"
15044        one.second_completion
15045        two sˇ
15046        three sˇ
15047        additional edit
15048    "});
15049    handle_completion_request(
15050        indoc! {"
15051            one.second_completion
15052            two s
15053            three <s|>
15054            additional edit
15055        "},
15056        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15057        true,
15058        counter.clone(),
15059        &mut cx,
15060    )
15061    .await;
15062    cx.condition(|editor, _| editor.context_menu_visible())
15063        .await;
15064    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15065
15066    cx.simulate_keystroke("i");
15067
15068    handle_completion_request(
15069        indoc! {"
15070            one.second_completion
15071            two si
15072            three <si|>
15073            additional edit
15074        "},
15075        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15076        true,
15077        counter.clone(),
15078        &mut cx,
15079    )
15080    .await;
15081    cx.condition(|editor, _| editor.context_menu_visible())
15082        .await;
15083    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15084
15085    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15086        editor
15087            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15088            .unwrap()
15089    });
15090    cx.assert_editor_state(indoc! {"
15091        one.second_completion
15092        two sixth_completionˇ
15093        three sixth_completionˇ
15094        additional edit
15095    "});
15096
15097    apply_additional_edits.await.unwrap();
15098
15099    update_test_language_settings(&mut cx, |settings| {
15100        settings.defaults.show_completions_on_input = Some(false);
15101    });
15102    cx.set_state("editorˇ");
15103    cx.simulate_keystroke(".");
15104    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15105    cx.simulate_keystrokes("c l o");
15106    cx.assert_editor_state("editor.cloˇ");
15107    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15108    cx.update_editor(|editor, window, cx| {
15109        editor.show_completions(&ShowCompletions, window, cx);
15110    });
15111    handle_completion_request(
15112        "editor.<clo|>",
15113        vec!["close", "clobber"],
15114        true,
15115        counter.clone(),
15116        &mut cx,
15117    )
15118    .await;
15119    cx.condition(|editor, _| editor.context_menu_visible())
15120        .await;
15121    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15122
15123    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15124        editor
15125            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15126            .unwrap()
15127    });
15128    cx.assert_editor_state("editor.clobberˇ");
15129    handle_resolve_completion_request(&mut cx, None).await;
15130    apply_additional_edits.await.unwrap();
15131}
15132
15133#[gpui::test]
15134async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15135    init_test(cx, |_| {});
15136
15137    let fs = FakeFs::new(cx.executor());
15138    fs.insert_tree(
15139        path!("/a"),
15140        json!({
15141            "main.rs": "",
15142        }),
15143    )
15144    .await;
15145
15146    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15147    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15148    language_registry.add(rust_lang());
15149    let command_calls = Arc::new(AtomicUsize::new(0));
15150    let registered_command = "_the/command";
15151
15152    let closure_command_calls = command_calls.clone();
15153    let mut fake_servers = language_registry.register_fake_lsp(
15154        "Rust",
15155        FakeLspAdapter {
15156            capabilities: lsp::ServerCapabilities {
15157                completion_provider: Some(lsp::CompletionOptions {
15158                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15159                    ..lsp::CompletionOptions::default()
15160                }),
15161                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15162                    commands: vec![registered_command.to_owned()],
15163                    ..lsp::ExecuteCommandOptions::default()
15164                }),
15165                ..lsp::ServerCapabilities::default()
15166            },
15167            initializer: Some(Box::new(move |fake_server| {
15168                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15169                    move |params, _| async move {
15170                        Ok(Some(lsp::CompletionResponse::Array(vec![
15171                            lsp::CompletionItem {
15172                                label: "registered_command".to_owned(),
15173                                text_edit: gen_text_edit(&params, ""),
15174                                command: Some(lsp::Command {
15175                                    title: registered_command.to_owned(),
15176                                    command: "_the/command".to_owned(),
15177                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15178                                }),
15179                                ..lsp::CompletionItem::default()
15180                            },
15181                            lsp::CompletionItem {
15182                                label: "unregistered_command".to_owned(),
15183                                text_edit: gen_text_edit(&params, ""),
15184                                command: Some(lsp::Command {
15185                                    title: "????????????".to_owned(),
15186                                    command: "????????????".to_owned(),
15187                                    arguments: Some(vec![serde_json::Value::Null]),
15188                                }),
15189                                ..lsp::CompletionItem::default()
15190                            },
15191                        ])))
15192                    },
15193                );
15194                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15195                    let command_calls = closure_command_calls.clone();
15196                    move |params, _| {
15197                        assert_eq!(params.command, registered_command);
15198                        let command_calls = command_calls.clone();
15199                        async move {
15200                            command_calls.fetch_add(1, atomic::Ordering::Release);
15201                            Ok(Some(json!(null)))
15202                        }
15203                    }
15204                });
15205            })),
15206            ..FakeLspAdapter::default()
15207        },
15208    );
15209    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15210    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15211    let editor = workspace
15212        .update(cx, |workspace, window, cx| {
15213            workspace.open_abs_path(
15214                PathBuf::from(path!("/a/main.rs")),
15215                OpenOptions::default(),
15216                window,
15217                cx,
15218            )
15219        })
15220        .unwrap()
15221        .await
15222        .unwrap()
15223        .downcast::<Editor>()
15224        .unwrap();
15225    let _fake_server = fake_servers.next().await.unwrap();
15226    cx.run_until_parked();
15227
15228    editor.update_in(cx, |editor, window, cx| {
15229        cx.focus_self(window);
15230        editor.move_to_end(&MoveToEnd, window, cx);
15231        editor.handle_input(".", window, cx);
15232    });
15233    cx.run_until_parked();
15234    editor.update(cx, |editor, _| {
15235        assert!(editor.context_menu_visible());
15236        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15237        {
15238            let completion_labels = menu
15239                .completions
15240                .borrow()
15241                .iter()
15242                .map(|c| c.label.text.clone())
15243                .collect::<Vec<_>>();
15244            assert_eq!(
15245                completion_labels,
15246                &["registered_command", "unregistered_command",],
15247            );
15248        } else {
15249            panic!("expected completion menu to be open");
15250        }
15251    });
15252
15253    editor
15254        .update_in(cx, |editor, window, cx| {
15255            editor
15256                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15257                .unwrap()
15258        })
15259        .await
15260        .unwrap();
15261    cx.run_until_parked();
15262    assert_eq!(
15263        command_calls.load(atomic::Ordering::Acquire),
15264        1,
15265        "For completion with a registered command, Zed should send a command execution request",
15266    );
15267
15268    editor.update_in(cx, |editor, window, cx| {
15269        cx.focus_self(window);
15270        editor.handle_input(".", window, cx);
15271    });
15272    cx.run_until_parked();
15273    editor.update(cx, |editor, _| {
15274        assert!(editor.context_menu_visible());
15275        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15276        {
15277            let completion_labels = menu
15278                .completions
15279                .borrow()
15280                .iter()
15281                .map(|c| c.label.text.clone())
15282                .collect::<Vec<_>>();
15283            assert_eq!(
15284                completion_labels,
15285                &["registered_command", "unregistered_command",],
15286            );
15287        } else {
15288            panic!("expected completion menu to be open");
15289        }
15290    });
15291    editor
15292        .update_in(cx, |editor, window, cx| {
15293            editor.context_menu_next(&Default::default(), window, cx);
15294            editor
15295                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15296                .unwrap()
15297        })
15298        .await
15299        .unwrap();
15300    cx.run_until_parked();
15301    assert_eq!(
15302        command_calls.load(atomic::Ordering::Acquire),
15303        1,
15304        "For completion with an unregistered command, Zed should not send a command execution request",
15305    );
15306}
15307
15308#[gpui::test]
15309async fn test_completion_reuse(cx: &mut TestAppContext) {
15310    init_test(cx, |_| {});
15311
15312    let mut cx = EditorLspTestContext::new_rust(
15313        lsp::ServerCapabilities {
15314            completion_provider: Some(lsp::CompletionOptions {
15315                trigger_characters: Some(vec![".".to_string()]),
15316                ..Default::default()
15317            }),
15318            ..Default::default()
15319        },
15320        cx,
15321    )
15322    .await;
15323
15324    let counter = Arc::new(AtomicUsize::new(0));
15325    cx.set_state("objˇ");
15326    cx.simulate_keystroke(".");
15327
15328    // Initial completion request returns complete results
15329    let is_incomplete = false;
15330    handle_completion_request(
15331        "obj.|<>",
15332        vec!["a", "ab", "abc"],
15333        is_incomplete,
15334        counter.clone(),
15335        &mut cx,
15336    )
15337    .await;
15338    cx.run_until_parked();
15339    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15340    cx.assert_editor_state("obj.ˇ");
15341    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15342
15343    // Type "a" - filters existing completions
15344    cx.simulate_keystroke("a");
15345    cx.run_until_parked();
15346    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15347    cx.assert_editor_state("obj.aˇ");
15348    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15349
15350    // Type "b" - filters existing completions
15351    cx.simulate_keystroke("b");
15352    cx.run_until_parked();
15353    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15354    cx.assert_editor_state("obj.abˇ");
15355    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15356
15357    // Type "c" - filters existing completions
15358    cx.simulate_keystroke("c");
15359    cx.run_until_parked();
15360    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15361    cx.assert_editor_state("obj.abcˇ");
15362    check_displayed_completions(vec!["abc"], &mut cx);
15363
15364    // Backspace to delete "c" - filters existing completions
15365    cx.update_editor(|editor, window, cx| {
15366        editor.backspace(&Backspace, window, cx);
15367    });
15368    cx.run_until_parked();
15369    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15370    cx.assert_editor_state("obj.abˇ");
15371    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15372
15373    // Moving cursor to the left dismisses menu.
15374    cx.update_editor(|editor, window, cx| {
15375        editor.move_left(&MoveLeft, window, cx);
15376    });
15377    cx.run_until_parked();
15378    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15379    cx.assert_editor_state("obj.aˇb");
15380    cx.update_editor(|editor, _, _| {
15381        assert_eq!(editor.context_menu_visible(), false);
15382    });
15383
15384    // Type "b" - new request
15385    cx.simulate_keystroke("b");
15386    let is_incomplete = false;
15387    handle_completion_request(
15388        "obj.<ab|>a",
15389        vec!["ab", "abc"],
15390        is_incomplete,
15391        counter.clone(),
15392        &mut cx,
15393    )
15394    .await;
15395    cx.run_until_parked();
15396    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15397    cx.assert_editor_state("obj.abˇb");
15398    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15399
15400    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15401    cx.update_editor(|editor, window, cx| {
15402        editor.backspace(&Backspace, window, cx);
15403    });
15404    let is_incomplete = false;
15405    handle_completion_request(
15406        "obj.<a|>b",
15407        vec!["a", "ab", "abc"],
15408        is_incomplete,
15409        counter.clone(),
15410        &mut cx,
15411    )
15412    .await;
15413    cx.run_until_parked();
15414    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15415    cx.assert_editor_state("obj.aˇb");
15416    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15417
15418    // Backspace to delete "a" - dismisses menu.
15419    cx.update_editor(|editor, window, cx| {
15420        editor.backspace(&Backspace, window, cx);
15421    });
15422    cx.run_until_parked();
15423    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15424    cx.assert_editor_state("obj.ˇb");
15425    cx.update_editor(|editor, _, _| {
15426        assert_eq!(editor.context_menu_visible(), false);
15427    });
15428}
15429
15430#[gpui::test]
15431async fn test_word_completion(cx: &mut TestAppContext) {
15432    let lsp_fetch_timeout_ms = 10;
15433    init_test(cx, |language_settings| {
15434        language_settings.defaults.completions = Some(CompletionSettingsContent {
15435            words_min_length: Some(0),
15436            lsp_fetch_timeout_ms: Some(10),
15437            lsp_insert_mode: Some(LspInsertMode::Insert),
15438            ..Default::default()
15439        });
15440    });
15441
15442    let mut cx = EditorLspTestContext::new_rust(
15443        lsp::ServerCapabilities {
15444            completion_provider: Some(lsp::CompletionOptions {
15445                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15446                ..lsp::CompletionOptions::default()
15447            }),
15448            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15449            ..lsp::ServerCapabilities::default()
15450        },
15451        cx,
15452    )
15453    .await;
15454
15455    let throttle_completions = Arc::new(AtomicBool::new(false));
15456
15457    let lsp_throttle_completions = throttle_completions.clone();
15458    let _completion_requests_handler =
15459        cx.lsp
15460            .server
15461            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15462                let lsp_throttle_completions = lsp_throttle_completions.clone();
15463                let cx = cx.clone();
15464                async move {
15465                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15466                        cx.background_executor()
15467                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15468                            .await;
15469                    }
15470                    Ok(Some(lsp::CompletionResponse::Array(vec![
15471                        lsp::CompletionItem {
15472                            label: "first".into(),
15473                            ..lsp::CompletionItem::default()
15474                        },
15475                        lsp::CompletionItem {
15476                            label: "last".into(),
15477                            ..lsp::CompletionItem::default()
15478                        },
15479                    ])))
15480                }
15481            });
15482
15483    cx.set_state(indoc! {"
15484        oneˇ
15485        two
15486        three
15487    "});
15488    cx.simulate_keystroke(".");
15489    cx.executor().run_until_parked();
15490    cx.condition(|editor, _| editor.context_menu_visible())
15491        .await;
15492    cx.update_editor(|editor, window, cx| {
15493        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15494        {
15495            assert_eq!(
15496                completion_menu_entries(menu),
15497                &["first", "last"],
15498                "When LSP server is fast to reply, no fallback word completions are used"
15499            );
15500        } else {
15501            panic!("expected completion menu to be open");
15502        }
15503        editor.cancel(&Cancel, window, cx);
15504    });
15505    cx.executor().run_until_parked();
15506    cx.condition(|editor, _| !editor.context_menu_visible())
15507        .await;
15508
15509    throttle_completions.store(true, atomic::Ordering::Release);
15510    cx.simulate_keystroke(".");
15511    cx.executor()
15512        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15513    cx.executor().run_until_parked();
15514    cx.condition(|editor, _| editor.context_menu_visible())
15515        .await;
15516    cx.update_editor(|editor, _, _| {
15517        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15518        {
15519            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15520                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15521        } else {
15522            panic!("expected completion menu to be open");
15523        }
15524    });
15525}
15526
15527#[gpui::test]
15528async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15529    init_test(cx, |language_settings| {
15530        language_settings.defaults.completions = Some(CompletionSettingsContent {
15531            words: Some(WordsCompletionMode::Enabled),
15532            words_min_length: Some(0),
15533            lsp_insert_mode: Some(LspInsertMode::Insert),
15534            ..Default::default()
15535        });
15536    });
15537
15538    let mut cx = EditorLspTestContext::new_rust(
15539        lsp::ServerCapabilities {
15540            completion_provider: Some(lsp::CompletionOptions {
15541                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15542                ..lsp::CompletionOptions::default()
15543            }),
15544            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15545            ..lsp::ServerCapabilities::default()
15546        },
15547        cx,
15548    )
15549    .await;
15550
15551    let _completion_requests_handler =
15552        cx.lsp
15553            .server
15554            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15555                Ok(Some(lsp::CompletionResponse::Array(vec![
15556                    lsp::CompletionItem {
15557                        label: "first".into(),
15558                        ..lsp::CompletionItem::default()
15559                    },
15560                    lsp::CompletionItem {
15561                        label: "last".into(),
15562                        ..lsp::CompletionItem::default()
15563                    },
15564                ])))
15565            });
15566
15567    cx.set_state(indoc! {"ˇ
15568        first
15569        last
15570        second
15571    "});
15572    cx.simulate_keystroke(".");
15573    cx.executor().run_until_parked();
15574    cx.condition(|editor, _| editor.context_menu_visible())
15575        .await;
15576    cx.update_editor(|editor, _, _| {
15577        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15578        {
15579            assert_eq!(
15580                completion_menu_entries(menu),
15581                &["first", "last", "second"],
15582                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15583            );
15584        } else {
15585            panic!("expected completion menu to be open");
15586        }
15587    });
15588}
15589
15590#[gpui::test]
15591async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15592    init_test(cx, |language_settings| {
15593        language_settings.defaults.completions = Some(CompletionSettingsContent {
15594            words: Some(WordsCompletionMode::Disabled),
15595            words_min_length: Some(0),
15596            lsp_insert_mode: Some(LspInsertMode::Insert),
15597            ..Default::default()
15598        });
15599    });
15600
15601    let mut cx = EditorLspTestContext::new_rust(
15602        lsp::ServerCapabilities {
15603            completion_provider: Some(lsp::CompletionOptions {
15604                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15605                ..lsp::CompletionOptions::default()
15606            }),
15607            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15608            ..lsp::ServerCapabilities::default()
15609        },
15610        cx,
15611    )
15612    .await;
15613
15614    let _completion_requests_handler =
15615        cx.lsp
15616            .server
15617            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15618                panic!("LSP completions should not be queried when dealing with word completions")
15619            });
15620
15621    cx.set_state(indoc! {"ˇ
15622        first
15623        last
15624        second
15625    "});
15626    cx.update_editor(|editor, window, cx| {
15627        editor.show_word_completions(&ShowWordCompletions, window, cx);
15628    });
15629    cx.executor().run_until_parked();
15630    cx.condition(|editor, _| editor.context_menu_visible())
15631        .await;
15632    cx.update_editor(|editor, _, _| {
15633        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15634        {
15635            assert_eq!(
15636                completion_menu_entries(menu),
15637                &["first", "last", "second"],
15638                "`ShowWordCompletions` action should show word completions"
15639            );
15640        } else {
15641            panic!("expected completion menu to be open");
15642        }
15643    });
15644
15645    cx.simulate_keystroke("l");
15646    cx.executor().run_until_parked();
15647    cx.condition(|editor, _| editor.context_menu_visible())
15648        .await;
15649    cx.update_editor(|editor, _, _| {
15650        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15651        {
15652            assert_eq!(
15653                completion_menu_entries(menu),
15654                &["last"],
15655                "After showing word completions, further editing should filter them and not query the LSP"
15656            );
15657        } else {
15658            panic!("expected completion menu to be open");
15659        }
15660    });
15661}
15662
15663#[gpui::test]
15664async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15665    init_test(cx, |language_settings| {
15666        language_settings.defaults.completions = Some(CompletionSettingsContent {
15667            words_min_length: Some(0),
15668            lsp: Some(false),
15669            lsp_insert_mode: Some(LspInsertMode::Insert),
15670            ..Default::default()
15671        });
15672    });
15673
15674    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15675
15676    cx.set_state(indoc! {"ˇ
15677        0_usize
15678        let
15679        33
15680        4.5f32
15681    "});
15682    cx.update_editor(|editor, window, cx| {
15683        editor.show_completions(&ShowCompletions, window, cx);
15684    });
15685    cx.executor().run_until_parked();
15686    cx.condition(|editor, _| editor.context_menu_visible())
15687        .await;
15688    cx.update_editor(|editor, window, cx| {
15689        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15690        {
15691            assert_eq!(
15692                completion_menu_entries(menu),
15693                &["let"],
15694                "With no digits in the completion query, no digits should be in the word completions"
15695            );
15696        } else {
15697            panic!("expected completion menu to be open");
15698        }
15699        editor.cancel(&Cancel, window, cx);
15700    });
15701
15702    cx.set_state(indoc! {"15703        0_usize
15704        let
15705        3
15706        33.35f32
15707    "});
15708    cx.update_editor(|editor, window, cx| {
15709        editor.show_completions(&ShowCompletions, window, cx);
15710    });
15711    cx.executor().run_until_parked();
15712    cx.condition(|editor, _| editor.context_menu_visible())
15713        .await;
15714    cx.update_editor(|editor, _, _| {
15715        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15716        {
15717            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15718                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15719        } else {
15720            panic!("expected completion menu to be open");
15721        }
15722    });
15723}
15724
15725#[gpui::test]
15726async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15727    init_test(cx, |language_settings| {
15728        language_settings.defaults.completions = Some(CompletionSettingsContent {
15729            words: Some(WordsCompletionMode::Enabled),
15730            words_min_length: Some(3),
15731            lsp_insert_mode: Some(LspInsertMode::Insert),
15732            ..Default::default()
15733        });
15734    });
15735
15736    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15737    cx.set_state(indoc! {"ˇ
15738        wow
15739        wowen
15740        wowser
15741    "});
15742    cx.simulate_keystroke("w");
15743    cx.executor().run_until_parked();
15744    cx.update_editor(|editor, _, _| {
15745        if editor.context_menu.borrow_mut().is_some() {
15746            panic!(
15747                "expected completion menu to be hidden, as words completion threshold is not met"
15748            );
15749        }
15750    });
15751
15752    cx.update_editor(|editor, window, cx| {
15753        editor.show_word_completions(&ShowWordCompletions, window, cx);
15754    });
15755    cx.executor().run_until_parked();
15756    cx.update_editor(|editor, window, cx| {
15757        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15758        {
15759            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");
15760        } else {
15761            panic!("expected completion menu to be open after the word completions are called with an action");
15762        }
15763
15764        editor.cancel(&Cancel, window, cx);
15765    });
15766    cx.update_editor(|editor, _, _| {
15767        if editor.context_menu.borrow_mut().is_some() {
15768            panic!("expected completion menu to be hidden after canceling");
15769        }
15770    });
15771
15772    cx.simulate_keystroke("o");
15773    cx.executor().run_until_parked();
15774    cx.update_editor(|editor, _, _| {
15775        if editor.context_menu.borrow_mut().is_some() {
15776            panic!(
15777                "expected completion menu to be hidden, as words completion threshold is not met still"
15778            );
15779        }
15780    });
15781
15782    cx.simulate_keystroke("w");
15783    cx.executor().run_until_parked();
15784    cx.update_editor(|editor, _, _| {
15785        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15786        {
15787            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15788        } else {
15789            panic!("expected completion menu to be open after the word completions threshold is met");
15790        }
15791    });
15792}
15793
15794#[gpui::test]
15795async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15796    init_test(cx, |language_settings| {
15797        language_settings.defaults.completions = Some(CompletionSettingsContent {
15798            words: Some(WordsCompletionMode::Enabled),
15799            words_min_length: Some(0),
15800            lsp_insert_mode: Some(LspInsertMode::Insert),
15801            ..Default::default()
15802        });
15803    });
15804
15805    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15806    cx.update_editor(|editor, _, _| {
15807        editor.disable_word_completions();
15808    });
15809    cx.set_state(indoc! {"ˇ
15810        wow
15811        wowen
15812        wowser
15813    "});
15814    cx.simulate_keystroke("w");
15815    cx.executor().run_until_parked();
15816    cx.update_editor(|editor, _, _| {
15817        if editor.context_menu.borrow_mut().is_some() {
15818            panic!(
15819                "expected completion menu to be hidden, as words completion are disabled for this editor"
15820            );
15821        }
15822    });
15823
15824    cx.update_editor(|editor, window, cx| {
15825        editor.show_word_completions(&ShowWordCompletions, window, cx);
15826    });
15827    cx.executor().run_until_parked();
15828    cx.update_editor(|editor, _, _| {
15829        if editor.context_menu.borrow_mut().is_some() {
15830            panic!(
15831                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15832            );
15833        }
15834    });
15835}
15836
15837#[gpui::test]
15838async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15839    init_test(cx, |language_settings| {
15840        language_settings.defaults.completions = Some(CompletionSettingsContent {
15841            words: Some(WordsCompletionMode::Disabled),
15842            words_min_length: Some(0),
15843            lsp_insert_mode: Some(LspInsertMode::Insert),
15844            ..Default::default()
15845        });
15846    });
15847
15848    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15849    cx.update_editor(|editor, _, _| {
15850        editor.set_completion_provider(None);
15851    });
15852    cx.set_state(indoc! {"ˇ
15853        wow
15854        wowen
15855        wowser
15856    "});
15857    cx.simulate_keystroke("w");
15858    cx.executor().run_until_parked();
15859    cx.update_editor(|editor, _, _| {
15860        if editor.context_menu.borrow_mut().is_some() {
15861            panic!("expected completion menu to be hidden, as disabled in settings");
15862        }
15863    });
15864}
15865
15866fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15867    let position = || lsp::Position {
15868        line: params.text_document_position.position.line,
15869        character: params.text_document_position.position.character,
15870    };
15871    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15872        range: lsp::Range {
15873            start: position(),
15874            end: position(),
15875        },
15876        new_text: text.to_string(),
15877    }))
15878}
15879
15880#[gpui::test]
15881async fn test_multiline_completion(cx: &mut TestAppContext) {
15882    init_test(cx, |_| {});
15883
15884    let fs = FakeFs::new(cx.executor());
15885    fs.insert_tree(
15886        path!("/a"),
15887        json!({
15888            "main.ts": "a",
15889        }),
15890    )
15891    .await;
15892
15893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15894    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15895    let typescript_language = Arc::new(Language::new(
15896        LanguageConfig {
15897            name: "TypeScript".into(),
15898            matcher: LanguageMatcher {
15899                path_suffixes: vec!["ts".to_string()],
15900                ..LanguageMatcher::default()
15901            },
15902            line_comments: vec!["// ".into()],
15903            ..LanguageConfig::default()
15904        },
15905        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15906    ));
15907    language_registry.add(typescript_language.clone());
15908    let mut fake_servers = language_registry.register_fake_lsp(
15909        "TypeScript",
15910        FakeLspAdapter {
15911            capabilities: lsp::ServerCapabilities {
15912                completion_provider: Some(lsp::CompletionOptions {
15913                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15914                    ..lsp::CompletionOptions::default()
15915                }),
15916                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15917                ..lsp::ServerCapabilities::default()
15918            },
15919            // Emulate vtsls label generation
15920            label_for_completion: Some(Box::new(|item, _| {
15921                let text = if let Some(description) = item
15922                    .label_details
15923                    .as_ref()
15924                    .and_then(|label_details| label_details.description.as_ref())
15925                {
15926                    format!("{} {}", item.label, description)
15927                } else if let Some(detail) = &item.detail {
15928                    format!("{} {}", item.label, detail)
15929                } else {
15930                    item.label.clone()
15931                };
15932                Some(language::CodeLabel::plain(text, None))
15933            })),
15934            ..FakeLspAdapter::default()
15935        },
15936    );
15937    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15938    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15939    let worktree_id = workspace
15940        .update(cx, |workspace, _window, cx| {
15941            workspace.project().update(cx, |project, cx| {
15942                project.worktrees(cx).next().unwrap().read(cx).id()
15943            })
15944        })
15945        .unwrap();
15946    let _buffer = project
15947        .update(cx, |project, cx| {
15948            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15949        })
15950        .await
15951        .unwrap();
15952    let editor = workspace
15953        .update(cx, |workspace, window, cx| {
15954            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15955        })
15956        .unwrap()
15957        .await
15958        .unwrap()
15959        .downcast::<Editor>()
15960        .unwrap();
15961    let fake_server = fake_servers.next().await.unwrap();
15962    cx.run_until_parked();
15963
15964    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15965    let multiline_label_2 = "a\nb\nc\n";
15966    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15967    let multiline_description = "d\ne\nf\n";
15968    let multiline_detail_2 = "g\nh\ni\n";
15969
15970    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15971        move |params, _| async move {
15972            Ok(Some(lsp::CompletionResponse::Array(vec![
15973                lsp::CompletionItem {
15974                    label: multiline_label.to_string(),
15975                    text_edit: gen_text_edit(&params, "new_text_1"),
15976                    ..lsp::CompletionItem::default()
15977                },
15978                lsp::CompletionItem {
15979                    label: "single line label 1".to_string(),
15980                    detail: Some(multiline_detail.to_string()),
15981                    text_edit: gen_text_edit(&params, "new_text_2"),
15982                    ..lsp::CompletionItem::default()
15983                },
15984                lsp::CompletionItem {
15985                    label: "single line label 2".to_string(),
15986                    label_details: Some(lsp::CompletionItemLabelDetails {
15987                        description: Some(multiline_description.to_string()),
15988                        detail: None,
15989                    }),
15990                    text_edit: gen_text_edit(&params, "new_text_2"),
15991                    ..lsp::CompletionItem::default()
15992                },
15993                lsp::CompletionItem {
15994                    label: multiline_label_2.to_string(),
15995                    detail: Some(multiline_detail_2.to_string()),
15996                    text_edit: gen_text_edit(&params, "new_text_3"),
15997                    ..lsp::CompletionItem::default()
15998                },
15999                lsp::CompletionItem {
16000                    label: "Label with many     spaces and \t but without newlines".to_string(),
16001                    detail: Some(
16002                        "Details with many     spaces and \t but without newlines".to_string(),
16003                    ),
16004                    text_edit: gen_text_edit(&params, "new_text_4"),
16005                    ..lsp::CompletionItem::default()
16006                },
16007            ])))
16008        },
16009    );
16010
16011    editor.update_in(cx, |editor, window, cx| {
16012        cx.focus_self(window);
16013        editor.move_to_end(&MoveToEnd, window, cx);
16014        editor.handle_input(".", window, cx);
16015    });
16016    cx.run_until_parked();
16017    completion_handle.next().await.unwrap();
16018
16019    editor.update(cx, |editor, _| {
16020        assert!(editor.context_menu_visible());
16021        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16022        {
16023            let completion_labels = menu
16024                .completions
16025                .borrow()
16026                .iter()
16027                .map(|c| c.label.text.clone())
16028                .collect::<Vec<_>>();
16029            assert_eq!(
16030                completion_labels,
16031                &[
16032                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16033                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16034                    "single line label 2 d e f ",
16035                    "a b c g h i ",
16036                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16037                ],
16038                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16039            );
16040
16041            for completion in menu
16042                .completions
16043                .borrow()
16044                .iter() {
16045                    assert_eq!(
16046                        completion.label.filter_range,
16047                        0..completion.label.text.len(),
16048                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16049                    );
16050                }
16051        } else {
16052            panic!("expected completion menu to be open");
16053        }
16054    });
16055}
16056
16057#[gpui::test]
16058async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16059    init_test(cx, |_| {});
16060    let mut cx = EditorLspTestContext::new_rust(
16061        lsp::ServerCapabilities {
16062            completion_provider: Some(lsp::CompletionOptions {
16063                trigger_characters: Some(vec![".".to_string()]),
16064                ..Default::default()
16065            }),
16066            ..Default::default()
16067        },
16068        cx,
16069    )
16070    .await;
16071    cx.lsp
16072        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16073            Ok(Some(lsp::CompletionResponse::Array(vec![
16074                lsp::CompletionItem {
16075                    label: "first".into(),
16076                    ..Default::default()
16077                },
16078                lsp::CompletionItem {
16079                    label: "last".into(),
16080                    ..Default::default()
16081                },
16082            ])))
16083        });
16084    cx.set_state("variableˇ");
16085    cx.simulate_keystroke(".");
16086    cx.executor().run_until_parked();
16087
16088    cx.update_editor(|editor, _, _| {
16089        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16090        {
16091            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16092        } else {
16093            panic!("expected completion menu to be open");
16094        }
16095    });
16096
16097    cx.update_editor(|editor, window, cx| {
16098        editor.move_page_down(&MovePageDown::default(), window, cx);
16099        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16100        {
16101            assert!(
16102                menu.selected_item == 1,
16103                "expected PageDown to select the last item from the context menu"
16104            );
16105        } else {
16106            panic!("expected completion menu to stay open after PageDown");
16107        }
16108    });
16109
16110    cx.update_editor(|editor, window, cx| {
16111        editor.move_page_up(&MovePageUp::default(), window, cx);
16112        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16113        {
16114            assert!(
16115                menu.selected_item == 0,
16116                "expected PageUp to select the first item from the context menu"
16117            );
16118        } else {
16119            panic!("expected completion menu to stay open after PageUp");
16120        }
16121    });
16122}
16123
16124#[gpui::test]
16125async fn test_as_is_completions(cx: &mut TestAppContext) {
16126    init_test(cx, |_| {});
16127    let mut cx = EditorLspTestContext::new_rust(
16128        lsp::ServerCapabilities {
16129            completion_provider: Some(lsp::CompletionOptions {
16130                ..Default::default()
16131            }),
16132            ..Default::default()
16133        },
16134        cx,
16135    )
16136    .await;
16137    cx.lsp
16138        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16139            Ok(Some(lsp::CompletionResponse::Array(vec![
16140                lsp::CompletionItem {
16141                    label: "unsafe".into(),
16142                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16143                        range: lsp::Range {
16144                            start: lsp::Position {
16145                                line: 1,
16146                                character: 2,
16147                            },
16148                            end: lsp::Position {
16149                                line: 1,
16150                                character: 3,
16151                            },
16152                        },
16153                        new_text: "unsafe".to_string(),
16154                    })),
16155                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16156                    ..Default::default()
16157                },
16158            ])))
16159        });
16160    cx.set_state("fn a() {}\n");
16161    cx.executor().run_until_parked();
16162    cx.update_editor(|editor, window, cx| {
16163        editor.trigger_completion_on_input("n", true, window, cx)
16164    });
16165    cx.executor().run_until_parked();
16166
16167    cx.update_editor(|editor, window, cx| {
16168        editor.confirm_completion(&Default::default(), window, cx)
16169    });
16170    cx.executor().run_until_parked();
16171    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16172}
16173
16174#[gpui::test]
16175async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16176    init_test(cx, |_| {});
16177    let language =
16178        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16179    let mut cx = EditorLspTestContext::new(
16180        language,
16181        lsp::ServerCapabilities {
16182            completion_provider: Some(lsp::CompletionOptions {
16183                ..lsp::CompletionOptions::default()
16184            }),
16185            ..lsp::ServerCapabilities::default()
16186        },
16187        cx,
16188    )
16189    .await;
16190
16191    cx.set_state(
16192        "#ifndef BAR_H
16193#define BAR_H
16194
16195#include <stdbool.h>
16196
16197int fn_branch(bool do_branch1, bool do_branch2);
16198
16199#endif // BAR_H
16200ˇ",
16201    );
16202    cx.executor().run_until_parked();
16203    cx.update_editor(|editor, window, cx| {
16204        editor.handle_input("#", window, cx);
16205    });
16206    cx.executor().run_until_parked();
16207    cx.update_editor(|editor, window, cx| {
16208        editor.handle_input("i", window, cx);
16209    });
16210    cx.executor().run_until_parked();
16211    cx.update_editor(|editor, window, cx| {
16212        editor.handle_input("n", window, cx);
16213    });
16214    cx.executor().run_until_parked();
16215    cx.assert_editor_state(
16216        "#ifndef BAR_H
16217#define BAR_H
16218
16219#include <stdbool.h>
16220
16221int fn_branch(bool do_branch1, bool do_branch2);
16222
16223#endif // BAR_H
16224#inˇ",
16225    );
16226
16227    cx.lsp
16228        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16229            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16230                is_incomplete: false,
16231                item_defaults: None,
16232                items: vec![lsp::CompletionItem {
16233                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16234                    label_details: Some(lsp::CompletionItemLabelDetails {
16235                        detail: Some("header".to_string()),
16236                        description: None,
16237                    }),
16238                    label: " include".to_string(),
16239                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16240                        range: lsp::Range {
16241                            start: lsp::Position {
16242                                line: 8,
16243                                character: 1,
16244                            },
16245                            end: lsp::Position {
16246                                line: 8,
16247                                character: 1,
16248                            },
16249                        },
16250                        new_text: "include \"$0\"".to_string(),
16251                    })),
16252                    sort_text: Some("40b67681include".to_string()),
16253                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16254                    filter_text: Some("include".to_string()),
16255                    insert_text: Some("include \"$0\"".to_string()),
16256                    ..lsp::CompletionItem::default()
16257                }],
16258            })))
16259        });
16260    cx.update_editor(|editor, window, cx| {
16261        editor.show_completions(&ShowCompletions, window, cx);
16262    });
16263    cx.executor().run_until_parked();
16264    cx.update_editor(|editor, window, cx| {
16265        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16266    });
16267    cx.executor().run_until_parked();
16268    cx.assert_editor_state(
16269        "#ifndef BAR_H
16270#define BAR_H
16271
16272#include <stdbool.h>
16273
16274int fn_branch(bool do_branch1, bool do_branch2);
16275
16276#endif // BAR_H
16277#include \"ˇ\"",
16278    );
16279
16280    cx.lsp
16281        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16282            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16283                is_incomplete: true,
16284                item_defaults: None,
16285                items: vec![lsp::CompletionItem {
16286                    kind: Some(lsp::CompletionItemKind::FILE),
16287                    label: "AGL/".to_string(),
16288                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16289                        range: lsp::Range {
16290                            start: lsp::Position {
16291                                line: 8,
16292                                character: 10,
16293                            },
16294                            end: lsp::Position {
16295                                line: 8,
16296                                character: 11,
16297                            },
16298                        },
16299                        new_text: "AGL/".to_string(),
16300                    })),
16301                    sort_text: Some("40b67681AGL/".to_string()),
16302                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16303                    filter_text: Some("AGL/".to_string()),
16304                    insert_text: Some("AGL/".to_string()),
16305                    ..lsp::CompletionItem::default()
16306                }],
16307            })))
16308        });
16309    cx.update_editor(|editor, window, cx| {
16310        editor.show_completions(&ShowCompletions, window, cx);
16311    });
16312    cx.executor().run_until_parked();
16313    cx.update_editor(|editor, window, cx| {
16314        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16315    });
16316    cx.executor().run_until_parked();
16317    cx.assert_editor_state(
16318        r##"#ifndef BAR_H
16319#define BAR_H
16320
16321#include <stdbool.h>
16322
16323int fn_branch(bool do_branch1, bool do_branch2);
16324
16325#endif // BAR_H
16326#include "AGL/ˇ"##,
16327    );
16328
16329    cx.update_editor(|editor, window, cx| {
16330        editor.handle_input("\"", window, cx);
16331    });
16332    cx.executor().run_until_parked();
16333    cx.assert_editor_state(
16334        r##"#ifndef BAR_H
16335#define BAR_H
16336
16337#include <stdbool.h>
16338
16339int fn_branch(bool do_branch1, bool do_branch2);
16340
16341#endif // BAR_H
16342#include "AGL/"ˇ"##,
16343    );
16344}
16345
16346#[gpui::test]
16347async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16348    init_test(cx, |_| {});
16349
16350    let mut cx = EditorLspTestContext::new_rust(
16351        lsp::ServerCapabilities {
16352            completion_provider: Some(lsp::CompletionOptions {
16353                trigger_characters: Some(vec![".".to_string()]),
16354                resolve_provider: Some(true),
16355                ..Default::default()
16356            }),
16357            ..Default::default()
16358        },
16359        cx,
16360    )
16361    .await;
16362
16363    cx.set_state("fn main() { let a = 2ˇ; }");
16364    cx.simulate_keystroke(".");
16365    let completion_item = lsp::CompletionItem {
16366        label: "Some".into(),
16367        kind: Some(lsp::CompletionItemKind::SNIPPET),
16368        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16369        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16370            kind: lsp::MarkupKind::Markdown,
16371            value: "```rust\nSome(2)\n```".to_string(),
16372        })),
16373        deprecated: Some(false),
16374        sort_text: Some("Some".to_string()),
16375        filter_text: Some("Some".to_string()),
16376        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16377        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16378            range: lsp::Range {
16379                start: lsp::Position {
16380                    line: 0,
16381                    character: 22,
16382                },
16383                end: lsp::Position {
16384                    line: 0,
16385                    character: 22,
16386                },
16387            },
16388            new_text: "Some(2)".to_string(),
16389        })),
16390        additional_text_edits: Some(vec![lsp::TextEdit {
16391            range: lsp::Range {
16392                start: lsp::Position {
16393                    line: 0,
16394                    character: 20,
16395                },
16396                end: lsp::Position {
16397                    line: 0,
16398                    character: 22,
16399                },
16400            },
16401            new_text: "".to_string(),
16402        }]),
16403        ..Default::default()
16404    };
16405
16406    let closure_completion_item = completion_item.clone();
16407    let counter = Arc::new(AtomicUsize::new(0));
16408    let counter_clone = counter.clone();
16409    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16410        let task_completion_item = closure_completion_item.clone();
16411        counter_clone.fetch_add(1, atomic::Ordering::Release);
16412        async move {
16413            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16414                is_incomplete: true,
16415                item_defaults: None,
16416                items: vec![task_completion_item],
16417            })))
16418        }
16419    });
16420
16421    cx.condition(|editor, _| editor.context_menu_visible())
16422        .await;
16423    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16424    assert!(request.next().await.is_some());
16425    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16426
16427    cx.simulate_keystrokes("S o m");
16428    cx.condition(|editor, _| editor.context_menu_visible())
16429        .await;
16430    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16431    assert!(request.next().await.is_some());
16432    assert!(request.next().await.is_some());
16433    assert!(request.next().await.is_some());
16434    request.close();
16435    assert!(request.next().await.is_none());
16436    assert_eq!(
16437        counter.load(atomic::Ordering::Acquire),
16438        4,
16439        "With the completions menu open, only one LSP request should happen per input"
16440    );
16441}
16442
16443#[gpui::test]
16444async fn test_toggle_comment(cx: &mut TestAppContext) {
16445    init_test(cx, |_| {});
16446    let mut cx = EditorTestContext::new(cx).await;
16447    let language = Arc::new(Language::new(
16448        LanguageConfig {
16449            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16450            ..Default::default()
16451        },
16452        Some(tree_sitter_rust::LANGUAGE.into()),
16453    ));
16454    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16455
16456    // If multiple selections intersect a line, the line is only toggled once.
16457    cx.set_state(indoc! {"
16458        fn a() {
16459            «//b();
16460            ˇ»// «c();
16461            //ˇ»  d();
16462        }
16463    "});
16464
16465    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16466
16467    cx.assert_editor_state(indoc! {"
16468        fn a() {
16469            «b();
16470            ˇ»«c();
16471            ˇ» d();
16472        }
16473    "});
16474
16475    // The comment prefix is inserted at the same column for every line in a
16476    // selection.
16477    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16478
16479    cx.assert_editor_state(indoc! {"
16480        fn a() {
16481            // «b();
16482            ˇ»// «c();
16483            ˇ» // d();
16484        }
16485    "});
16486
16487    // If a selection ends at the beginning of a line, that line is not toggled.
16488    cx.set_selections_state(indoc! {"
16489        fn a() {
16490            // b();
16491            «// c();
16492        ˇ»     // d();
16493        }
16494    "});
16495
16496    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16497
16498    cx.assert_editor_state(indoc! {"
16499        fn a() {
16500            // b();
16501            «c();
16502        ˇ»     // d();
16503        }
16504    "});
16505
16506    // If a selection span a single line and is empty, the line is toggled.
16507    cx.set_state(indoc! {"
16508        fn a() {
16509            a();
16510            b();
16511        ˇ
16512        }
16513    "});
16514
16515    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16516
16517    cx.assert_editor_state(indoc! {"
16518        fn a() {
16519            a();
16520            b();
16521        //•ˇ
16522        }
16523    "});
16524
16525    // If a selection span multiple lines, empty lines are not toggled.
16526    cx.set_state(indoc! {"
16527        fn a() {
16528            «a();
16529
16530            c();ˇ»
16531        }
16532    "});
16533
16534    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16535
16536    cx.assert_editor_state(indoc! {"
16537        fn a() {
16538            // «a();
16539
16540            // c();ˇ»
16541        }
16542    "});
16543
16544    // If a selection includes multiple comment prefixes, all lines are uncommented.
16545    cx.set_state(indoc! {"
16546        fn a() {
16547            «// a();
16548            /// b();
16549            //! c();ˇ»
16550        }
16551    "});
16552
16553    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16554
16555    cx.assert_editor_state(indoc! {"
16556        fn a() {
16557            «a();
16558            b();
16559            c();ˇ»
16560        }
16561    "});
16562}
16563
16564#[gpui::test]
16565async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16566    init_test(cx, |_| {});
16567    let mut cx = EditorTestContext::new(cx).await;
16568    let language = Arc::new(Language::new(
16569        LanguageConfig {
16570            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16571            ..Default::default()
16572        },
16573        Some(tree_sitter_rust::LANGUAGE.into()),
16574    ));
16575    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16576
16577    let toggle_comments = &ToggleComments {
16578        advance_downwards: false,
16579        ignore_indent: true,
16580    };
16581
16582    // If multiple selections intersect a line, the line is only toggled once.
16583    cx.set_state(indoc! {"
16584        fn a() {
16585        //    «b();
16586        //    c();
16587        //    ˇ» d();
16588        }
16589    "});
16590
16591    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16592
16593    cx.assert_editor_state(indoc! {"
16594        fn a() {
16595            «b();
16596            c();
16597            ˇ» d();
16598        }
16599    "});
16600
16601    // The comment prefix is inserted at the beginning of each line
16602    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16603
16604    cx.assert_editor_state(indoc! {"
16605        fn a() {
16606        //    «b();
16607        //    c();
16608        //    ˇ» d();
16609        }
16610    "});
16611
16612    // If a selection ends at the beginning of a line, that line is not toggled.
16613    cx.set_selections_state(indoc! {"
16614        fn a() {
16615        //    b();
16616        //    «c();
16617        ˇ»//     d();
16618        }
16619    "});
16620
16621    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16622
16623    cx.assert_editor_state(indoc! {"
16624        fn a() {
16625        //    b();
16626            «c();
16627        ˇ»//     d();
16628        }
16629    "});
16630
16631    // If a selection span a single line and is empty, the line is toggled.
16632    cx.set_state(indoc! {"
16633        fn a() {
16634            a();
16635            b();
16636        ˇ
16637        }
16638    "});
16639
16640    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16641
16642    cx.assert_editor_state(indoc! {"
16643        fn a() {
16644            a();
16645            b();
16646        //ˇ
16647        }
16648    "});
16649
16650    // If a selection span multiple lines, empty lines are not toggled.
16651    cx.set_state(indoc! {"
16652        fn a() {
16653            «a();
16654
16655            c();ˇ»
16656        }
16657    "});
16658
16659    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16660
16661    cx.assert_editor_state(indoc! {"
16662        fn a() {
16663        //    «a();
16664
16665        //    c();ˇ»
16666        }
16667    "});
16668
16669    // If a selection includes multiple comment prefixes, all lines are uncommented.
16670    cx.set_state(indoc! {"
16671        fn a() {
16672        //    «a();
16673        ///    b();
16674        //!    c();ˇ»
16675        }
16676    "});
16677
16678    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16679
16680    cx.assert_editor_state(indoc! {"
16681        fn a() {
16682            «a();
16683            b();
16684            c();ˇ»
16685        }
16686    "});
16687}
16688
16689#[gpui::test]
16690async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16691    init_test(cx, |_| {});
16692
16693    let language = Arc::new(Language::new(
16694        LanguageConfig {
16695            line_comments: vec!["// ".into()],
16696            ..Default::default()
16697        },
16698        Some(tree_sitter_rust::LANGUAGE.into()),
16699    ));
16700
16701    let mut cx = EditorTestContext::new(cx).await;
16702
16703    cx.language_registry().add(language.clone());
16704    cx.update_buffer(|buffer, cx| {
16705        buffer.set_language(Some(language), cx);
16706    });
16707
16708    let toggle_comments = &ToggleComments {
16709        advance_downwards: true,
16710        ignore_indent: false,
16711    };
16712
16713    // Single cursor on one line -> advance
16714    // Cursor moves horizontally 3 characters as well on non-blank line
16715    cx.set_state(indoc!(
16716        "fn a() {
16717             ˇdog();
16718             cat();
16719        }"
16720    ));
16721    cx.update_editor(|editor, window, cx| {
16722        editor.toggle_comments(toggle_comments, window, cx);
16723    });
16724    cx.assert_editor_state(indoc!(
16725        "fn a() {
16726             // dog();
16727             catˇ();
16728        }"
16729    ));
16730
16731    // Single selection on one line -> don't advance
16732    cx.set_state(indoc!(
16733        "fn a() {
16734             «dog()ˇ»;
16735             cat();
16736        }"
16737    ));
16738    cx.update_editor(|editor, window, cx| {
16739        editor.toggle_comments(toggle_comments, window, cx);
16740    });
16741    cx.assert_editor_state(indoc!(
16742        "fn a() {
16743             // «dog()ˇ»;
16744             cat();
16745        }"
16746    ));
16747
16748    // Multiple cursors on one line -> advance
16749    cx.set_state(indoc!(
16750        "fn a() {
16751             ˇdˇog();
16752             cat();
16753        }"
16754    ));
16755    cx.update_editor(|editor, window, cx| {
16756        editor.toggle_comments(toggle_comments, window, cx);
16757    });
16758    cx.assert_editor_state(indoc!(
16759        "fn a() {
16760             // dog();
16761             catˇ(ˇ);
16762        }"
16763    ));
16764
16765    // Multiple cursors on one line, with selection -> don't advance
16766    cx.set_state(indoc!(
16767        "fn a() {
16768             ˇdˇog«()ˇ»;
16769             cat();
16770        }"
16771    ));
16772    cx.update_editor(|editor, window, cx| {
16773        editor.toggle_comments(toggle_comments, window, cx);
16774    });
16775    cx.assert_editor_state(indoc!(
16776        "fn a() {
16777             // ˇdˇog«()ˇ»;
16778             cat();
16779        }"
16780    ));
16781
16782    // Single cursor on one line -> advance
16783    // Cursor moves to column 0 on blank line
16784    cx.set_state(indoc!(
16785        "fn a() {
16786             ˇdog();
16787
16788             cat();
16789        }"
16790    ));
16791    cx.update_editor(|editor, window, cx| {
16792        editor.toggle_comments(toggle_comments, window, cx);
16793    });
16794    cx.assert_editor_state(indoc!(
16795        "fn a() {
16796             // dog();
16797        ˇ
16798             cat();
16799        }"
16800    ));
16801
16802    // Single cursor on one line -> advance
16803    // Cursor starts and ends at column 0
16804    cx.set_state(indoc!(
16805        "fn a() {
16806         ˇ    dog();
16807             cat();
16808        }"
16809    ));
16810    cx.update_editor(|editor, window, cx| {
16811        editor.toggle_comments(toggle_comments, window, cx);
16812    });
16813    cx.assert_editor_state(indoc!(
16814        "fn a() {
16815             // dog();
16816         ˇ    cat();
16817        }"
16818    ));
16819}
16820
16821#[gpui::test]
16822async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16823    init_test(cx, |_| {});
16824
16825    let mut cx = EditorTestContext::new(cx).await;
16826
16827    let html_language = Arc::new(
16828        Language::new(
16829            LanguageConfig {
16830                name: "HTML".into(),
16831                block_comment: Some(BlockCommentConfig {
16832                    start: "<!-- ".into(),
16833                    prefix: "".into(),
16834                    end: " -->".into(),
16835                    tab_size: 0,
16836                }),
16837                ..Default::default()
16838            },
16839            Some(tree_sitter_html::LANGUAGE.into()),
16840        )
16841        .with_injection_query(
16842            r#"
16843            (script_element
16844                (raw_text) @injection.content
16845                (#set! injection.language "javascript"))
16846            "#,
16847        )
16848        .unwrap(),
16849    );
16850
16851    let javascript_language = Arc::new(Language::new(
16852        LanguageConfig {
16853            name: "JavaScript".into(),
16854            line_comments: vec!["// ".into()],
16855            ..Default::default()
16856        },
16857        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16858    ));
16859
16860    cx.language_registry().add(html_language.clone());
16861    cx.language_registry().add(javascript_language);
16862    cx.update_buffer(|buffer, cx| {
16863        buffer.set_language(Some(html_language), cx);
16864    });
16865
16866    // Toggle comments for empty selections
16867    cx.set_state(
16868        &r#"
16869            <p>A</p>ˇ
16870            <p>B</p>ˇ
16871            <p>C</p>ˇ
16872        "#
16873        .unindent(),
16874    );
16875    cx.update_editor(|editor, window, cx| {
16876        editor.toggle_comments(&ToggleComments::default(), window, cx)
16877    });
16878    cx.assert_editor_state(
16879        &r#"
16880            <!-- <p>A</p>ˇ -->
16881            <!-- <p>B</p>ˇ -->
16882            <!-- <p>C</p>ˇ -->
16883        "#
16884        .unindent(),
16885    );
16886    cx.update_editor(|editor, window, cx| {
16887        editor.toggle_comments(&ToggleComments::default(), window, cx)
16888    });
16889    cx.assert_editor_state(
16890        &r#"
16891            <p>A</p>ˇ
16892            <p>B</p>ˇ
16893            <p>C</p>ˇ
16894        "#
16895        .unindent(),
16896    );
16897
16898    // Toggle comments for mixture of empty and non-empty selections, where
16899    // multiple selections occupy a given line.
16900    cx.set_state(
16901        &r#"
16902            <p>A«</p>
16903            <p>ˇ»B</p>ˇ
16904            <p>C«</p>
16905            <p>ˇ»D</p>ˇ
16906        "#
16907        .unindent(),
16908    );
16909
16910    cx.update_editor(|editor, window, cx| {
16911        editor.toggle_comments(&ToggleComments::default(), window, cx)
16912    });
16913    cx.assert_editor_state(
16914        &r#"
16915            <!-- <p>A«</p>
16916            <p>ˇ»B</p>ˇ -->
16917            <!-- <p>C«</p>
16918            <p>ˇ»D</p>ˇ -->
16919        "#
16920        .unindent(),
16921    );
16922    cx.update_editor(|editor, window, cx| {
16923        editor.toggle_comments(&ToggleComments::default(), window, cx)
16924    });
16925    cx.assert_editor_state(
16926        &r#"
16927            <p>A«</p>
16928            <p>ˇ»B</p>ˇ
16929            <p>C«</p>
16930            <p>ˇ»D</p>ˇ
16931        "#
16932        .unindent(),
16933    );
16934
16935    // Toggle comments when different languages are active for different
16936    // selections.
16937    cx.set_state(
16938        &r#"
16939            ˇ<script>
16940                ˇvar x = new Y();
16941            ˇ</script>
16942        "#
16943        .unindent(),
16944    );
16945    cx.executor().run_until_parked();
16946    cx.update_editor(|editor, window, cx| {
16947        editor.toggle_comments(&ToggleComments::default(), window, cx)
16948    });
16949    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16950    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16951    cx.assert_editor_state(
16952        &r#"
16953            <!-- ˇ<script> -->
16954                // ˇvar x = new Y();
16955            <!-- ˇ</script> -->
16956        "#
16957        .unindent(),
16958    );
16959}
16960
16961#[gpui::test]
16962fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16963    init_test(cx, |_| {});
16964
16965    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16966    let multibuffer = cx.new(|cx| {
16967        let mut multibuffer = MultiBuffer::new(ReadWrite);
16968        multibuffer.push_excerpts(
16969            buffer.clone(),
16970            [
16971                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16972                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16973            ],
16974            cx,
16975        );
16976        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16977        multibuffer
16978    });
16979
16980    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16981    editor.update_in(cx, |editor, window, cx| {
16982        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16984            s.select_ranges([
16985                Point::new(0, 0)..Point::new(0, 0),
16986                Point::new(1, 0)..Point::new(1, 0),
16987            ])
16988        });
16989
16990        editor.handle_input("X", window, cx);
16991        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16992        assert_eq!(
16993            editor.selections.ranges(&editor.display_snapshot(cx)),
16994            [
16995                Point::new(0, 1)..Point::new(0, 1),
16996                Point::new(1, 1)..Point::new(1, 1),
16997            ]
16998        );
16999
17000        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17001        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17002            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17003        });
17004        editor.backspace(&Default::default(), window, cx);
17005        assert_eq!(editor.text(cx), "Xa\nbbb");
17006        assert_eq!(
17007            editor.selections.ranges(&editor.display_snapshot(cx)),
17008            [Point::new(1, 0)..Point::new(1, 0)]
17009        );
17010
17011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17012            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17013        });
17014        editor.backspace(&Default::default(), window, cx);
17015        assert_eq!(editor.text(cx), "X\nbb");
17016        assert_eq!(
17017            editor.selections.ranges(&editor.display_snapshot(cx)),
17018            [Point::new(0, 1)..Point::new(0, 1)]
17019        );
17020    });
17021}
17022
17023#[gpui::test]
17024fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17025    init_test(cx, |_| {});
17026
17027    let markers = vec![('[', ']').into(), ('(', ')').into()];
17028    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17029        indoc! {"
17030            [aaaa
17031            (bbbb]
17032            cccc)",
17033        },
17034        markers.clone(),
17035    );
17036    let excerpt_ranges = markers.into_iter().map(|marker| {
17037        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17038        ExcerptRange::new(context)
17039    });
17040    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17041    let multibuffer = cx.new(|cx| {
17042        let mut multibuffer = MultiBuffer::new(ReadWrite);
17043        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17044        multibuffer
17045    });
17046
17047    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17048    editor.update_in(cx, |editor, window, cx| {
17049        let (expected_text, selection_ranges) = marked_text_ranges(
17050            indoc! {"
17051                aaaa
17052                bˇbbb
17053                bˇbbˇb
17054                cccc"
17055            },
17056            true,
17057        );
17058        assert_eq!(editor.text(cx), expected_text);
17059        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17060            s.select_ranges(
17061                selection_ranges
17062                    .iter()
17063                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17064            )
17065        });
17066
17067        editor.handle_input("X", window, cx);
17068
17069        let (expected_text, expected_selections) = marked_text_ranges(
17070            indoc! {"
17071                aaaa
17072                bXˇbbXb
17073                bXˇbbXˇb
17074                cccc"
17075            },
17076            false,
17077        );
17078        assert_eq!(editor.text(cx), expected_text);
17079        assert_eq!(
17080            editor.selections.ranges(&editor.display_snapshot(cx)),
17081            expected_selections
17082                .iter()
17083                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17084                .collect::<Vec<_>>()
17085        );
17086
17087        editor.newline(&Newline, window, cx);
17088        let (expected_text, expected_selections) = marked_text_ranges(
17089            indoc! {"
17090                aaaa
17091                bX
17092                ˇbbX
17093                b
17094                bX
17095                ˇbbX
17096                ˇb
17097                cccc"
17098            },
17099            false,
17100        );
17101        assert_eq!(editor.text(cx), expected_text);
17102        assert_eq!(
17103            editor.selections.ranges(&editor.display_snapshot(cx)),
17104            expected_selections
17105                .iter()
17106                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17107                .collect::<Vec<_>>()
17108        );
17109    });
17110}
17111
17112#[gpui::test]
17113fn test_refresh_selections(cx: &mut TestAppContext) {
17114    init_test(cx, |_| {});
17115
17116    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17117    let mut excerpt1_id = None;
17118    let multibuffer = cx.new(|cx| {
17119        let mut multibuffer = MultiBuffer::new(ReadWrite);
17120        excerpt1_id = multibuffer
17121            .push_excerpts(
17122                buffer.clone(),
17123                [
17124                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17125                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17126                ],
17127                cx,
17128            )
17129            .into_iter()
17130            .next();
17131        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17132        multibuffer
17133    });
17134
17135    let editor = cx.add_window(|window, cx| {
17136        let mut editor = build_editor(multibuffer.clone(), window, cx);
17137        let snapshot = editor.snapshot(window, cx);
17138        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17139            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17140        });
17141        editor.begin_selection(
17142            Point::new(2, 1).to_display_point(&snapshot),
17143            true,
17144            1,
17145            window,
17146            cx,
17147        );
17148        assert_eq!(
17149            editor.selections.ranges(&editor.display_snapshot(cx)),
17150            [
17151                Point::new(1, 3)..Point::new(1, 3),
17152                Point::new(2, 1)..Point::new(2, 1),
17153            ]
17154        );
17155        editor
17156    });
17157
17158    // Refreshing selections is a no-op when excerpts haven't changed.
17159    _ = editor.update(cx, |editor, window, cx| {
17160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17161        assert_eq!(
17162            editor.selections.ranges(&editor.display_snapshot(cx)),
17163            [
17164                Point::new(1, 3)..Point::new(1, 3),
17165                Point::new(2, 1)..Point::new(2, 1),
17166            ]
17167        );
17168    });
17169
17170    multibuffer.update(cx, |multibuffer, cx| {
17171        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17172    });
17173    _ = editor.update(cx, |editor, window, cx| {
17174        // Removing an excerpt causes the first selection to become degenerate.
17175        assert_eq!(
17176            editor.selections.ranges(&editor.display_snapshot(cx)),
17177            [
17178                Point::new(0, 0)..Point::new(0, 0),
17179                Point::new(0, 1)..Point::new(0, 1)
17180            ]
17181        );
17182
17183        // Refreshing selections will relocate the first selection to the original buffer
17184        // location.
17185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17186        assert_eq!(
17187            editor.selections.ranges(&editor.display_snapshot(cx)),
17188            [
17189                Point::new(0, 1)..Point::new(0, 1),
17190                Point::new(0, 3)..Point::new(0, 3)
17191            ]
17192        );
17193        assert!(editor.selections.pending_anchor().is_some());
17194    });
17195}
17196
17197#[gpui::test]
17198fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17199    init_test(cx, |_| {});
17200
17201    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17202    let mut excerpt1_id = None;
17203    let multibuffer = cx.new(|cx| {
17204        let mut multibuffer = MultiBuffer::new(ReadWrite);
17205        excerpt1_id = multibuffer
17206            .push_excerpts(
17207                buffer.clone(),
17208                [
17209                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17210                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17211                ],
17212                cx,
17213            )
17214            .into_iter()
17215            .next();
17216        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17217        multibuffer
17218    });
17219
17220    let editor = cx.add_window(|window, cx| {
17221        let mut editor = build_editor(multibuffer.clone(), window, cx);
17222        let snapshot = editor.snapshot(window, cx);
17223        editor.begin_selection(
17224            Point::new(1, 3).to_display_point(&snapshot),
17225            false,
17226            1,
17227            window,
17228            cx,
17229        );
17230        assert_eq!(
17231            editor.selections.ranges(&editor.display_snapshot(cx)),
17232            [Point::new(1, 3)..Point::new(1, 3)]
17233        );
17234        editor
17235    });
17236
17237    multibuffer.update(cx, |multibuffer, cx| {
17238        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17239    });
17240    _ = editor.update(cx, |editor, window, cx| {
17241        assert_eq!(
17242            editor.selections.ranges(&editor.display_snapshot(cx)),
17243            [Point::new(0, 0)..Point::new(0, 0)]
17244        );
17245
17246        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17248        assert_eq!(
17249            editor.selections.ranges(&editor.display_snapshot(cx)),
17250            [Point::new(0, 3)..Point::new(0, 3)]
17251        );
17252        assert!(editor.selections.pending_anchor().is_some());
17253    });
17254}
17255
17256#[gpui::test]
17257async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17258    init_test(cx, |_| {});
17259
17260    let language = Arc::new(
17261        Language::new(
17262            LanguageConfig {
17263                brackets: BracketPairConfig {
17264                    pairs: vec![
17265                        BracketPair {
17266                            start: "{".to_string(),
17267                            end: "}".to_string(),
17268                            close: true,
17269                            surround: true,
17270                            newline: true,
17271                        },
17272                        BracketPair {
17273                            start: "/* ".to_string(),
17274                            end: " */".to_string(),
17275                            close: true,
17276                            surround: true,
17277                            newline: true,
17278                        },
17279                    ],
17280                    ..Default::default()
17281                },
17282                ..Default::default()
17283            },
17284            Some(tree_sitter_rust::LANGUAGE.into()),
17285        )
17286        .with_indents_query("")
17287        .unwrap(),
17288    );
17289
17290    let text = concat!(
17291        "{   }\n",     //
17292        "  x\n",       //
17293        "  /*   */\n", //
17294        "x\n",         //
17295        "{{} }\n",     //
17296    );
17297
17298    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17299    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17300    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17301    editor
17302        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17303        .await;
17304
17305    editor.update_in(cx, |editor, window, cx| {
17306        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17307            s.select_display_ranges([
17308                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17309                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17310                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17311            ])
17312        });
17313        editor.newline(&Newline, window, cx);
17314
17315        assert_eq!(
17316            editor.buffer().read(cx).read(cx).text(),
17317            concat!(
17318                "{ \n",    // Suppress rustfmt
17319                "\n",      //
17320                "}\n",     //
17321                "  x\n",   //
17322                "  /* \n", //
17323                "  \n",    //
17324                "  */\n",  //
17325                "x\n",     //
17326                "{{} \n",  //
17327                "}\n",     //
17328            )
17329        );
17330    });
17331}
17332
17333#[gpui::test]
17334fn test_highlighted_ranges(cx: &mut TestAppContext) {
17335    init_test(cx, |_| {});
17336
17337    let editor = cx.add_window(|window, cx| {
17338        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17339        build_editor(buffer, window, cx)
17340    });
17341
17342    _ = editor.update(cx, |editor, window, cx| {
17343        struct Type1;
17344        struct Type2;
17345
17346        let buffer = editor.buffer.read(cx).snapshot(cx);
17347
17348        let anchor_range =
17349            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17350
17351        editor.highlight_background::<Type1>(
17352            &[
17353                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17354                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17355                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17356                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17357            ],
17358            |_, _| Hsla::red(),
17359            cx,
17360        );
17361        editor.highlight_background::<Type2>(
17362            &[
17363                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17364                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17365                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17366                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17367            ],
17368            |_, _| Hsla::green(),
17369            cx,
17370        );
17371
17372        let snapshot = editor.snapshot(window, cx);
17373        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17374            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17375            &snapshot,
17376            cx.theme(),
17377        );
17378        assert_eq!(
17379            highlighted_ranges,
17380            &[
17381                (
17382                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17383                    Hsla::green(),
17384                ),
17385                (
17386                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17387                    Hsla::red(),
17388                ),
17389                (
17390                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17391                    Hsla::green(),
17392                ),
17393                (
17394                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17395                    Hsla::red(),
17396                ),
17397            ]
17398        );
17399        assert_eq!(
17400            editor.sorted_background_highlights_in_range(
17401                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17402                &snapshot,
17403                cx.theme(),
17404            ),
17405            &[(
17406                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17407                Hsla::red(),
17408            )]
17409        );
17410    });
17411}
17412
17413#[gpui::test]
17414async fn test_following(cx: &mut TestAppContext) {
17415    init_test(cx, |_| {});
17416
17417    let fs = FakeFs::new(cx.executor());
17418    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17419
17420    let buffer = project.update(cx, |project, cx| {
17421        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17422        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17423    });
17424    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17425    let follower = cx.update(|cx| {
17426        cx.open_window(
17427            WindowOptions {
17428                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17429                    gpui::Point::new(px(0.), px(0.)),
17430                    gpui::Point::new(px(10.), px(80.)),
17431                ))),
17432                ..Default::default()
17433            },
17434            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17435        )
17436        .unwrap()
17437    });
17438
17439    let is_still_following = Rc::new(RefCell::new(true));
17440    let follower_edit_event_count = Rc::new(RefCell::new(0));
17441    let pending_update = Rc::new(RefCell::new(None));
17442    let leader_entity = leader.root(cx).unwrap();
17443    let follower_entity = follower.root(cx).unwrap();
17444    _ = follower.update(cx, {
17445        let update = pending_update.clone();
17446        let is_still_following = is_still_following.clone();
17447        let follower_edit_event_count = follower_edit_event_count.clone();
17448        |_, window, cx| {
17449            cx.subscribe_in(
17450                &leader_entity,
17451                window,
17452                move |_, leader, event, window, cx| {
17453                    leader.read(cx).add_event_to_update_proto(
17454                        event,
17455                        &mut update.borrow_mut(),
17456                        window,
17457                        cx,
17458                    );
17459                },
17460            )
17461            .detach();
17462
17463            cx.subscribe_in(
17464                &follower_entity,
17465                window,
17466                move |_, _, event: &EditorEvent, _window, _cx| {
17467                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17468                        *is_still_following.borrow_mut() = false;
17469                    }
17470
17471                    if let EditorEvent::BufferEdited = event {
17472                        *follower_edit_event_count.borrow_mut() += 1;
17473                    }
17474                },
17475            )
17476            .detach();
17477        }
17478    });
17479
17480    // Update the selections only
17481    _ = leader.update(cx, |leader, window, cx| {
17482        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17483            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17484        });
17485    });
17486    follower
17487        .update(cx, |follower, window, cx| {
17488            follower.apply_update_proto(
17489                &project,
17490                pending_update.borrow_mut().take().unwrap(),
17491                window,
17492                cx,
17493            )
17494        })
17495        .unwrap()
17496        .await
17497        .unwrap();
17498    _ = follower.update(cx, |follower, _, cx| {
17499        assert_eq!(
17500            follower.selections.ranges(&follower.display_snapshot(cx)),
17501            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17502        );
17503    });
17504    assert!(*is_still_following.borrow());
17505    assert_eq!(*follower_edit_event_count.borrow(), 0);
17506
17507    // Update the scroll position only
17508    _ = leader.update(cx, |leader, window, cx| {
17509        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17510    });
17511    follower
17512        .update(cx, |follower, window, cx| {
17513            follower.apply_update_proto(
17514                &project,
17515                pending_update.borrow_mut().take().unwrap(),
17516                window,
17517                cx,
17518            )
17519        })
17520        .unwrap()
17521        .await
17522        .unwrap();
17523    assert_eq!(
17524        follower
17525            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17526            .unwrap(),
17527        gpui::Point::new(1.5, 3.5)
17528    );
17529    assert!(*is_still_following.borrow());
17530    assert_eq!(*follower_edit_event_count.borrow(), 0);
17531
17532    // Update the selections and scroll position. The follower's scroll position is updated
17533    // via autoscroll, not via the leader's exact scroll position.
17534    _ = leader.update(cx, |leader, window, cx| {
17535        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17536            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17537        });
17538        leader.request_autoscroll(Autoscroll::newest(), cx);
17539        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17540    });
17541    follower
17542        .update(cx, |follower, window, cx| {
17543            follower.apply_update_proto(
17544                &project,
17545                pending_update.borrow_mut().take().unwrap(),
17546                window,
17547                cx,
17548            )
17549        })
17550        .unwrap()
17551        .await
17552        .unwrap();
17553    _ = follower.update(cx, |follower, _, cx| {
17554        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17555        assert_eq!(
17556            follower.selections.ranges(&follower.display_snapshot(cx)),
17557            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17558        );
17559    });
17560    assert!(*is_still_following.borrow());
17561
17562    // Creating a pending selection that precedes another selection
17563    _ = leader.update(cx, |leader, window, cx| {
17564        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17565            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17566        });
17567        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17568    });
17569    follower
17570        .update(cx, |follower, window, cx| {
17571            follower.apply_update_proto(
17572                &project,
17573                pending_update.borrow_mut().take().unwrap(),
17574                window,
17575                cx,
17576            )
17577        })
17578        .unwrap()
17579        .await
17580        .unwrap();
17581    _ = follower.update(cx, |follower, _, cx| {
17582        assert_eq!(
17583            follower.selections.ranges(&follower.display_snapshot(cx)),
17584            vec![
17585                MultiBufferOffset(0)..MultiBufferOffset(0),
17586                MultiBufferOffset(1)..MultiBufferOffset(1)
17587            ]
17588        );
17589    });
17590    assert!(*is_still_following.borrow());
17591
17592    // Extend the pending selection so that it surrounds another selection
17593    _ = leader.update(cx, |leader, window, cx| {
17594        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17595    });
17596    follower
17597        .update(cx, |follower, window, cx| {
17598            follower.apply_update_proto(
17599                &project,
17600                pending_update.borrow_mut().take().unwrap(),
17601                window,
17602                cx,
17603            )
17604        })
17605        .unwrap()
17606        .await
17607        .unwrap();
17608    _ = follower.update(cx, |follower, _, cx| {
17609        assert_eq!(
17610            follower.selections.ranges(&follower.display_snapshot(cx)),
17611            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17612        );
17613    });
17614
17615    // Scrolling locally breaks the follow
17616    _ = follower.update(cx, |follower, window, cx| {
17617        let top_anchor = follower
17618            .buffer()
17619            .read(cx)
17620            .read(cx)
17621            .anchor_after(MultiBufferOffset(0));
17622        follower.set_scroll_anchor(
17623            ScrollAnchor {
17624                anchor: top_anchor,
17625                offset: gpui::Point::new(0.0, 0.5),
17626            },
17627            window,
17628            cx,
17629        );
17630    });
17631    assert!(!(*is_still_following.borrow()));
17632}
17633
17634#[gpui::test]
17635async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17636    init_test(cx, |_| {});
17637
17638    let fs = FakeFs::new(cx.executor());
17639    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17640    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17641    let pane = workspace
17642        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17643        .unwrap();
17644
17645    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17646
17647    let leader = pane.update_in(cx, |_, window, cx| {
17648        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17649        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17650    });
17651
17652    // Start following the editor when it has no excerpts.
17653    let mut state_message =
17654        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17655    let workspace_entity = workspace.root(cx).unwrap();
17656    let follower_1 = cx
17657        .update_window(*workspace.deref(), |_, window, cx| {
17658            Editor::from_state_proto(
17659                workspace_entity,
17660                ViewId {
17661                    creator: CollaboratorId::PeerId(PeerId::default()),
17662                    id: 0,
17663                },
17664                &mut state_message,
17665                window,
17666                cx,
17667            )
17668        })
17669        .unwrap()
17670        .unwrap()
17671        .await
17672        .unwrap();
17673
17674    let update_message = Rc::new(RefCell::new(None));
17675    follower_1.update_in(cx, {
17676        let update = update_message.clone();
17677        |_, window, cx| {
17678            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17679                leader.read(cx).add_event_to_update_proto(
17680                    event,
17681                    &mut update.borrow_mut(),
17682                    window,
17683                    cx,
17684                );
17685            })
17686            .detach();
17687        }
17688    });
17689
17690    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17691        (
17692            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17693            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17694        )
17695    });
17696
17697    // Insert some excerpts.
17698    leader.update(cx, |leader, cx| {
17699        leader.buffer.update(cx, |multibuffer, cx| {
17700            multibuffer.set_excerpts_for_path(
17701                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17702                buffer_1.clone(),
17703                vec![
17704                    Point::row_range(0..3),
17705                    Point::row_range(1..6),
17706                    Point::row_range(12..15),
17707                ],
17708                0,
17709                cx,
17710            );
17711            multibuffer.set_excerpts_for_path(
17712                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17713                buffer_2.clone(),
17714                vec![Point::row_range(0..6), Point::row_range(8..12)],
17715                0,
17716                cx,
17717            );
17718        });
17719    });
17720
17721    // Apply the update of adding the excerpts.
17722    follower_1
17723        .update_in(cx, |follower, window, cx| {
17724            follower.apply_update_proto(
17725                &project,
17726                update_message.borrow().clone().unwrap(),
17727                window,
17728                cx,
17729            )
17730        })
17731        .await
17732        .unwrap();
17733    assert_eq!(
17734        follower_1.update(cx, |editor, cx| editor.text(cx)),
17735        leader.update(cx, |editor, cx| editor.text(cx))
17736    );
17737    update_message.borrow_mut().take();
17738
17739    // Start following separately after it already has excerpts.
17740    let mut state_message =
17741        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17742    let workspace_entity = workspace.root(cx).unwrap();
17743    let follower_2 = cx
17744        .update_window(*workspace.deref(), |_, window, cx| {
17745            Editor::from_state_proto(
17746                workspace_entity,
17747                ViewId {
17748                    creator: CollaboratorId::PeerId(PeerId::default()),
17749                    id: 0,
17750                },
17751                &mut state_message,
17752                window,
17753                cx,
17754            )
17755        })
17756        .unwrap()
17757        .unwrap()
17758        .await
17759        .unwrap();
17760    assert_eq!(
17761        follower_2.update(cx, |editor, cx| editor.text(cx)),
17762        leader.update(cx, |editor, cx| editor.text(cx))
17763    );
17764
17765    // Remove some excerpts.
17766    leader.update(cx, |leader, cx| {
17767        leader.buffer.update(cx, |multibuffer, cx| {
17768            let excerpt_ids = multibuffer.excerpt_ids();
17769            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17770            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17771        });
17772    });
17773
17774    // Apply the update of removing the excerpts.
17775    follower_1
17776        .update_in(cx, |follower, window, cx| {
17777            follower.apply_update_proto(
17778                &project,
17779                update_message.borrow().clone().unwrap(),
17780                window,
17781                cx,
17782            )
17783        })
17784        .await
17785        .unwrap();
17786    follower_2
17787        .update_in(cx, |follower, window, cx| {
17788            follower.apply_update_proto(
17789                &project,
17790                update_message.borrow().clone().unwrap(),
17791                window,
17792                cx,
17793            )
17794        })
17795        .await
17796        .unwrap();
17797    update_message.borrow_mut().take();
17798    assert_eq!(
17799        follower_1.update(cx, |editor, cx| editor.text(cx)),
17800        leader.update(cx, |editor, cx| editor.text(cx))
17801    );
17802}
17803
17804#[gpui::test]
17805async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17806    init_test(cx, |_| {});
17807
17808    let mut cx = EditorTestContext::new(cx).await;
17809    let lsp_store =
17810        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17811
17812    cx.set_state(indoc! {"
17813        ˇfn func(abc def: i32) -> u32 {
17814        }
17815    "});
17816
17817    cx.update(|_, cx| {
17818        lsp_store.update(cx, |lsp_store, cx| {
17819            lsp_store
17820                .update_diagnostics(
17821                    LanguageServerId(0),
17822                    lsp::PublishDiagnosticsParams {
17823                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17824                        version: None,
17825                        diagnostics: vec![
17826                            lsp::Diagnostic {
17827                                range: lsp::Range::new(
17828                                    lsp::Position::new(0, 11),
17829                                    lsp::Position::new(0, 12),
17830                                ),
17831                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17832                                ..Default::default()
17833                            },
17834                            lsp::Diagnostic {
17835                                range: lsp::Range::new(
17836                                    lsp::Position::new(0, 12),
17837                                    lsp::Position::new(0, 15),
17838                                ),
17839                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17840                                ..Default::default()
17841                            },
17842                            lsp::Diagnostic {
17843                                range: lsp::Range::new(
17844                                    lsp::Position::new(0, 25),
17845                                    lsp::Position::new(0, 28),
17846                                ),
17847                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17848                                ..Default::default()
17849                            },
17850                        ],
17851                    },
17852                    None,
17853                    DiagnosticSourceKind::Pushed,
17854                    &[],
17855                    cx,
17856                )
17857                .unwrap()
17858        });
17859    });
17860
17861    executor.run_until_parked();
17862
17863    cx.update_editor(|editor, window, cx| {
17864        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17865    });
17866
17867    cx.assert_editor_state(indoc! {"
17868        fn func(abc def: i32) -> ˇu32 {
17869        }
17870    "});
17871
17872    cx.update_editor(|editor, window, cx| {
17873        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17874    });
17875
17876    cx.assert_editor_state(indoc! {"
17877        fn func(abc ˇdef: i32) -> u32 {
17878        }
17879    "});
17880
17881    cx.update_editor(|editor, window, cx| {
17882        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17883    });
17884
17885    cx.assert_editor_state(indoc! {"
17886        fn func(abcˇ def: i32) -> u32 {
17887        }
17888    "});
17889
17890    cx.update_editor(|editor, window, cx| {
17891        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17892    });
17893
17894    cx.assert_editor_state(indoc! {"
17895        fn func(abc def: i32) -> ˇu32 {
17896        }
17897    "});
17898}
17899
17900#[gpui::test]
17901async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17902    init_test(cx, |_| {});
17903
17904    let mut cx = EditorTestContext::new(cx).await;
17905
17906    let diff_base = r#"
17907        use some::mod;
17908
17909        const A: u32 = 42;
17910
17911        fn main() {
17912            println!("hello");
17913
17914            println!("world");
17915        }
17916        "#
17917    .unindent();
17918
17919    // Edits are modified, removed, modified, added
17920    cx.set_state(
17921        &r#"
17922        use some::modified;
17923
17924        ˇ
17925        fn main() {
17926            println!("hello there");
17927
17928            println!("around the");
17929            println!("world");
17930        }
17931        "#
17932        .unindent(),
17933    );
17934
17935    cx.set_head_text(&diff_base);
17936    executor.run_until_parked();
17937
17938    cx.update_editor(|editor, window, cx| {
17939        //Wrap around the bottom of the buffer
17940        for _ in 0..3 {
17941            editor.go_to_next_hunk(&GoToHunk, window, cx);
17942        }
17943    });
17944
17945    cx.assert_editor_state(
17946        &r#"
17947        ˇuse some::modified;
17948
17949
17950        fn main() {
17951            println!("hello there");
17952
17953            println!("around the");
17954            println!("world");
17955        }
17956        "#
17957        .unindent(),
17958    );
17959
17960    cx.update_editor(|editor, window, cx| {
17961        //Wrap around the top of the buffer
17962        for _ in 0..2 {
17963            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17964        }
17965    });
17966
17967    cx.assert_editor_state(
17968        &r#"
17969        use some::modified;
17970
17971
17972        fn main() {
17973        ˇ    println!("hello there");
17974
17975            println!("around the");
17976            println!("world");
17977        }
17978        "#
17979        .unindent(),
17980    );
17981
17982    cx.update_editor(|editor, window, cx| {
17983        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17984    });
17985
17986    cx.assert_editor_state(
17987        &r#"
17988        use some::modified;
17989
17990        ˇ
17991        fn main() {
17992            println!("hello there");
17993
17994            println!("around the");
17995            println!("world");
17996        }
17997        "#
17998        .unindent(),
17999    );
18000
18001    cx.update_editor(|editor, window, cx| {
18002        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18003    });
18004
18005    cx.assert_editor_state(
18006        &r#"
18007        ˇuse some::modified;
18008
18009
18010        fn main() {
18011            println!("hello there");
18012
18013            println!("around the");
18014            println!("world");
18015        }
18016        "#
18017        .unindent(),
18018    );
18019
18020    cx.update_editor(|editor, window, cx| {
18021        for _ in 0..2 {
18022            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18023        }
18024    });
18025
18026    cx.assert_editor_state(
18027        &r#"
18028        use some::modified;
18029
18030
18031        fn main() {
18032        ˇ    println!("hello there");
18033
18034            println!("around the");
18035            println!("world");
18036        }
18037        "#
18038        .unindent(),
18039    );
18040
18041    cx.update_editor(|editor, window, cx| {
18042        editor.fold(&Fold, window, cx);
18043    });
18044
18045    cx.update_editor(|editor, window, cx| {
18046        editor.go_to_next_hunk(&GoToHunk, window, cx);
18047    });
18048
18049    cx.assert_editor_state(
18050        &r#"
18051        ˇuse some::modified;
18052
18053
18054        fn main() {
18055            println!("hello there");
18056
18057            println!("around the");
18058            println!("world");
18059        }
18060        "#
18061        .unindent(),
18062    );
18063}
18064
18065#[test]
18066fn test_split_words() {
18067    fn split(text: &str) -> Vec<&str> {
18068        split_words(text).collect()
18069    }
18070
18071    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18072    assert_eq!(split("hello_world"), &["hello_", "world"]);
18073    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18074    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18075    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18076    assert_eq!(split("helloworld"), &["helloworld"]);
18077
18078    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18079}
18080
18081#[test]
18082fn test_split_words_for_snippet_prefix() {
18083    fn split(text: &str) -> Vec<&str> {
18084        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18085    }
18086
18087    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18088    assert_eq!(split("hello_world"), &["hello_world"]);
18089    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18090    assert_eq!(split("Hello_World"), &["Hello_World"]);
18091    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18092    assert_eq!(split("helloworld"), &["helloworld"]);
18093    assert_eq!(
18094        split("this@is!@#$^many   . symbols"),
18095        &[
18096            "symbols",
18097            " symbols",
18098            ". symbols",
18099            " . symbols",
18100            "  . symbols",
18101            "   . symbols",
18102            "many   . symbols",
18103            "^many   . symbols",
18104            "$^many   . symbols",
18105            "#$^many   . symbols",
18106            "@#$^many   . symbols",
18107            "!@#$^many   . symbols",
18108            "is!@#$^many   . symbols",
18109            "@is!@#$^many   . symbols",
18110            "this@is!@#$^many   . symbols",
18111        ],
18112    );
18113    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18114}
18115
18116#[gpui::test]
18117async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18118    init_test(cx, |_| {});
18119
18120    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18121
18122    #[track_caller]
18123    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18124        let _state_context = cx.set_state(before);
18125        cx.run_until_parked();
18126        cx.update_editor(|editor, window, cx| {
18127            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18128        });
18129        cx.run_until_parked();
18130        cx.assert_editor_state(after);
18131    }
18132
18133    // Outside bracket jumps to outside of matching bracket
18134    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18135    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18136
18137    // Inside bracket jumps to inside of matching bracket
18138    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18139    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18140
18141    // When outside a bracket and inside, favor jumping to the inside bracket
18142    assert(
18143        "console.log('foo', [1, 2, 3]ˇ);",
18144        "console.log('foo', ˇ[1, 2, 3]);",
18145        &mut cx,
18146    );
18147    assert(
18148        "console.log(ˇ'foo', [1, 2, 3]);",
18149        "console.log('foo'ˇ, [1, 2, 3]);",
18150        &mut cx,
18151    );
18152
18153    // Bias forward if two options are equally likely
18154    assert(
18155        "let result = curried_fun()ˇ();",
18156        "let result = curried_fun()()ˇ;",
18157        &mut cx,
18158    );
18159
18160    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18161    assert(
18162        indoc! {"
18163            function test() {
18164                console.log('test')ˇ
18165            }"},
18166        indoc! {"
18167            function test() {
18168                console.logˇ('test')
18169            }"},
18170        &mut cx,
18171    );
18172}
18173
18174#[gpui::test]
18175async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18176    init_test(cx, |_| {});
18177    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18178    language_registry.add(markdown_lang());
18179    language_registry.add(rust_lang());
18180    let buffer = cx.new(|cx| {
18181        let mut buffer = language::Buffer::local(
18182            indoc! {"
18183            ```rs
18184            impl Worktree {
18185                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18186                }
18187            }
18188            ```
18189        "},
18190            cx,
18191        );
18192        buffer.set_language_registry(language_registry.clone());
18193        buffer.set_language(Some(markdown_lang()), cx);
18194        buffer
18195    });
18196    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18197    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18198    cx.executor().run_until_parked();
18199    _ = editor.update(cx, |editor, window, cx| {
18200        // Case 1: Test outer enclosing brackets
18201        select_ranges(
18202            editor,
18203            &indoc! {"
18204                ```rs
18205                impl Worktree {
18206                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18207                    }
1820818209                ```
18210            "},
18211            window,
18212            cx,
18213        );
18214        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18215        assert_text_with_selections(
18216            editor,
18217            &indoc! {"
18218                ```rs
18219                impl Worktree ˇ{
18220                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18221                    }
18222                }
18223                ```
18224            "},
18225            cx,
18226        );
18227        // Case 2: Test inner enclosing brackets
18228        select_ranges(
18229            editor,
18230            &indoc! {"
18231                ```rs
18232                impl Worktree {
18233                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1823418235                }
18236                ```
18237            "},
18238            window,
18239            cx,
18240        );
18241        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18242        assert_text_with_selections(
18243            editor,
18244            &indoc! {"
18245                ```rs
18246                impl Worktree {
18247                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18248                    }
18249                }
18250                ```
18251            "},
18252            cx,
18253        );
18254    });
18255}
18256
18257#[gpui::test]
18258async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18259    init_test(cx, |_| {});
18260
18261    let fs = FakeFs::new(cx.executor());
18262    fs.insert_tree(
18263        path!("/a"),
18264        json!({
18265            "main.rs": "fn main() { let a = 5; }",
18266            "other.rs": "// Test file",
18267        }),
18268    )
18269    .await;
18270    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18271
18272    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18273    language_registry.add(Arc::new(Language::new(
18274        LanguageConfig {
18275            name: "Rust".into(),
18276            matcher: LanguageMatcher {
18277                path_suffixes: vec!["rs".to_string()],
18278                ..Default::default()
18279            },
18280            brackets: BracketPairConfig {
18281                pairs: vec![BracketPair {
18282                    start: "{".to_string(),
18283                    end: "}".to_string(),
18284                    close: true,
18285                    surround: true,
18286                    newline: true,
18287                }],
18288                disabled_scopes_by_bracket_ix: Vec::new(),
18289            },
18290            ..Default::default()
18291        },
18292        Some(tree_sitter_rust::LANGUAGE.into()),
18293    )));
18294    let mut fake_servers = language_registry.register_fake_lsp(
18295        "Rust",
18296        FakeLspAdapter {
18297            capabilities: lsp::ServerCapabilities {
18298                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18299                    first_trigger_character: "{".to_string(),
18300                    more_trigger_character: None,
18301                }),
18302                ..Default::default()
18303            },
18304            ..Default::default()
18305        },
18306    );
18307
18308    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18309
18310    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18311
18312    let worktree_id = workspace
18313        .update(cx, |workspace, _, cx| {
18314            workspace.project().update(cx, |project, cx| {
18315                project.worktrees(cx).next().unwrap().read(cx).id()
18316            })
18317        })
18318        .unwrap();
18319
18320    let buffer = project
18321        .update(cx, |project, cx| {
18322            project.open_local_buffer(path!("/a/main.rs"), cx)
18323        })
18324        .await
18325        .unwrap();
18326    let editor_handle = workspace
18327        .update(cx, |workspace, window, cx| {
18328            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18329        })
18330        .unwrap()
18331        .await
18332        .unwrap()
18333        .downcast::<Editor>()
18334        .unwrap();
18335
18336    let fake_server = fake_servers.next().await.unwrap();
18337
18338    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18339        |params, _| async move {
18340            assert_eq!(
18341                params.text_document_position.text_document.uri,
18342                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18343            );
18344            assert_eq!(
18345                params.text_document_position.position,
18346                lsp::Position::new(0, 21),
18347            );
18348
18349            Ok(Some(vec![lsp::TextEdit {
18350                new_text: "]".to_string(),
18351                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18352            }]))
18353        },
18354    );
18355
18356    editor_handle.update_in(cx, |editor, window, cx| {
18357        window.focus(&editor.focus_handle(cx), cx);
18358        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18359            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18360        });
18361        editor.handle_input("{", window, cx);
18362    });
18363
18364    cx.executor().run_until_parked();
18365
18366    buffer.update(cx, |buffer, _| {
18367        assert_eq!(
18368            buffer.text(),
18369            "fn main() { let a = {5}; }",
18370            "No extra braces from on type formatting should appear in the buffer"
18371        )
18372    });
18373}
18374
18375#[gpui::test(iterations = 20, seeds(31))]
18376async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18377    init_test(cx, |_| {});
18378
18379    let mut cx = EditorLspTestContext::new_rust(
18380        lsp::ServerCapabilities {
18381            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18382                first_trigger_character: ".".to_string(),
18383                more_trigger_character: None,
18384            }),
18385            ..Default::default()
18386        },
18387        cx,
18388    )
18389    .await;
18390
18391    cx.update_buffer(|buffer, _| {
18392        // This causes autoindent to be async.
18393        buffer.set_sync_parse_timeout(None)
18394    });
18395
18396    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18397    cx.simulate_keystroke("\n");
18398    cx.run_until_parked();
18399
18400    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18401    let mut request =
18402        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18403            let buffer_cloned = buffer_cloned.clone();
18404            async move {
18405                buffer_cloned.update(&mut cx, |buffer, _| {
18406                    assert_eq!(
18407                        buffer.text(),
18408                        "fn c() {\n    d()\n        .\n}\n",
18409                        "OnTypeFormatting should triggered after autoindent applied"
18410                    )
18411                });
18412
18413                Ok(Some(vec![]))
18414            }
18415        });
18416
18417    cx.simulate_keystroke(".");
18418    cx.run_until_parked();
18419
18420    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18421    assert!(request.next().await.is_some());
18422    request.close();
18423    assert!(request.next().await.is_none());
18424}
18425
18426#[gpui::test]
18427async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18428    init_test(cx, |_| {});
18429
18430    let fs = FakeFs::new(cx.executor());
18431    fs.insert_tree(
18432        path!("/a"),
18433        json!({
18434            "main.rs": "fn main() { let a = 5; }",
18435            "other.rs": "// Test file",
18436        }),
18437    )
18438    .await;
18439
18440    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18441
18442    let server_restarts = Arc::new(AtomicUsize::new(0));
18443    let closure_restarts = Arc::clone(&server_restarts);
18444    let language_server_name = "test language server";
18445    let language_name: LanguageName = "Rust".into();
18446
18447    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18448    language_registry.add(Arc::new(Language::new(
18449        LanguageConfig {
18450            name: language_name.clone(),
18451            matcher: LanguageMatcher {
18452                path_suffixes: vec!["rs".to_string()],
18453                ..Default::default()
18454            },
18455            ..Default::default()
18456        },
18457        Some(tree_sitter_rust::LANGUAGE.into()),
18458    )));
18459    let mut fake_servers = language_registry.register_fake_lsp(
18460        "Rust",
18461        FakeLspAdapter {
18462            name: language_server_name,
18463            initialization_options: Some(json!({
18464                "testOptionValue": true
18465            })),
18466            initializer: Some(Box::new(move |fake_server| {
18467                let task_restarts = Arc::clone(&closure_restarts);
18468                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18469                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18470                    futures::future::ready(Ok(()))
18471                });
18472            })),
18473            ..Default::default()
18474        },
18475    );
18476
18477    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18478    let _buffer = project
18479        .update(cx, |project, cx| {
18480            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18481        })
18482        .await
18483        .unwrap();
18484    let _fake_server = fake_servers.next().await.unwrap();
18485    update_test_language_settings(cx, |language_settings| {
18486        language_settings.languages.0.insert(
18487            language_name.clone().0,
18488            LanguageSettingsContent {
18489                tab_size: NonZeroU32::new(8),
18490                ..Default::default()
18491            },
18492        );
18493    });
18494    cx.executor().run_until_parked();
18495    assert_eq!(
18496        server_restarts.load(atomic::Ordering::Acquire),
18497        0,
18498        "Should not restart LSP server on an unrelated change"
18499    );
18500
18501    update_test_project_settings(cx, |project_settings| {
18502        project_settings.lsp.0.insert(
18503            "Some other server name".into(),
18504            LspSettings {
18505                binary: None,
18506                settings: None,
18507                initialization_options: Some(json!({
18508                    "some other init value": false
18509                })),
18510                enable_lsp_tasks: false,
18511                fetch: None,
18512            },
18513        );
18514    });
18515    cx.executor().run_until_parked();
18516    assert_eq!(
18517        server_restarts.load(atomic::Ordering::Acquire),
18518        0,
18519        "Should not restart LSP server on an unrelated LSP settings change"
18520    );
18521
18522    update_test_project_settings(cx, |project_settings| {
18523        project_settings.lsp.0.insert(
18524            language_server_name.into(),
18525            LspSettings {
18526                binary: None,
18527                settings: None,
18528                initialization_options: Some(json!({
18529                    "anotherInitValue": false
18530                })),
18531                enable_lsp_tasks: false,
18532                fetch: None,
18533            },
18534        );
18535    });
18536    cx.executor().run_until_parked();
18537    assert_eq!(
18538        server_restarts.load(atomic::Ordering::Acquire),
18539        1,
18540        "Should restart LSP server on a related LSP settings change"
18541    );
18542
18543    update_test_project_settings(cx, |project_settings| {
18544        project_settings.lsp.0.insert(
18545            language_server_name.into(),
18546            LspSettings {
18547                binary: None,
18548                settings: None,
18549                initialization_options: Some(json!({
18550                    "anotherInitValue": false
18551                })),
18552                enable_lsp_tasks: false,
18553                fetch: None,
18554            },
18555        );
18556    });
18557    cx.executor().run_until_parked();
18558    assert_eq!(
18559        server_restarts.load(atomic::Ordering::Acquire),
18560        1,
18561        "Should not restart LSP server on a related LSP settings change that is the same"
18562    );
18563
18564    update_test_project_settings(cx, |project_settings| {
18565        project_settings.lsp.0.insert(
18566            language_server_name.into(),
18567            LspSettings {
18568                binary: None,
18569                settings: None,
18570                initialization_options: None,
18571                enable_lsp_tasks: false,
18572                fetch: None,
18573            },
18574        );
18575    });
18576    cx.executor().run_until_parked();
18577    assert_eq!(
18578        server_restarts.load(atomic::Ordering::Acquire),
18579        2,
18580        "Should restart LSP server on another related LSP settings change"
18581    );
18582}
18583
18584#[gpui::test]
18585async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18586    init_test(cx, |_| {});
18587
18588    let mut cx = EditorLspTestContext::new_rust(
18589        lsp::ServerCapabilities {
18590            completion_provider: Some(lsp::CompletionOptions {
18591                trigger_characters: Some(vec![".".to_string()]),
18592                resolve_provider: Some(true),
18593                ..Default::default()
18594            }),
18595            ..Default::default()
18596        },
18597        cx,
18598    )
18599    .await;
18600
18601    cx.set_state("fn main() { let a = 2ˇ; }");
18602    cx.simulate_keystroke(".");
18603    let completion_item = lsp::CompletionItem {
18604        label: "some".into(),
18605        kind: Some(lsp::CompletionItemKind::SNIPPET),
18606        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18607        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18608            kind: lsp::MarkupKind::Markdown,
18609            value: "```rust\nSome(2)\n```".to_string(),
18610        })),
18611        deprecated: Some(false),
18612        sort_text: Some("fffffff2".to_string()),
18613        filter_text: Some("some".to_string()),
18614        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18615        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18616            range: lsp::Range {
18617                start: lsp::Position {
18618                    line: 0,
18619                    character: 22,
18620                },
18621                end: lsp::Position {
18622                    line: 0,
18623                    character: 22,
18624                },
18625            },
18626            new_text: "Some(2)".to_string(),
18627        })),
18628        additional_text_edits: Some(vec![lsp::TextEdit {
18629            range: lsp::Range {
18630                start: lsp::Position {
18631                    line: 0,
18632                    character: 20,
18633                },
18634                end: lsp::Position {
18635                    line: 0,
18636                    character: 22,
18637                },
18638            },
18639            new_text: "".to_string(),
18640        }]),
18641        ..Default::default()
18642    };
18643
18644    let closure_completion_item = completion_item.clone();
18645    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18646        let task_completion_item = closure_completion_item.clone();
18647        async move {
18648            Ok(Some(lsp::CompletionResponse::Array(vec![
18649                task_completion_item,
18650            ])))
18651        }
18652    });
18653
18654    request.next().await;
18655
18656    cx.condition(|editor, _| editor.context_menu_visible())
18657        .await;
18658    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18659        editor
18660            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18661            .unwrap()
18662    });
18663    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18664
18665    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18666        let task_completion_item = completion_item.clone();
18667        async move { Ok(task_completion_item) }
18668    })
18669    .next()
18670    .await
18671    .unwrap();
18672    apply_additional_edits.await.unwrap();
18673    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18674}
18675
18676#[gpui::test]
18677async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18678    init_test(cx, |_| {});
18679
18680    let mut cx = EditorLspTestContext::new_rust(
18681        lsp::ServerCapabilities {
18682            completion_provider: Some(lsp::CompletionOptions {
18683                trigger_characters: Some(vec![".".to_string()]),
18684                resolve_provider: Some(true),
18685                ..Default::default()
18686            }),
18687            ..Default::default()
18688        },
18689        cx,
18690    )
18691    .await;
18692
18693    cx.set_state("fn main() { let a = 2ˇ; }");
18694    cx.simulate_keystroke(".");
18695
18696    let item1 = lsp::CompletionItem {
18697        label: "method id()".to_string(),
18698        filter_text: Some("id".to_string()),
18699        detail: None,
18700        documentation: None,
18701        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18702            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18703            new_text: ".id".to_string(),
18704        })),
18705        ..lsp::CompletionItem::default()
18706    };
18707
18708    let item2 = lsp::CompletionItem {
18709        label: "other".to_string(),
18710        filter_text: Some("other".to_string()),
18711        detail: None,
18712        documentation: None,
18713        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18714            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18715            new_text: ".other".to_string(),
18716        })),
18717        ..lsp::CompletionItem::default()
18718    };
18719
18720    let item1 = item1.clone();
18721    cx.set_request_handler::<lsp::request::Completion, _, _>({
18722        let item1 = item1.clone();
18723        move |_, _, _| {
18724            let item1 = item1.clone();
18725            let item2 = item2.clone();
18726            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18727        }
18728    })
18729    .next()
18730    .await;
18731
18732    cx.condition(|editor, _| editor.context_menu_visible())
18733        .await;
18734    cx.update_editor(|editor, _, _| {
18735        let context_menu = editor.context_menu.borrow_mut();
18736        let context_menu = context_menu
18737            .as_ref()
18738            .expect("Should have the context menu deployed");
18739        match context_menu {
18740            CodeContextMenu::Completions(completions_menu) => {
18741                let completions = completions_menu.completions.borrow_mut();
18742                assert_eq!(
18743                    completions
18744                        .iter()
18745                        .map(|completion| &completion.label.text)
18746                        .collect::<Vec<_>>(),
18747                    vec!["method id()", "other"]
18748                )
18749            }
18750            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18751        }
18752    });
18753
18754    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18755        let item1 = item1.clone();
18756        move |_, item_to_resolve, _| {
18757            let item1 = item1.clone();
18758            async move {
18759                if item1 == item_to_resolve {
18760                    Ok(lsp::CompletionItem {
18761                        label: "method id()".to_string(),
18762                        filter_text: Some("id".to_string()),
18763                        detail: Some("Now resolved!".to_string()),
18764                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18765                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18766                            range: lsp::Range::new(
18767                                lsp::Position::new(0, 22),
18768                                lsp::Position::new(0, 22),
18769                            ),
18770                            new_text: ".id".to_string(),
18771                        })),
18772                        ..lsp::CompletionItem::default()
18773                    })
18774                } else {
18775                    Ok(item_to_resolve)
18776                }
18777            }
18778        }
18779    })
18780    .next()
18781    .await
18782    .unwrap();
18783    cx.run_until_parked();
18784
18785    cx.update_editor(|editor, window, cx| {
18786        editor.context_menu_next(&Default::default(), window, cx);
18787    });
18788    cx.run_until_parked();
18789
18790    cx.update_editor(|editor, _, _| {
18791        let context_menu = editor.context_menu.borrow_mut();
18792        let context_menu = context_menu
18793            .as_ref()
18794            .expect("Should have the context menu deployed");
18795        match context_menu {
18796            CodeContextMenu::Completions(completions_menu) => {
18797                let completions = completions_menu.completions.borrow_mut();
18798                assert_eq!(
18799                    completions
18800                        .iter()
18801                        .map(|completion| &completion.label.text)
18802                        .collect::<Vec<_>>(),
18803                    vec!["method id() Now resolved!", "other"],
18804                    "Should update first completion label, but not second as the filter text did not match."
18805                );
18806            }
18807            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18808        }
18809    });
18810}
18811
18812#[gpui::test]
18813async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18814    init_test(cx, |_| {});
18815    let mut cx = EditorLspTestContext::new_rust(
18816        lsp::ServerCapabilities {
18817            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18818            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18819            completion_provider: Some(lsp::CompletionOptions {
18820                resolve_provider: Some(true),
18821                ..Default::default()
18822            }),
18823            ..Default::default()
18824        },
18825        cx,
18826    )
18827    .await;
18828    cx.set_state(indoc! {"
18829        struct TestStruct {
18830            field: i32
18831        }
18832
18833        fn mainˇ() {
18834            let unused_var = 42;
18835            let test_struct = TestStruct { field: 42 };
18836        }
18837    "});
18838    let symbol_range = cx.lsp_range(indoc! {"
18839        struct TestStruct {
18840            field: i32
18841        }
18842
18843        «fn main»() {
18844            let unused_var = 42;
18845            let test_struct = TestStruct { field: 42 };
18846        }
18847    "});
18848    let mut hover_requests =
18849        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18850            Ok(Some(lsp::Hover {
18851                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18852                    kind: lsp::MarkupKind::Markdown,
18853                    value: "Function documentation".to_string(),
18854                }),
18855                range: Some(symbol_range),
18856            }))
18857        });
18858
18859    // Case 1: Test that code action menu hide hover popover
18860    cx.dispatch_action(Hover);
18861    hover_requests.next().await;
18862    cx.condition(|editor, _| editor.hover_state.visible()).await;
18863    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18864        move |_, _, _| async move {
18865            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18866                lsp::CodeAction {
18867                    title: "Remove unused variable".to_string(),
18868                    kind: Some(CodeActionKind::QUICKFIX),
18869                    edit: Some(lsp::WorkspaceEdit {
18870                        changes: Some(
18871                            [(
18872                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18873                                vec![lsp::TextEdit {
18874                                    range: lsp::Range::new(
18875                                        lsp::Position::new(5, 4),
18876                                        lsp::Position::new(5, 27),
18877                                    ),
18878                                    new_text: "".to_string(),
18879                                }],
18880                            )]
18881                            .into_iter()
18882                            .collect(),
18883                        ),
18884                        ..Default::default()
18885                    }),
18886                    ..Default::default()
18887                },
18888            )]))
18889        },
18890    );
18891    cx.update_editor(|editor, window, cx| {
18892        editor.toggle_code_actions(
18893            &ToggleCodeActions {
18894                deployed_from: None,
18895                quick_launch: false,
18896            },
18897            window,
18898            cx,
18899        );
18900    });
18901    code_action_requests.next().await;
18902    cx.run_until_parked();
18903    cx.condition(|editor, _| editor.context_menu_visible())
18904        .await;
18905    cx.update_editor(|editor, _, _| {
18906        assert!(
18907            !editor.hover_state.visible(),
18908            "Hover popover should be hidden when code action menu is shown"
18909        );
18910        // Hide code actions
18911        editor.context_menu.take();
18912    });
18913
18914    // Case 2: Test that code completions hide hover popover
18915    cx.dispatch_action(Hover);
18916    hover_requests.next().await;
18917    cx.condition(|editor, _| editor.hover_state.visible()).await;
18918    let counter = Arc::new(AtomicUsize::new(0));
18919    let mut completion_requests =
18920        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18921            let counter = counter.clone();
18922            async move {
18923                counter.fetch_add(1, atomic::Ordering::Release);
18924                Ok(Some(lsp::CompletionResponse::Array(vec![
18925                    lsp::CompletionItem {
18926                        label: "main".into(),
18927                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18928                        detail: Some("() -> ()".to_string()),
18929                        ..Default::default()
18930                    },
18931                    lsp::CompletionItem {
18932                        label: "TestStruct".into(),
18933                        kind: Some(lsp::CompletionItemKind::STRUCT),
18934                        detail: Some("struct TestStruct".to_string()),
18935                        ..Default::default()
18936                    },
18937                ])))
18938            }
18939        });
18940    cx.update_editor(|editor, window, cx| {
18941        editor.show_completions(&ShowCompletions, window, cx);
18942    });
18943    completion_requests.next().await;
18944    cx.condition(|editor, _| editor.context_menu_visible())
18945        .await;
18946    cx.update_editor(|editor, _, _| {
18947        assert!(
18948            !editor.hover_state.visible(),
18949            "Hover popover should be hidden when completion menu is shown"
18950        );
18951    });
18952}
18953
18954#[gpui::test]
18955async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18956    init_test(cx, |_| {});
18957
18958    let mut cx = EditorLspTestContext::new_rust(
18959        lsp::ServerCapabilities {
18960            completion_provider: Some(lsp::CompletionOptions {
18961                trigger_characters: Some(vec![".".to_string()]),
18962                resolve_provider: Some(true),
18963                ..Default::default()
18964            }),
18965            ..Default::default()
18966        },
18967        cx,
18968    )
18969    .await;
18970
18971    cx.set_state("fn main() { let a = 2ˇ; }");
18972    cx.simulate_keystroke(".");
18973
18974    let unresolved_item_1 = lsp::CompletionItem {
18975        label: "id".to_string(),
18976        filter_text: Some("id".to_string()),
18977        detail: None,
18978        documentation: None,
18979        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18980            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18981            new_text: ".id".to_string(),
18982        })),
18983        ..lsp::CompletionItem::default()
18984    };
18985    let resolved_item_1 = lsp::CompletionItem {
18986        additional_text_edits: Some(vec![lsp::TextEdit {
18987            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18988            new_text: "!!".to_string(),
18989        }]),
18990        ..unresolved_item_1.clone()
18991    };
18992    let unresolved_item_2 = lsp::CompletionItem {
18993        label: "other".to_string(),
18994        filter_text: Some("other".to_string()),
18995        detail: None,
18996        documentation: None,
18997        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18998            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18999            new_text: ".other".to_string(),
19000        })),
19001        ..lsp::CompletionItem::default()
19002    };
19003    let resolved_item_2 = lsp::CompletionItem {
19004        additional_text_edits: Some(vec![lsp::TextEdit {
19005            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19006            new_text: "??".to_string(),
19007        }]),
19008        ..unresolved_item_2.clone()
19009    };
19010
19011    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19012    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19013    cx.lsp
19014        .server
19015        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19016            let unresolved_item_1 = unresolved_item_1.clone();
19017            let resolved_item_1 = resolved_item_1.clone();
19018            let unresolved_item_2 = unresolved_item_2.clone();
19019            let resolved_item_2 = resolved_item_2.clone();
19020            let resolve_requests_1 = resolve_requests_1.clone();
19021            let resolve_requests_2 = resolve_requests_2.clone();
19022            move |unresolved_request, _| {
19023                let unresolved_item_1 = unresolved_item_1.clone();
19024                let resolved_item_1 = resolved_item_1.clone();
19025                let unresolved_item_2 = unresolved_item_2.clone();
19026                let resolved_item_2 = resolved_item_2.clone();
19027                let resolve_requests_1 = resolve_requests_1.clone();
19028                let resolve_requests_2 = resolve_requests_2.clone();
19029                async move {
19030                    if unresolved_request == unresolved_item_1 {
19031                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19032                        Ok(resolved_item_1.clone())
19033                    } else if unresolved_request == unresolved_item_2 {
19034                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19035                        Ok(resolved_item_2.clone())
19036                    } else {
19037                        panic!("Unexpected completion item {unresolved_request:?}")
19038                    }
19039                }
19040            }
19041        })
19042        .detach();
19043
19044    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19045        let unresolved_item_1 = unresolved_item_1.clone();
19046        let unresolved_item_2 = unresolved_item_2.clone();
19047        async move {
19048            Ok(Some(lsp::CompletionResponse::Array(vec![
19049                unresolved_item_1,
19050                unresolved_item_2,
19051            ])))
19052        }
19053    })
19054    .next()
19055    .await;
19056
19057    cx.condition(|editor, _| editor.context_menu_visible())
19058        .await;
19059    cx.update_editor(|editor, _, _| {
19060        let context_menu = editor.context_menu.borrow_mut();
19061        let context_menu = context_menu
19062            .as_ref()
19063            .expect("Should have the context menu deployed");
19064        match context_menu {
19065            CodeContextMenu::Completions(completions_menu) => {
19066                let completions = completions_menu.completions.borrow_mut();
19067                assert_eq!(
19068                    completions
19069                        .iter()
19070                        .map(|completion| &completion.label.text)
19071                        .collect::<Vec<_>>(),
19072                    vec!["id", "other"]
19073                )
19074            }
19075            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19076        }
19077    });
19078    cx.run_until_parked();
19079
19080    cx.update_editor(|editor, window, cx| {
19081        editor.context_menu_next(&ContextMenuNext, window, cx);
19082    });
19083    cx.run_until_parked();
19084    cx.update_editor(|editor, window, cx| {
19085        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19086    });
19087    cx.run_until_parked();
19088    cx.update_editor(|editor, window, cx| {
19089        editor.context_menu_next(&ContextMenuNext, window, cx);
19090    });
19091    cx.run_until_parked();
19092    cx.update_editor(|editor, window, cx| {
19093        editor
19094            .compose_completion(&ComposeCompletion::default(), window, cx)
19095            .expect("No task returned")
19096    })
19097    .await
19098    .expect("Completion failed");
19099    cx.run_until_parked();
19100
19101    cx.update_editor(|editor, _, cx| {
19102        assert_eq!(
19103            resolve_requests_1.load(atomic::Ordering::Acquire),
19104            1,
19105            "Should always resolve once despite multiple selections"
19106        );
19107        assert_eq!(
19108            resolve_requests_2.load(atomic::Ordering::Acquire),
19109            1,
19110            "Should always resolve once after multiple selections and applying the completion"
19111        );
19112        assert_eq!(
19113            editor.text(cx),
19114            "fn main() { let a = ??.other; }",
19115            "Should use resolved data when applying the completion"
19116        );
19117    });
19118}
19119
19120#[gpui::test]
19121async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19122    init_test(cx, |_| {});
19123
19124    let item_0 = lsp::CompletionItem {
19125        label: "abs".into(),
19126        insert_text: Some("abs".into()),
19127        data: Some(json!({ "very": "special"})),
19128        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19129        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19130            lsp::InsertReplaceEdit {
19131                new_text: "abs".to_string(),
19132                insert: lsp::Range::default(),
19133                replace: lsp::Range::default(),
19134            },
19135        )),
19136        ..lsp::CompletionItem::default()
19137    };
19138    let items = iter::once(item_0.clone())
19139        .chain((11..51).map(|i| lsp::CompletionItem {
19140            label: format!("item_{}", i),
19141            insert_text: Some(format!("item_{}", i)),
19142            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19143            ..lsp::CompletionItem::default()
19144        }))
19145        .collect::<Vec<_>>();
19146
19147    let default_commit_characters = vec!["?".to_string()];
19148    let default_data = json!({ "default": "data"});
19149    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19150    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19151    let default_edit_range = lsp::Range {
19152        start: lsp::Position {
19153            line: 0,
19154            character: 5,
19155        },
19156        end: lsp::Position {
19157            line: 0,
19158            character: 5,
19159        },
19160    };
19161
19162    let mut cx = EditorLspTestContext::new_rust(
19163        lsp::ServerCapabilities {
19164            completion_provider: Some(lsp::CompletionOptions {
19165                trigger_characters: Some(vec![".".to_string()]),
19166                resolve_provider: Some(true),
19167                ..Default::default()
19168            }),
19169            ..Default::default()
19170        },
19171        cx,
19172    )
19173    .await;
19174
19175    cx.set_state("fn main() { let a = 2ˇ; }");
19176    cx.simulate_keystroke(".");
19177
19178    let completion_data = default_data.clone();
19179    let completion_characters = default_commit_characters.clone();
19180    let completion_items = items.clone();
19181    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19182        let default_data = completion_data.clone();
19183        let default_commit_characters = completion_characters.clone();
19184        let items = completion_items.clone();
19185        async move {
19186            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19187                items,
19188                item_defaults: Some(lsp::CompletionListItemDefaults {
19189                    data: Some(default_data.clone()),
19190                    commit_characters: Some(default_commit_characters.clone()),
19191                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19192                        default_edit_range,
19193                    )),
19194                    insert_text_format: Some(default_insert_text_format),
19195                    insert_text_mode: Some(default_insert_text_mode),
19196                }),
19197                ..lsp::CompletionList::default()
19198            })))
19199        }
19200    })
19201    .next()
19202    .await;
19203
19204    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19205    cx.lsp
19206        .server
19207        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19208            let closure_resolved_items = resolved_items.clone();
19209            move |item_to_resolve, _| {
19210                let closure_resolved_items = closure_resolved_items.clone();
19211                async move {
19212                    closure_resolved_items.lock().push(item_to_resolve.clone());
19213                    Ok(item_to_resolve)
19214                }
19215            }
19216        })
19217        .detach();
19218
19219    cx.condition(|editor, _| editor.context_menu_visible())
19220        .await;
19221    cx.run_until_parked();
19222    cx.update_editor(|editor, _, _| {
19223        let menu = editor.context_menu.borrow_mut();
19224        match menu.as_ref().expect("should have the completions menu") {
19225            CodeContextMenu::Completions(completions_menu) => {
19226                assert_eq!(
19227                    completions_menu
19228                        .entries
19229                        .borrow()
19230                        .iter()
19231                        .map(|mat| mat.string.clone())
19232                        .collect::<Vec<String>>(),
19233                    items
19234                        .iter()
19235                        .map(|completion| completion.label.clone())
19236                        .collect::<Vec<String>>()
19237                );
19238            }
19239            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19240        }
19241    });
19242    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19243    // with 4 from the end.
19244    assert_eq!(
19245        *resolved_items.lock(),
19246        [&items[0..16], &items[items.len() - 4..items.len()]]
19247            .concat()
19248            .iter()
19249            .cloned()
19250            .map(|mut item| {
19251                if item.data.is_none() {
19252                    item.data = Some(default_data.clone());
19253                }
19254                item
19255            })
19256            .collect::<Vec<lsp::CompletionItem>>(),
19257        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19258    );
19259    resolved_items.lock().clear();
19260
19261    cx.update_editor(|editor, window, cx| {
19262        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19263    });
19264    cx.run_until_parked();
19265    // Completions that have already been resolved are skipped.
19266    assert_eq!(
19267        *resolved_items.lock(),
19268        items[items.len() - 17..items.len() - 4]
19269            .iter()
19270            .cloned()
19271            .map(|mut item| {
19272                if item.data.is_none() {
19273                    item.data = Some(default_data.clone());
19274                }
19275                item
19276            })
19277            .collect::<Vec<lsp::CompletionItem>>()
19278    );
19279    resolved_items.lock().clear();
19280}
19281
19282#[gpui::test]
19283async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19284    init_test(cx, |_| {});
19285
19286    let mut cx = EditorLspTestContext::new(
19287        Language::new(
19288            LanguageConfig {
19289                matcher: LanguageMatcher {
19290                    path_suffixes: vec!["jsx".into()],
19291                    ..Default::default()
19292                },
19293                overrides: [(
19294                    "element".into(),
19295                    LanguageConfigOverride {
19296                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19297                        ..Default::default()
19298                    },
19299                )]
19300                .into_iter()
19301                .collect(),
19302                ..Default::default()
19303            },
19304            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19305        )
19306        .with_override_query("(jsx_self_closing_element) @element")
19307        .unwrap(),
19308        lsp::ServerCapabilities {
19309            completion_provider: Some(lsp::CompletionOptions {
19310                trigger_characters: Some(vec![":".to_string()]),
19311                ..Default::default()
19312            }),
19313            ..Default::default()
19314        },
19315        cx,
19316    )
19317    .await;
19318
19319    cx.lsp
19320        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19321            Ok(Some(lsp::CompletionResponse::Array(vec![
19322                lsp::CompletionItem {
19323                    label: "bg-blue".into(),
19324                    ..Default::default()
19325                },
19326                lsp::CompletionItem {
19327                    label: "bg-red".into(),
19328                    ..Default::default()
19329                },
19330                lsp::CompletionItem {
19331                    label: "bg-yellow".into(),
19332                    ..Default::default()
19333                },
19334            ])))
19335        });
19336
19337    cx.set_state(r#"<p class="bgˇ" />"#);
19338
19339    // Trigger completion when typing a dash, because the dash is an extra
19340    // word character in the 'element' scope, which contains the cursor.
19341    cx.simulate_keystroke("-");
19342    cx.executor().run_until_parked();
19343    cx.update_editor(|editor, _, _| {
19344        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19345        {
19346            assert_eq!(
19347                completion_menu_entries(menu),
19348                &["bg-blue", "bg-red", "bg-yellow"]
19349            );
19350        } else {
19351            panic!("expected completion menu to be open");
19352        }
19353    });
19354
19355    cx.simulate_keystroke("l");
19356    cx.executor().run_until_parked();
19357    cx.update_editor(|editor, _, _| {
19358        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19359        {
19360            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19361        } else {
19362            panic!("expected completion menu to be open");
19363        }
19364    });
19365
19366    // When filtering completions, consider the character after the '-' to
19367    // be the start of a subword.
19368    cx.set_state(r#"<p class="yelˇ" />"#);
19369    cx.simulate_keystroke("l");
19370    cx.executor().run_until_parked();
19371    cx.update_editor(|editor, _, _| {
19372        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19373        {
19374            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19375        } else {
19376            panic!("expected completion menu to be open");
19377        }
19378    });
19379}
19380
19381fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19382    let entries = menu.entries.borrow();
19383    entries.iter().map(|mat| mat.string.clone()).collect()
19384}
19385
19386#[gpui::test]
19387async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19388    init_test(cx, |settings| {
19389        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19390    });
19391
19392    let fs = FakeFs::new(cx.executor());
19393    fs.insert_file(path!("/file.ts"), Default::default()).await;
19394
19395    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19396    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19397
19398    language_registry.add(Arc::new(Language::new(
19399        LanguageConfig {
19400            name: "TypeScript".into(),
19401            matcher: LanguageMatcher {
19402                path_suffixes: vec!["ts".to_string()],
19403                ..Default::default()
19404            },
19405            ..Default::default()
19406        },
19407        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19408    )));
19409    update_test_language_settings(cx, |settings| {
19410        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19411    });
19412
19413    let test_plugin = "test_plugin";
19414    let _ = language_registry.register_fake_lsp(
19415        "TypeScript",
19416        FakeLspAdapter {
19417            prettier_plugins: vec![test_plugin],
19418            ..Default::default()
19419        },
19420    );
19421
19422    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19423    let buffer = project
19424        .update(cx, |project, cx| {
19425            project.open_local_buffer(path!("/file.ts"), cx)
19426        })
19427        .await
19428        .unwrap();
19429
19430    let buffer_text = "one\ntwo\nthree\n";
19431    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19432    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19433    editor.update_in(cx, |editor, window, cx| {
19434        editor.set_text(buffer_text, window, cx)
19435    });
19436
19437    editor
19438        .update_in(cx, |editor, window, cx| {
19439            editor.perform_format(
19440                project.clone(),
19441                FormatTrigger::Manual,
19442                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19443                window,
19444                cx,
19445            )
19446        })
19447        .unwrap()
19448        .await;
19449    assert_eq!(
19450        editor.update(cx, |editor, cx| editor.text(cx)),
19451        buffer_text.to_string() + prettier_format_suffix,
19452        "Test prettier formatting was not applied to the original buffer text",
19453    );
19454
19455    update_test_language_settings(cx, |settings| {
19456        settings.defaults.formatter = Some(FormatterList::default())
19457    });
19458    let format = editor.update_in(cx, |editor, window, cx| {
19459        editor.perform_format(
19460            project.clone(),
19461            FormatTrigger::Manual,
19462            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19463            window,
19464            cx,
19465        )
19466    });
19467    format.await.unwrap();
19468    assert_eq!(
19469        editor.update(cx, |editor, cx| editor.text(cx)),
19470        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19471        "Autoformatting (via test prettier) was not applied to the original buffer text",
19472    );
19473}
19474
19475#[gpui::test]
19476async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19477    init_test(cx, |settings| {
19478        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19479    });
19480
19481    let fs = FakeFs::new(cx.executor());
19482    fs.insert_file(path!("/file.settings"), Default::default())
19483        .await;
19484
19485    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19487
19488    let ts_lang = Arc::new(Language::new(
19489        LanguageConfig {
19490            name: "TypeScript".into(),
19491            matcher: LanguageMatcher {
19492                path_suffixes: vec!["ts".to_string()],
19493                ..LanguageMatcher::default()
19494            },
19495            prettier_parser_name: Some("typescript".to_string()),
19496            ..LanguageConfig::default()
19497        },
19498        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19499    ));
19500
19501    language_registry.add(ts_lang.clone());
19502
19503    update_test_language_settings(cx, |settings| {
19504        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19505    });
19506
19507    let test_plugin = "test_plugin";
19508    let _ = language_registry.register_fake_lsp(
19509        "TypeScript",
19510        FakeLspAdapter {
19511            prettier_plugins: vec![test_plugin],
19512            ..Default::default()
19513        },
19514    );
19515
19516    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19517    let buffer = project
19518        .update(cx, |project, cx| {
19519            project.open_local_buffer(path!("/file.settings"), cx)
19520        })
19521        .await
19522        .unwrap();
19523
19524    project.update(cx, |project, cx| {
19525        project.set_language_for_buffer(&buffer, ts_lang, cx)
19526    });
19527
19528    let buffer_text = "one\ntwo\nthree\n";
19529    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19530    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19531    editor.update_in(cx, |editor, window, cx| {
19532        editor.set_text(buffer_text, window, cx)
19533    });
19534
19535    editor
19536        .update_in(cx, |editor, window, cx| {
19537            editor.perform_format(
19538                project.clone(),
19539                FormatTrigger::Manual,
19540                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19541                window,
19542                cx,
19543            )
19544        })
19545        .unwrap()
19546        .await;
19547    assert_eq!(
19548        editor.update(cx, |editor, cx| editor.text(cx)),
19549        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19550        "Test prettier formatting was not applied to the original buffer text",
19551    );
19552
19553    update_test_language_settings(cx, |settings| {
19554        settings.defaults.formatter = Some(FormatterList::default())
19555    });
19556    let format = editor.update_in(cx, |editor, window, cx| {
19557        editor.perform_format(
19558            project.clone(),
19559            FormatTrigger::Manual,
19560            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19561            window,
19562            cx,
19563        )
19564    });
19565    format.await.unwrap();
19566
19567    assert_eq!(
19568        editor.update(cx, |editor, cx| editor.text(cx)),
19569        buffer_text.to_string()
19570            + prettier_format_suffix
19571            + "\ntypescript\n"
19572            + prettier_format_suffix
19573            + "\ntypescript",
19574        "Autoformatting (via test prettier) was not applied to the original buffer text",
19575    );
19576}
19577
19578#[gpui::test]
19579async fn test_addition_reverts(cx: &mut TestAppContext) {
19580    init_test(cx, |_| {});
19581    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19582    let base_text = indoc! {r#"
19583        struct Row;
19584        struct Row1;
19585        struct Row2;
19586
19587        struct Row4;
19588        struct Row5;
19589        struct Row6;
19590
19591        struct Row8;
19592        struct Row9;
19593        struct Row10;"#};
19594
19595    // When addition hunks are not adjacent to carets, no hunk revert is performed
19596    assert_hunk_revert(
19597        indoc! {r#"struct Row;
19598                   struct Row1;
19599                   struct Row1.1;
19600                   struct Row1.2;
19601                   struct Row2;ˇ
19602
19603                   struct Row4;
19604                   struct Row5;
19605                   struct Row6;
19606
19607                   struct Row8;
19608                   ˇstruct Row9;
19609                   struct Row9.1;
19610                   struct Row9.2;
19611                   struct Row9.3;
19612                   struct Row10;"#},
19613        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19614        indoc! {r#"struct Row;
19615                   struct Row1;
19616                   struct Row1.1;
19617                   struct Row1.2;
19618                   struct Row2;ˇ
19619
19620                   struct Row4;
19621                   struct Row5;
19622                   struct Row6;
19623
19624                   struct Row8;
19625                   ˇstruct Row9;
19626                   struct Row9.1;
19627                   struct Row9.2;
19628                   struct Row9.3;
19629                   struct Row10;"#},
19630        base_text,
19631        &mut cx,
19632    );
19633    // Same for selections
19634    assert_hunk_revert(
19635        indoc! {r#"struct Row;
19636                   struct Row1;
19637                   struct Row2;
19638                   struct Row2.1;
19639                   struct Row2.2;
19640                   «ˇ
19641                   struct Row4;
19642                   struct» Row5;
19643                   «struct Row6;
19644                   ˇ»
19645                   struct Row9.1;
19646                   struct Row9.2;
19647                   struct Row9.3;
19648                   struct Row8;
19649                   struct Row9;
19650                   struct Row10;"#},
19651        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19652        indoc! {r#"struct Row;
19653                   struct Row1;
19654                   struct Row2;
19655                   struct Row2.1;
19656                   struct Row2.2;
19657                   «ˇ
19658                   struct Row4;
19659                   struct» Row5;
19660                   «struct Row6;
19661                   ˇ»
19662                   struct Row9.1;
19663                   struct Row9.2;
19664                   struct Row9.3;
19665                   struct Row8;
19666                   struct Row9;
19667                   struct Row10;"#},
19668        base_text,
19669        &mut cx,
19670    );
19671
19672    // When carets and selections intersect the addition hunks, those are reverted.
19673    // Adjacent carets got merged.
19674    assert_hunk_revert(
19675        indoc! {r#"struct Row;
19676                   ˇ// something on the top
19677                   struct Row1;
19678                   struct Row2;
19679                   struct Roˇw3.1;
19680                   struct Row2.2;
19681                   struct Row2.3;ˇ
19682
19683                   struct Row4;
19684                   struct ˇRow5.1;
19685                   struct Row5.2;
19686                   struct «Rowˇ»5.3;
19687                   struct Row5;
19688                   struct Row6;
19689                   ˇ
19690                   struct Row9.1;
19691                   struct «Rowˇ»9.2;
19692                   struct «ˇRow»9.3;
19693                   struct Row8;
19694                   struct Row9;
19695                   «ˇ// something on bottom»
19696                   struct Row10;"#},
19697        vec![
19698            DiffHunkStatusKind::Added,
19699            DiffHunkStatusKind::Added,
19700            DiffHunkStatusKind::Added,
19701            DiffHunkStatusKind::Added,
19702            DiffHunkStatusKind::Added,
19703        ],
19704        indoc! {r#"struct Row;
19705                   ˇstruct Row1;
19706                   struct Row2;
19707                   ˇ
19708                   struct Row4;
19709                   ˇstruct Row5;
19710                   struct Row6;
19711                   ˇ
19712                   ˇstruct Row8;
19713                   struct Row9;
19714                   ˇstruct Row10;"#},
19715        base_text,
19716        &mut cx,
19717    );
19718}
19719
19720#[gpui::test]
19721async fn test_modification_reverts(cx: &mut TestAppContext) {
19722    init_test(cx, |_| {});
19723    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19724    let base_text = indoc! {r#"
19725        struct Row;
19726        struct Row1;
19727        struct Row2;
19728
19729        struct Row4;
19730        struct Row5;
19731        struct Row6;
19732
19733        struct Row8;
19734        struct Row9;
19735        struct Row10;"#};
19736
19737    // Modification hunks behave the same as the addition ones.
19738    assert_hunk_revert(
19739        indoc! {r#"struct Row;
19740                   struct Row1;
19741                   struct Row33;
19742                   ˇ
19743                   struct Row4;
19744                   struct Row5;
19745                   struct Row6;
19746                   ˇ
19747                   struct Row99;
19748                   struct Row9;
19749                   struct Row10;"#},
19750        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19751        indoc! {r#"struct Row;
19752                   struct Row1;
19753                   struct Row33;
19754                   ˇ
19755                   struct Row4;
19756                   struct Row5;
19757                   struct Row6;
19758                   ˇ
19759                   struct Row99;
19760                   struct Row9;
19761                   struct Row10;"#},
19762        base_text,
19763        &mut cx,
19764    );
19765    assert_hunk_revert(
19766        indoc! {r#"struct Row;
19767                   struct Row1;
19768                   struct Row33;
19769                   «ˇ
19770                   struct Row4;
19771                   struct» Row5;
19772                   «struct Row6;
19773                   ˇ»
19774                   struct Row99;
19775                   struct Row9;
19776                   struct Row10;"#},
19777        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19778        indoc! {r#"struct Row;
19779                   struct Row1;
19780                   struct Row33;
19781                   «ˇ
19782                   struct Row4;
19783                   struct» Row5;
19784                   «struct Row6;
19785                   ˇ»
19786                   struct Row99;
19787                   struct Row9;
19788                   struct Row10;"#},
19789        base_text,
19790        &mut cx,
19791    );
19792
19793    assert_hunk_revert(
19794        indoc! {r#"ˇstruct Row1.1;
19795                   struct Row1;
19796                   «ˇstr»uct Row22;
19797
19798                   struct ˇRow44;
19799                   struct Row5;
19800                   struct «Rˇ»ow66;ˇ
19801
19802                   «struˇ»ct Row88;
19803                   struct Row9;
19804                   struct Row1011;ˇ"#},
19805        vec![
19806            DiffHunkStatusKind::Modified,
19807            DiffHunkStatusKind::Modified,
19808            DiffHunkStatusKind::Modified,
19809            DiffHunkStatusKind::Modified,
19810            DiffHunkStatusKind::Modified,
19811            DiffHunkStatusKind::Modified,
19812        ],
19813        indoc! {r#"struct Row;
19814                   ˇstruct Row1;
19815                   struct Row2;
19816                   ˇ
19817                   struct Row4;
19818                   ˇstruct Row5;
19819                   struct Row6;
19820                   ˇ
19821                   struct Row8;
19822                   ˇstruct Row9;
19823                   struct Row10;ˇ"#},
19824        base_text,
19825        &mut cx,
19826    );
19827}
19828
19829#[gpui::test]
19830async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19831    init_test(cx, |_| {});
19832    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19833    let base_text = indoc! {r#"
19834        one
19835
19836        two
19837        three
19838        "#};
19839
19840    cx.set_head_text(base_text);
19841    cx.set_state("\nˇ\n");
19842    cx.executor().run_until_parked();
19843    cx.update_editor(|editor, _window, cx| {
19844        editor.expand_selected_diff_hunks(cx);
19845    });
19846    cx.executor().run_until_parked();
19847    cx.update_editor(|editor, window, cx| {
19848        editor.backspace(&Default::default(), window, cx);
19849    });
19850    cx.run_until_parked();
19851    cx.assert_state_with_diff(
19852        indoc! {r#"
19853
19854        - two
19855        - threeˇ
19856        +
19857        "#}
19858        .to_string(),
19859    );
19860}
19861
19862#[gpui::test]
19863async fn test_deletion_reverts(cx: &mut TestAppContext) {
19864    init_test(cx, |_| {});
19865    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19866    let base_text = indoc! {r#"struct Row;
19867struct Row1;
19868struct Row2;
19869
19870struct Row4;
19871struct Row5;
19872struct Row6;
19873
19874struct Row8;
19875struct Row9;
19876struct Row10;"#};
19877
19878    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19879    assert_hunk_revert(
19880        indoc! {r#"struct Row;
19881                   struct Row2;
19882
19883                   ˇstruct Row4;
19884                   struct Row5;
19885                   struct Row6;
19886                   ˇ
19887                   struct Row8;
19888                   struct Row10;"#},
19889        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19890        indoc! {r#"struct Row;
19891                   struct Row2;
19892
19893                   ˇstruct Row4;
19894                   struct Row5;
19895                   struct Row6;
19896                   ˇ
19897                   struct Row8;
19898                   struct Row10;"#},
19899        base_text,
19900        &mut cx,
19901    );
19902    assert_hunk_revert(
19903        indoc! {r#"struct Row;
19904                   struct Row2;
19905
19906                   «ˇstruct Row4;
19907                   struct» Row5;
19908                   «struct Row6;
19909                   ˇ»
19910                   struct Row8;
19911                   struct Row10;"#},
19912        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19913        indoc! {r#"struct Row;
19914                   struct Row2;
19915
19916                   «ˇstruct Row4;
19917                   struct» Row5;
19918                   «struct Row6;
19919                   ˇ»
19920                   struct Row8;
19921                   struct Row10;"#},
19922        base_text,
19923        &mut cx,
19924    );
19925
19926    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19927    assert_hunk_revert(
19928        indoc! {r#"struct Row;
19929                   ˇstruct Row2;
19930
19931                   struct Row4;
19932                   struct Row5;
19933                   struct Row6;
19934
19935                   struct Row8;ˇ
19936                   struct Row10;"#},
19937        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19938        indoc! {r#"struct Row;
19939                   struct Row1;
19940                   ˇstruct Row2;
19941
19942                   struct Row4;
19943                   struct Row5;
19944                   struct Row6;
19945
19946                   struct Row8;ˇ
19947                   struct Row9;
19948                   struct Row10;"#},
19949        base_text,
19950        &mut cx,
19951    );
19952    assert_hunk_revert(
19953        indoc! {r#"struct Row;
19954                   struct Row2«ˇ;
19955                   struct Row4;
19956                   struct» Row5;
19957                   «struct Row6;
19958
19959                   struct Row8;ˇ»
19960                   struct Row10;"#},
19961        vec![
19962            DiffHunkStatusKind::Deleted,
19963            DiffHunkStatusKind::Deleted,
19964            DiffHunkStatusKind::Deleted,
19965        ],
19966        indoc! {r#"struct Row;
19967                   struct Row1;
19968                   struct Row2«ˇ;
19969
19970                   struct Row4;
19971                   struct» Row5;
19972                   «struct Row6;
19973
19974                   struct Row8;ˇ»
19975                   struct Row9;
19976                   struct Row10;"#},
19977        base_text,
19978        &mut cx,
19979    );
19980}
19981
19982#[gpui::test]
19983async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19984    init_test(cx, |_| {});
19985
19986    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19987    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19988    let base_text_3 =
19989        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19990
19991    let text_1 = edit_first_char_of_every_line(base_text_1);
19992    let text_2 = edit_first_char_of_every_line(base_text_2);
19993    let text_3 = edit_first_char_of_every_line(base_text_3);
19994
19995    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19996    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19997    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19998
19999    let multibuffer = cx.new(|cx| {
20000        let mut multibuffer = MultiBuffer::new(ReadWrite);
20001        multibuffer.push_excerpts(
20002            buffer_1.clone(),
20003            [
20004                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20005                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20006                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20007            ],
20008            cx,
20009        );
20010        multibuffer.push_excerpts(
20011            buffer_2.clone(),
20012            [
20013                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20014                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20015                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20016            ],
20017            cx,
20018        );
20019        multibuffer.push_excerpts(
20020            buffer_3.clone(),
20021            [
20022                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20023                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20024                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20025            ],
20026            cx,
20027        );
20028        multibuffer
20029    });
20030
20031    let fs = FakeFs::new(cx.executor());
20032    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20033    let (editor, cx) = cx
20034        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20035    editor.update_in(cx, |editor, _window, cx| {
20036        for (buffer, diff_base) in [
20037            (buffer_1.clone(), base_text_1),
20038            (buffer_2.clone(), base_text_2),
20039            (buffer_3.clone(), base_text_3),
20040        ] {
20041            let diff = cx.new(|cx| {
20042                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20043            });
20044            editor
20045                .buffer
20046                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20047        }
20048    });
20049    cx.executor().run_until_parked();
20050
20051    editor.update_in(cx, |editor, window, cx| {
20052        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}");
20053        editor.select_all(&SelectAll, window, cx);
20054        editor.git_restore(&Default::default(), window, cx);
20055    });
20056    cx.executor().run_until_parked();
20057
20058    // When all ranges are selected, all buffer hunks are reverted.
20059    editor.update(cx, |editor, cx| {
20060        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");
20061    });
20062    buffer_1.update(cx, |buffer, _| {
20063        assert_eq!(buffer.text(), base_text_1);
20064    });
20065    buffer_2.update(cx, |buffer, _| {
20066        assert_eq!(buffer.text(), base_text_2);
20067    });
20068    buffer_3.update(cx, |buffer, _| {
20069        assert_eq!(buffer.text(), base_text_3);
20070    });
20071
20072    editor.update_in(cx, |editor, window, cx| {
20073        editor.undo(&Default::default(), window, cx);
20074    });
20075
20076    editor.update_in(cx, |editor, window, cx| {
20077        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20078            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20079        });
20080        editor.git_restore(&Default::default(), window, cx);
20081    });
20082
20083    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20084    // but not affect buffer_2 and its related excerpts.
20085    editor.update(cx, |editor, cx| {
20086        assert_eq!(
20087            editor.text(cx),
20088            "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}"
20089        );
20090    });
20091    buffer_1.update(cx, |buffer, _| {
20092        assert_eq!(buffer.text(), base_text_1);
20093    });
20094    buffer_2.update(cx, |buffer, _| {
20095        assert_eq!(
20096            buffer.text(),
20097            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20098        );
20099    });
20100    buffer_3.update(cx, |buffer, _| {
20101        assert_eq!(
20102            buffer.text(),
20103            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20104        );
20105    });
20106
20107    fn edit_first_char_of_every_line(text: &str) -> String {
20108        text.split('\n')
20109            .map(|line| format!("X{}", &line[1..]))
20110            .collect::<Vec<_>>()
20111            .join("\n")
20112    }
20113}
20114
20115#[gpui::test]
20116async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20117    init_test(cx, |_| {});
20118
20119    let cols = 4;
20120    let rows = 10;
20121    let sample_text_1 = sample_text(rows, cols, 'a');
20122    assert_eq!(
20123        sample_text_1,
20124        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20125    );
20126    let sample_text_2 = sample_text(rows, cols, 'l');
20127    assert_eq!(
20128        sample_text_2,
20129        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20130    );
20131    let sample_text_3 = sample_text(rows, cols, 'v');
20132    assert_eq!(
20133        sample_text_3,
20134        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20135    );
20136
20137    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20138    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20139    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20140
20141    let multi_buffer = cx.new(|cx| {
20142        let mut multibuffer = MultiBuffer::new(ReadWrite);
20143        multibuffer.push_excerpts(
20144            buffer_1.clone(),
20145            [
20146                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20147                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20148                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20149            ],
20150            cx,
20151        );
20152        multibuffer.push_excerpts(
20153            buffer_2.clone(),
20154            [
20155                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20156                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20157                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20158            ],
20159            cx,
20160        );
20161        multibuffer.push_excerpts(
20162            buffer_3.clone(),
20163            [
20164                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20165                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20166                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20167            ],
20168            cx,
20169        );
20170        multibuffer
20171    });
20172
20173    let fs = FakeFs::new(cx.executor());
20174    fs.insert_tree(
20175        "/a",
20176        json!({
20177            "main.rs": sample_text_1,
20178            "other.rs": sample_text_2,
20179            "lib.rs": sample_text_3,
20180        }),
20181    )
20182    .await;
20183    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20184    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20185    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20186    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20187        Editor::new(
20188            EditorMode::full(),
20189            multi_buffer,
20190            Some(project.clone()),
20191            window,
20192            cx,
20193        )
20194    });
20195    let multibuffer_item_id = workspace
20196        .update(cx, |workspace, window, cx| {
20197            assert!(
20198                workspace.active_item(cx).is_none(),
20199                "active item should be None before the first item is added"
20200            );
20201            workspace.add_item_to_active_pane(
20202                Box::new(multi_buffer_editor.clone()),
20203                None,
20204                true,
20205                window,
20206                cx,
20207            );
20208            let active_item = workspace
20209                .active_item(cx)
20210                .expect("should have an active item after adding the multi buffer");
20211            assert_eq!(
20212                active_item.buffer_kind(cx),
20213                ItemBufferKind::Multibuffer,
20214                "A multi buffer was expected to active after adding"
20215            );
20216            active_item.item_id()
20217        })
20218        .unwrap();
20219    cx.executor().run_until_parked();
20220
20221    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20222        editor.change_selections(
20223            SelectionEffects::scroll(Autoscroll::Next),
20224            window,
20225            cx,
20226            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20227        );
20228        editor.open_excerpts(&OpenExcerpts, window, cx);
20229    });
20230    cx.executor().run_until_parked();
20231    let first_item_id = workspace
20232        .update(cx, |workspace, window, cx| {
20233            let active_item = workspace
20234                .active_item(cx)
20235                .expect("should have an active item after navigating into the 1st buffer");
20236            let first_item_id = active_item.item_id();
20237            assert_ne!(
20238                first_item_id, multibuffer_item_id,
20239                "Should navigate into the 1st buffer and activate it"
20240            );
20241            assert_eq!(
20242                active_item.buffer_kind(cx),
20243                ItemBufferKind::Singleton,
20244                "New active item should be a singleton buffer"
20245            );
20246            assert_eq!(
20247                active_item
20248                    .act_as::<Editor>(cx)
20249                    .expect("should have navigated into an editor for the 1st buffer")
20250                    .read(cx)
20251                    .text(cx),
20252                sample_text_1
20253            );
20254
20255            workspace
20256                .go_back(workspace.active_pane().downgrade(), window, cx)
20257                .detach_and_log_err(cx);
20258
20259            first_item_id
20260        })
20261        .unwrap();
20262    cx.executor().run_until_parked();
20263    workspace
20264        .update(cx, |workspace, _, cx| {
20265            let active_item = workspace
20266                .active_item(cx)
20267                .expect("should have an active item after navigating back");
20268            assert_eq!(
20269                active_item.item_id(),
20270                multibuffer_item_id,
20271                "Should navigate back to the multi buffer"
20272            );
20273            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20274        })
20275        .unwrap();
20276
20277    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20278        editor.change_selections(
20279            SelectionEffects::scroll(Autoscroll::Next),
20280            window,
20281            cx,
20282            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20283        );
20284        editor.open_excerpts(&OpenExcerpts, window, cx);
20285    });
20286    cx.executor().run_until_parked();
20287    let second_item_id = workspace
20288        .update(cx, |workspace, window, cx| {
20289            let active_item = workspace
20290                .active_item(cx)
20291                .expect("should have an active item after navigating into the 2nd buffer");
20292            let second_item_id = active_item.item_id();
20293            assert_ne!(
20294                second_item_id, multibuffer_item_id,
20295                "Should navigate away from the multibuffer"
20296            );
20297            assert_ne!(
20298                second_item_id, first_item_id,
20299                "Should navigate into the 2nd buffer and activate it"
20300            );
20301            assert_eq!(
20302                active_item.buffer_kind(cx),
20303                ItemBufferKind::Singleton,
20304                "New active item should be a singleton buffer"
20305            );
20306            assert_eq!(
20307                active_item
20308                    .act_as::<Editor>(cx)
20309                    .expect("should have navigated into an editor")
20310                    .read(cx)
20311                    .text(cx),
20312                sample_text_2
20313            );
20314
20315            workspace
20316                .go_back(workspace.active_pane().downgrade(), window, cx)
20317                .detach_and_log_err(cx);
20318
20319            second_item_id
20320        })
20321        .unwrap();
20322    cx.executor().run_until_parked();
20323    workspace
20324        .update(cx, |workspace, _, cx| {
20325            let active_item = workspace
20326                .active_item(cx)
20327                .expect("should have an active item after navigating back from the 2nd buffer");
20328            assert_eq!(
20329                active_item.item_id(),
20330                multibuffer_item_id,
20331                "Should navigate back from the 2nd buffer to the multi buffer"
20332            );
20333            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20334        })
20335        .unwrap();
20336
20337    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20338        editor.change_selections(
20339            SelectionEffects::scroll(Autoscroll::Next),
20340            window,
20341            cx,
20342            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20343        );
20344        editor.open_excerpts(&OpenExcerpts, window, cx);
20345    });
20346    cx.executor().run_until_parked();
20347    workspace
20348        .update(cx, |workspace, window, cx| {
20349            let active_item = workspace
20350                .active_item(cx)
20351                .expect("should have an active item after navigating into the 3rd buffer");
20352            let third_item_id = active_item.item_id();
20353            assert_ne!(
20354                third_item_id, multibuffer_item_id,
20355                "Should navigate into the 3rd buffer and activate it"
20356            );
20357            assert_ne!(third_item_id, first_item_id);
20358            assert_ne!(third_item_id, second_item_id);
20359            assert_eq!(
20360                active_item.buffer_kind(cx),
20361                ItemBufferKind::Singleton,
20362                "New active item should be a singleton buffer"
20363            );
20364            assert_eq!(
20365                active_item
20366                    .act_as::<Editor>(cx)
20367                    .expect("should have navigated into an editor")
20368                    .read(cx)
20369                    .text(cx),
20370                sample_text_3
20371            );
20372
20373            workspace
20374                .go_back(workspace.active_pane().downgrade(), window, cx)
20375                .detach_and_log_err(cx);
20376        })
20377        .unwrap();
20378    cx.executor().run_until_parked();
20379    workspace
20380        .update(cx, |workspace, _, cx| {
20381            let active_item = workspace
20382                .active_item(cx)
20383                .expect("should have an active item after navigating back from the 3rd buffer");
20384            assert_eq!(
20385                active_item.item_id(),
20386                multibuffer_item_id,
20387                "Should navigate back from the 3rd buffer to the multi buffer"
20388            );
20389            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20390        })
20391        .unwrap();
20392}
20393
20394#[gpui::test]
20395async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20396    init_test(cx, |_| {});
20397
20398    let mut cx = EditorTestContext::new(cx).await;
20399
20400    let diff_base = r#"
20401        use some::mod;
20402
20403        const A: u32 = 42;
20404
20405        fn main() {
20406            println!("hello");
20407
20408            println!("world");
20409        }
20410        "#
20411    .unindent();
20412
20413    cx.set_state(
20414        &r#"
20415        use some::modified;
20416
20417        ˇ
20418        fn main() {
20419            println!("hello there");
20420
20421            println!("around the");
20422            println!("world");
20423        }
20424        "#
20425        .unindent(),
20426    );
20427
20428    cx.set_head_text(&diff_base);
20429    executor.run_until_parked();
20430
20431    cx.update_editor(|editor, window, cx| {
20432        editor.go_to_next_hunk(&GoToHunk, window, cx);
20433        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20434    });
20435    executor.run_until_parked();
20436    cx.assert_state_with_diff(
20437        r#"
20438          use some::modified;
20439
20440
20441          fn main() {
20442        -     println!("hello");
20443        + ˇ    println!("hello there");
20444
20445              println!("around the");
20446              println!("world");
20447          }
20448        "#
20449        .unindent(),
20450    );
20451
20452    cx.update_editor(|editor, window, cx| {
20453        for _ in 0..2 {
20454            editor.go_to_next_hunk(&GoToHunk, window, cx);
20455            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20456        }
20457    });
20458    executor.run_until_parked();
20459    cx.assert_state_with_diff(
20460        r#"
20461        - use some::mod;
20462        + ˇuse some::modified;
20463
20464
20465          fn main() {
20466        -     println!("hello");
20467        +     println!("hello there");
20468
20469        +     println!("around the");
20470              println!("world");
20471          }
20472        "#
20473        .unindent(),
20474    );
20475
20476    cx.update_editor(|editor, window, cx| {
20477        editor.go_to_next_hunk(&GoToHunk, window, cx);
20478        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20479    });
20480    executor.run_until_parked();
20481    cx.assert_state_with_diff(
20482        r#"
20483        - use some::mod;
20484        + use some::modified;
20485
20486        - const A: u32 = 42;
20487          ˇ
20488          fn main() {
20489        -     println!("hello");
20490        +     println!("hello there");
20491
20492        +     println!("around the");
20493              println!("world");
20494          }
20495        "#
20496        .unindent(),
20497    );
20498
20499    cx.update_editor(|editor, window, cx| {
20500        editor.cancel(&Cancel, window, cx);
20501    });
20502
20503    cx.assert_state_with_diff(
20504        r#"
20505          use some::modified;
20506
20507          ˇ
20508          fn main() {
20509              println!("hello there");
20510
20511              println!("around the");
20512              println!("world");
20513          }
20514        "#
20515        .unindent(),
20516    );
20517}
20518
20519#[gpui::test]
20520async fn test_diff_base_change_with_expanded_diff_hunks(
20521    executor: BackgroundExecutor,
20522    cx: &mut TestAppContext,
20523) {
20524    init_test(cx, |_| {});
20525
20526    let mut cx = EditorTestContext::new(cx).await;
20527
20528    let diff_base = r#"
20529        use some::mod1;
20530        use some::mod2;
20531
20532        const A: u32 = 42;
20533        const B: u32 = 42;
20534        const C: u32 = 42;
20535
20536        fn main() {
20537            println!("hello");
20538
20539            println!("world");
20540        }
20541        "#
20542    .unindent();
20543
20544    cx.set_state(
20545        &r#"
20546        use some::mod2;
20547
20548        const A: u32 = 42;
20549        const C: u32 = 42;
20550
20551        fn main(ˇ) {
20552            //println!("hello");
20553
20554            println!("world");
20555            //
20556            //
20557        }
20558        "#
20559        .unindent(),
20560    );
20561
20562    cx.set_head_text(&diff_base);
20563    executor.run_until_parked();
20564
20565    cx.update_editor(|editor, window, cx| {
20566        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20567    });
20568    executor.run_until_parked();
20569    cx.assert_state_with_diff(
20570        r#"
20571        - use some::mod1;
20572          use some::mod2;
20573
20574          const A: u32 = 42;
20575        - const B: u32 = 42;
20576          const C: u32 = 42;
20577
20578          fn main(ˇ) {
20579        -     println!("hello");
20580        +     //println!("hello");
20581
20582              println!("world");
20583        +     //
20584        +     //
20585          }
20586        "#
20587        .unindent(),
20588    );
20589
20590    cx.set_head_text("new diff base!");
20591    executor.run_until_parked();
20592    cx.assert_state_with_diff(
20593        r#"
20594        - new diff base!
20595        + use some::mod2;
20596        +
20597        + const A: u32 = 42;
20598        + const C: u32 = 42;
20599        +
20600        + fn main(ˇ) {
20601        +     //println!("hello");
20602        +
20603        +     println!("world");
20604        +     //
20605        +     //
20606        + }
20607        "#
20608        .unindent(),
20609    );
20610}
20611
20612#[gpui::test]
20613async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20614    init_test(cx, |_| {});
20615
20616    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20617    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20618    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20619    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20620    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20621    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20622
20623    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20624    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20625    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20626
20627    let multi_buffer = cx.new(|cx| {
20628        let mut multibuffer = MultiBuffer::new(ReadWrite);
20629        multibuffer.push_excerpts(
20630            buffer_1.clone(),
20631            [
20632                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20633                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20634                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20635            ],
20636            cx,
20637        );
20638        multibuffer.push_excerpts(
20639            buffer_2.clone(),
20640            [
20641                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20642                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20643                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20644            ],
20645            cx,
20646        );
20647        multibuffer.push_excerpts(
20648            buffer_3.clone(),
20649            [
20650                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20651                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20652                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20653            ],
20654            cx,
20655        );
20656        multibuffer
20657    });
20658
20659    let editor =
20660        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20661    editor
20662        .update(cx, |editor, _window, cx| {
20663            for (buffer, diff_base) in [
20664                (buffer_1.clone(), file_1_old),
20665                (buffer_2.clone(), file_2_old),
20666                (buffer_3.clone(), file_3_old),
20667            ] {
20668                let diff = cx.new(|cx| {
20669                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20670                });
20671                editor
20672                    .buffer
20673                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20674            }
20675        })
20676        .unwrap();
20677
20678    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20679    cx.run_until_parked();
20680
20681    cx.assert_editor_state(
20682        &"
20683            ˇaaa
20684            ccc
20685            ddd
20686
20687            ggg
20688            hhh
20689
20690
20691            lll
20692            mmm
20693            NNN
20694
20695            qqq
20696            rrr
20697
20698            uuu
20699            111
20700            222
20701            333
20702
20703            666
20704            777
20705
20706            000
20707            !!!"
20708        .unindent(),
20709    );
20710
20711    cx.update_editor(|editor, window, cx| {
20712        editor.select_all(&SelectAll, window, cx);
20713        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20714    });
20715    cx.executor().run_until_parked();
20716
20717    cx.assert_state_with_diff(
20718        "
20719            «aaa
20720          - bbb
20721            ccc
20722            ddd
20723
20724            ggg
20725            hhh
20726
20727
20728            lll
20729            mmm
20730          - nnn
20731          + NNN
20732
20733            qqq
20734            rrr
20735
20736            uuu
20737            111
20738            222
20739            333
20740
20741          + 666
20742            777
20743
20744            000
20745            !!!ˇ»"
20746            .unindent(),
20747    );
20748}
20749
20750#[gpui::test]
20751async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20752    init_test(cx, |_| {});
20753
20754    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20755    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20756
20757    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20758    let multi_buffer = cx.new(|cx| {
20759        let mut multibuffer = MultiBuffer::new(ReadWrite);
20760        multibuffer.push_excerpts(
20761            buffer.clone(),
20762            [
20763                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20764                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20765                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20766            ],
20767            cx,
20768        );
20769        multibuffer
20770    });
20771
20772    let editor =
20773        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20774    editor
20775        .update(cx, |editor, _window, cx| {
20776            let diff = cx.new(|cx| {
20777                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
20778            });
20779            editor
20780                .buffer
20781                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20782        })
20783        .unwrap();
20784
20785    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20786    cx.run_until_parked();
20787
20788    cx.update_editor(|editor, window, cx| {
20789        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20790    });
20791    cx.executor().run_until_parked();
20792
20793    // When the start of a hunk coincides with the start of its excerpt,
20794    // the hunk is expanded. When the start of a hunk is earlier than
20795    // the start of its excerpt, the hunk is not expanded.
20796    cx.assert_state_with_diff(
20797        "
20798            ˇaaa
20799          - bbb
20800          + BBB
20801
20802          - ddd
20803          - eee
20804          + DDD
20805          + EEE
20806            fff
20807
20808            iii
20809        "
20810        .unindent(),
20811    );
20812}
20813
20814#[gpui::test]
20815async fn test_edits_around_expanded_insertion_hunks(
20816    executor: BackgroundExecutor,
20817    cx: &mut TestAppContext,
20818) {
20819    init_test(cx, |_| {});
20820
20821    let mut cx = EditorTestContext::new(cx).await;
20822
20823    let diff_base = r#"
20824        use some::mod1;
20825        use some::mod2;
20826
20827        const A: u32 = 42;
20828
20829        fn main() {
20830            println!("hello");
20831
20832            println!("world");
20833        }
20834        "#
20835    .unindent();
20836    executor.run_until_parked();
20837    cx.set_state(
20838        &r#"
20839        use some::mod1;
20840        use some::mod2;
20841
20842        const A: u32 = 42;
20843        const B: u32 = 42;
20844        const C: u32 = 42;
20845        ˇ
20846
20847        fn main() {
20848            println!("hello");
20849
20850            println!("world");
20851        }
20852        "#
20853        .unindent(),
20854    );
20855
20856    cx.set_head_text(&diff_base);
20857    executor.run_until_parked();
20858
20859    cx.update_editor(|editor, window, cx| {
20860        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20861    });
20862    executor.run_until_parked();
20863
20864    cx.assert_state_with_diff(
20865        r#"
20866        use some::mod1;
20867        use some::mod2;
20868
20869        const A: u32 = 42;
20870      + const B: u32 = 42;
20871      + const C: u32 = 42;
20872      + ˇ
20873
20874        fn main() {
20875            println!("hello");
20876
20877            println!("world");
20878        }
20879      "#
20880        .unindent(),
20881    );
20882
20883    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20884    executor.run_until_parked();
20885
20886    cx.assert_state_with_diff(
20887        r#"
20888        use some::mod1;
20889        use some::mod2;
20890
20891        const A: u32 = 42;
20892      + const B: u32 = 42;
20893      + const C: u32 = 42;
20894      + const D: u32 = 42;
20895      + ˇ
20896
20897        fn main() {
20898            println!("hello");
20899
20900            println!("world");
20901        }
20902      "#
20903        .unindent(),
20904    );
20905
20906    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20907    executor.run_until_parked();
20908
20909    cx.assert_state_with_diff(
20910        r#"
20911        use some::mod1;
20912        use some::mod2;
20913
20914        const A: u32 = 42;
20915      + const B: u32 = 42;
20916      + const C: u32 = 42;
20917      + const D: u32 = 42;
20918      + const E: u32 = 42;
20919      + ˇ
20920
20921        fn main() {
20922            println!("hello");
20923
20924            println!("world");
20925        }
20926      "#
20927        .unindent(),
20928    );
20929
20930    cx.update_editor(|editor, window, cx| {
20931        editor.delete_line(&DeleteLine, window, cx);
20932    });
20933    executor.run_until_parked();
20934
20935    cx.assert_state_with_diff(
20936        r#"
20937        use some::mod1;
20938        use some::mod2;
20939
20940        const A: u32 = 42;
20941      + const B: u32 = 42;
20942      + const C: u32 = 42;
20943      + const D: u32 = 42;
20944      + const E: u32 = 42;
20945        ˇ
20946        fn main() {
20947            println!("hello");
20948
20949            println!("world");
20950        }
20951      "#
20952        .unindent(),
20953    );
20954
20955    cx.update_editor(|editor, window, cx| {
20956        editor.move_up(&MoveUp, window, cx);
20957        editor.delete_line(&DeleteLine, window, cx);
20958        editor.move_up(&MoveUp, window, cx);
20959        editor.delete_line(&DeleteLine, window, cx);
20960        editor.move_up(&MoveUp, window, cx);
20961        editor.delete_line(&DeleteLine, window, cx);
20962    });
20963    executor.run_until_parked();
20964    cx.assert_state_with_diff(
20965        r#"
20966        use some::mod1;
20967        use some::mod2;
20968
20969        const A: u32 = 42;
20970      + const B: u32 = 42;
20971        ˇ
20972        fn main() {
20973            println!("hello");
20974
20975            println!("world");
20976        }
20977      "#
20978        .unindent(),
20979    );
20980
20981    cx.update_editor(|editor, window, cx| {
20982        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20983        editor.delete_line(&DeleteLine, window, cx);
20984    });
20985    executor.run_until_parked();
20986    cx.assert_state_with_diff(
20987        r#"
20988        ˇ
20989        fn main() {
20990            println!("hello");
20991
20992            println!("world");
20993        }
20994      "#
20995        .unindent(),
20996    );
20997}
20998
20999#[gpui::test]
21000async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21001    init_test(cx, |_| {});
21002
21003    let mut cx = EditorTestContext::new(cx).await;
21004    cx.set_head_text(indoc! { "
21005        one
21006        two
21007        three
21008        four
21009        five
21010        "
21011    });
21012    cx.set_state(indoc! { "
21013        one
21014        ˇthree
21015        five
21016    "});
21017    cx.run_until_parked();
21018    cx.update_editor(|editor, window, cx| {
21019        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21020    });
21021    cx.assert_state_with_diff(
21022        indoc! { "
21023        one
21024      - two
21025        ˇthree
21026      - four
21027        five
21028    "}
21029        .to_string(),
21030    );
21031    cx.update_editor(|editor, window, cx| {
21032        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21033    });
21034
21035    cx.assert_state_with_diff(
21036        indoc! { "
21037        one
21038        ˇthree
21039        five
21040    "}
21041        .to_string(),
21042    );
21043
21044    cx.update_editor(|editor, window, cx| {
21045        editor.move_up(&MoveUp, window, cx);
21046        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21047    });
21048    cx.assert_state_with_diff(
21049        indoc! { "
21050        ˇone
21051      - two
21052        three
21053        five
21054    "}
21055        .to_string(),
21056    );
21057
21058    cx.update_editor(|editor, window, cx| {
21059        editor.move_down(&MoveDown, window, cx);
21060        editor.move_down(&MoveDown, window, cx);
21061        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21062    });
21063    cx.assert_state_with_diff(
21064        indoc! { "
21065        one
21066      - two
21067        ˇthree
21068      - four
21069        five
21070    "}
21071        .to_string(),
21072    );
21073
21074    cx.set_state(indoc! { "
21075        one
21076        ˇTWO
21077        three
21078        four
21079        five
21080    "});
21081    cx.run_until_parked();
21082    cx.update_editor(|editor, window, cx| {
21083        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21084    });
21085
21086    cx.assert_state_with_diff(
21087        indoc! { "
21088            one
21089          - two
21090          + ˇTWO
21091            three
21092            four
21093            five
21094        "}
21095        .to_string(),
21096    );
21097    cx.update_editor(|editor, window, cx| {
21098        editor.move_up(&Default::default(), window, cx);
21099        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21100    });
21101    cx.assert_state_with_diff(
21102        indoc! { "
21103            one
21104            ˇTWO
21105            three
21106            four
21107            five
21108        "}
21109        .to_string(),
21110    );
21111}
21112
21113#[gpui::test]
21114async fn test_toggling_adjacent_diff_hunks_2(
21115    executor: BackgroundExecutor,
21116    cx: &mut TestAppContext,
21117) {
21118    init_test(cx, |_| {});
21119
21120    let mut cx = EditorTestContext::new(cx).await;
21121
21122    let diff_base = r#"
21123        lineA
21124        lineB
21125        lineC
21126        lineD
21127        "#
21128    .unindent();
21129
21130    cx.set_state(
21131        &r#"
21132        ˇlineA1
21133        lineB
21134        lineD
21135        "#
21136        .unindent(),
21137    );
21138    cx.set_head_text(&diff_base);
21139    executor.run_until_parked();
21140
21141    cx.update_editor(|editor, window, cx| {
21142        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21143    });
21144    executor.run_until_parked();
21145    cx.assert_state_with_diff(
21146        r#"
21147        - lineA
21148        + ˇlineA1
21149          lineB
21150          lineD
21151        "#
21152        .unindent(),
21153    );
21154
21155    cx.update_editor(|editor, window, cx| {
21156        editor.move_down(&MoveDown, window, cx);
21157        editor.move_right(&MoveRight, window, cx);
21158        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21159    });
21160    executor.run_until_parked();
21161    cx.assert_state_with_diff(
21162        r#"
21163        - lineA
21164        + lineA1
21165          lˇineB
21166        - lineC
21167          lineD
21168        "#
21169        .unindent(),
21170    );
21171}
21172
21173#[gpui::test]
21174async fn test_edits_around_expanded_deletion_hunks(
21175    executor: BackgroundExecutor,
21176    cx: &mut TestAppContext,
21177) {
21178    init_test(cx, |_| {});
21179
21180    let mut cx = EditorTestContext::new(cx).await;
21181
21182    let diff_base = r#"
21183        use some::mod1;
21184        use some::mod2;
21185
21186        const A: u32 = 42;
21187        const B: u32 = 42;
21188        const C: u32 = 42;
21189
21190
21191        fn main() {
21192            println!("hello");
21193
21194            println!("world");
21195        }
21196    "#
21197    .unindent();
21198    executor.run_until_parked();
21199    cx.set_state(
21200        &r#"
21201        use some::mod1;
21202        use some::mod2;
21203
21204        ˇconst B: u32 = 42;
21205        const C: u32 = 42;
21206
21207
21208        fn main() {
21209            println!("hello");
21210
21211            println!("world");
21212        }
21213        "#
21214        .unindent(),
21215    );
21216
21217    cx.set_head_text(&diff_base);
21218    executor.run_until_parked();
21219
21220    cx.update_editor(|editor, window, cx| {
21221        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21222    });
21223    executor.run_until_parked();
21224
21225    cx.assert_state_with_diff(
21226        r#"
21227        use some::mod1;
21228        use some::mod2;
21229
21230      - const A: u32 = 42;
21231        ˇconst B: u32 = 42;
21232        const C: u32 = 42;
21233
21234
21235        fn main() {
21236            println!("hello");
21237
21238            println!("world");
21239        }
21240      "#
21241        .unindent(),
21242    );
21243
21244    cx.update_editor(|editor, window, cx| {
21245        editor.delete_line(&DeleteLine, window, cx);
21246    });
21247    executor.run_until_parked();
21248    cx.assert_state_with_diff(
21249        r#"
21250        use some::mod1;
21251        use some::mod2;
21252
21253      - const A: u32 = 42;
21254      - const B: u32 = 42;
21255        ˇconst C: u32 = 42;
21256
21257
21258        fn main() {
21259            println!("hello");
21260
21261            println!("world");
21262        }
21263      "#
21264        .unindent(),
21265    );
21266
21267    cx.update_editor(|editor, window, cx| {
21268        editor.delete_line(&DeleteLine, window, cx);
21269    });
21270    executor.run_until_parked();
21271    cx.assert_state_with_diff(
21272        r#"
21273        use some::mod1;
21274        use some::mod2;
21275
21276      - const A: u32 = 42;
21277      - const B: u32 = 42;
21278      - const C: u32 = 42;
21279        ˇ
21280
21281        fn main() {
21282            println!("hello");
21283
21284            println!("world");
21285        }
21286      "#
21287        .unindent(),
21288    );
21289
21290    cx.update_editor(|editor, window, cx| {
21291        editor.handle_input("replacement", window, cx);
21292    });
21293    executor.run_until_parked();
21294    cx.assert_state_with_diff(
21295        r#"
21296        use some::mod1;
21297        use some::mod2;
21298
21299      - const A: u32 = 42;
21300      - const B: u32 = 42;
21301      - const C: u32 = 42;
21302      -
21303      + replacementˇ
21304
21305        fn main() {
21306            println!("hello");
21307
21308            println!("world");
21309        }
21310      "#
21311        .unindent(),
21312    );
21313}
21314
21315#[gpui::test]
21316async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21317    init_test(cx, |_| {});
21318
21319    let mut cx = EditorTestContext::new(cx).await;
21320
21321    let base_text = r#"
21322        one
21323        two
21324        three
21325        four
21326        five
21327    "#
21328    .unindent();
21329    executor.run_until_parked();
21330    cx.set_state(
21331        &r#"
21332        one
21333        two
21334        fˇour
21335        five
21336        "#
21337        .unindent(),
21338    );
21339
21340    cx.set_head_text(&base_text);
21341    executor.run_until_parked();
21342
21343    cx.update_editor(|editor, window, cx| {
21344        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21345    });
21346    executor.run_until_parked();
21347
21348    cx.assert_state_with_diff(
21349        r#"
21350          one
21351          two
21352        - three
21353          fˇour
21354          five
21355        "#
21356        .unindent(),
21357    );
21358
21359    cx.update_editor(|editor, window, cx| {
21360        editor.backspace(&Backspace, window, cx);
21361        editor.backspace(&Backspace, window, cx);
21362    });
21363    executor.run_until_parked();
21364    cx.assert_state_with_diff(
21365        r#"
21366          one
21367          two
21368        - threeˇ
21369        - four
21370        + our
21371          five
21372        "#
21373        .unindent(),
21374    );
21375}
21376
21377#[gpui::test]
21378async fn test_edit_after_expanded_modification_hunk(
21379    executor: BackgroundExecutor,
21380    cx: &mut TestAppContext,
21381) {
21382    init_test(cx, |_| {});
21383
21384    let mut cx = EditorTestContext::new(cx).await;
21385
21386    let diff_base = r#"
21387        use some::mod1;
21388        use some::mod2;
21389
21390        const A: u32 = 42;
21391        const B: u32 = 42;
21392        const C: u32 = 42;
21393        const D: u32 = 42;
21394
21395
21396        fn main() {
21397            println!("hello");
21398
21399            println!("world");
21400        }"#
21401    .unindent();
21402
21403    cx.set_state(
21404        &r#"
21405        use some::mod1;
21406        use some::mod2;
21407
21408        const A: u32 = 42;
21409        const B: u32 = 42;
21410        const C: u32 = 43ˇ
21411        const D: u32 = 42;
21412
21413
21414        fn main() {
21415            println!("hello");
21416
21417            println!("world");
21418        }"#
21419        .unindent(),
21420    );
21421
21422    cx.set_head_text(&diff_base);
21423    executor.run_until_parked();
21424    cx.update_editor(|editor, window, cx| {
21425        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21426    });
21427    executor.run_until_parked();
21428
21429    cx.assert_state_with_diff(
21430        r#"
21431        use some::mod1;
21432        use some::mod2;
21433
21434        const A: u32 = 42;
21435        const B: u32 = 42;
21436      - const C: u32 = 42;
21437      + const C: u32 = 43ˇ
21438        const D: u32 = 42;
21439
21440
21441        fn main() {
21442            println!("hello");
21443
21444            println!("world");
21445        }"#
21446        .unindent(),
21447    );
21448
21449    cx.update_editor(|editor, window, cx| {
21450        editor.handle_input("\nnew_line\n", window, cx);
21451    });
21452    executor.run_until_parked();
21453
21454    cx.assert_state_with_diff(
21455        r#"
21456        use some::mod1;
21457        use some::mod2;
21458
21459        const A: u32 = 42;
21460        const B: u32 = 42;
21461      - const C: u32 = 42;
21462      + const C: u32 = 43
21463      + new_line
21464      + ˇ
21465        const D: u32 = 42;
21466
21467
21468        fn main() {
21469            println!("hello");
21470
21471            println!("world");
21472        }"#
21473        .unindent(),
21474    );
21475}
21476
21477#[gpui::test]
21478async fn test_stage_and_unstage_added_file_hunk(
21479    executor: BackgroundExecutor,
21480    cx: &mut TestAppContext,
21481) {
21482    init_test(cx, |_| {});
21483
21484    let mut cx = EditorTestContext::new(cx).await;
21485    cx.update_editor(|editor, _, cx| {
21486        editor.set_expand_all_diff_hunks(cx);
21487    });
21488
21489    let working_copy = r#"
21490            ˇfn main() {
21491                println!("hello, world!");
21492            }
21493        "#
21494    .unindent();
21495
21496    cx.set_state(&working_copy);
21497    executor.run_until_parked();
21498
21499    cx.assert_state_with_diff(
21500        r#"
21501            + ˇfn main() {
21502            +     println!("hello, world!");
21503            + }
21504        "#
21505        .unindent(),
21506    );
21507    cx.assert_index_text(None);
21508
21509    cx.update_editor(|editor, window, cx| {
21510        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21511    });
21512    executor.run_until_parked();
21513    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21514    cx.assert_state_with_diff(
21515        r#"
21516            + ˇfn main() {
21517            +     println!("hello, world!");
21518            + }
21519        "#
21520        .unindent(),
21521    );
21522
21523    cx.update_editor(|editor, window, cx| {
21524        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21525    });
21526    executor.run_until_parked();
21527    cx.assert_index_text(None);
21528}
21529
21530async fn setup_indent_guides_editor(
21531    text: &str,
21532    cx: &mut TestAppContext,
21533) -> (BufferId, EditorTestContext) {
21534    init_test(cx, |_| {});
21535
21536    let mut cx = EditorTestContext::new(cx).await;
21537
21538    let buffer_id = cx.update_editor(|editor, window, cx| {
21539        editor.set_text(text, window, cx);
21540        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21541
21542        buffer_ids[0]
21543    });
21544
21545    (buffer_id, cx)
21546}
21547
21548fn assert_indent_guides(
21549    range: Range<u32>,
21550    expected: Vec<IndentGuide>,
21551    active_indices: Option<Vec<usize>>,
21552    cx: &mut EditorTestContext,
21553) {
21554    let indent_guides = cx.update_editor(|editor, window, cx| {
21555        let snapshot = editor.snapshot(window, cx).display_snapshot;
21556        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21557            editor,
21558            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21559            true,
21560            &snapshot,
21561            cx,
21562        );
21563
21564        indent_guides.sort_by(|a, b| {
21565            a.depth.cmp(&b.depth).then(
21566                a.start_row
21567                    .cmp(&b.start_row)
21568                    .then(a.end_row.cmp(&b.end_row)),
21569            )
21570        });
21571        indent_guides
21572    });
21573
21574    if let Some(expected) = active_indices {
21575        let active_indices = cx.update_editor(|editor, window, cx| {
21576            let snapshot = editor.snapshot(window, cx).display_snapshot;
21577            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21578        });
21579
21580        assert_eq!(
21581            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21582            expected,
21583            "Active indent guide indices do not match"
21584        );
21585    }
21586
21587    assert_eq!(indent_guides, expected, "Indent guides do not match");
21588}
21589
21590fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21591    IndentGuide {
21592        buffer_id,
21593        start_row: MultiBufferRow(start_row),
21594        end_row: MultiBufferRow(end_row),
21595        depth,
21596        tab_size: 4,
21597        settings: IndentGuideSettings {
21598            enabled: true,
21599            line_width: 1,
21600            active_line_width: 1,
21601            coloring: IndentGuideColoring::default(),
21602            background_coloring: IndentGuideBackgroundColoring::default(),
21603        },
21604    }
21605}
21606
21607#[gpui::test]
21608async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21609    let (buffer_id, mut cx) = setup_indent_guides_editor(
21610        &"
21611        fn main() {
21612            let a = 1;
21613        }"
21614        .unindent(),
21615        cx,
21616    )
21617    .await;
21618
21619    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21620}
21621
21622#[gpui::test]
21623async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21624    let (buffer_id, mut cx) = setup_indent_guides_editor(
21625        &"
21626        fn main() {
21627            let a = 1;
21628            let b = 2;
21629        }"
21630        .unindent(),
21631        cx,
21632    )
21633    .await;
21634
21635    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21636}
21637
21638#[gpui::test]
21639async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21640    let (buffer_id, mut cx) = setup_indent_guides_editor(
21641        &"
21642        fn main() {
21643            let a = 1;
21644            if a == 3 {
21645                let b = 2;
21646            } else {
21647                let c = 3;
21648            }
21649        }"
21650        .unindent(),
21651        cx,
21652    )
21653    .await;
21654
21655    assert_indent_guides(
21656        0..8,
21657        vec![
21658            indent_guide(buffer_id, 1, 6, 0),
21659            indent_guide(buffer_id, 3, 3, 1),
21660            indent_guide(buffer_id, 5, 5, 1),
21661        ],
21662        None,
21663        &mut cx,
21664    );
21665}
21666
21667#[gpui::test]
21668async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21669    let (buffer_id, mut cx) = setup_indent_guides_editor(
21670        &"
21671        fn main() {
21672            let a = 1;
21673                let b = 2;
21674            let c = 3;
21675        }"
21676        .unindent(),
21677        cx,
21678    )
21679    .await;
21680
21681    assert_indent_guides(
21682        0..5,
21683        vec![
21684            indent_guide(buffer_id, 1, 3, 0),
21685            indent_guide(buffer_id, 2, 2, 1),
21686        ],
21687        None,
21688        &mut cx,
21689    );
21690}
21691
21692#[gpui::test]
21693async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21694    let (buffer_id, mut cx) = setup_indent_guides_editor(
21695        &"
21696        fn main() {
21697            let a = 1;
21698
21699            let c = 3;
21700        }"
21701        .unindent(),
21702        cx,
21703    )
21704    .await;
21705
21706    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21707}
21708
21709#[gpui::test]
21710async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21711    let (buffer_id, mut cx) = setup_indent_guides_editor(
21712        &"
21713        fn main() {
21714            let a = 1;
21715
21716            let c = 3;
21717
21718            if a == 3 {
21719                let b = 2;
21720            } else {
21721                let c = 3;
21722            }
21723        }"
21724        .unindent(),
21725        cx,
21726    )
21727    .await;
21728
21729    assert_indent_guides(
21730        0..11,
21731        vec![
21732            indent_guide(buffer_id, 1, 9, 0),
21733            indent_guide(buffer_id, 6, 6, 1),
21734            indent_guide(buffer_id, 8, 8, 1),
21735        ],
21736        None,
21737        &mut cx,
21738    );
21739}
21740
21741#[gpui::test]
21742async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21743    let (buffer_id, mut cx) = setup_indent_guides_editor(
21744        &"
21745        fn main() {
21746            let a = 1;
21747
21748            let c = 3;
21749
21750            if a == 3 {
21751                let b = 2;
21752            } else {
21753                let c = 3;
21754            }
21755        }"
21756        .unindent(),
21757        cx,
21758    )
21759    .await;
21760
21761    assert_indent_guides(
21762        1..11,
21763        vec![
21764            indent_guide(buffer_id, 1, 9, 0),
21765            indent_guide(buffer_id, 6, 6, 1),
21766            indent_guide(buffer_id, 8, 8, 1),
21767        ],
21768        None,
21769        &mut cx,
21770    );
21771}
21772
21773#[gpui::test]
21774async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21775    let (buffer_id, mut cx) = setup_indent_guides_editor(
21776        &"
21777        fn main() {
21778            let a = 1;
21779
21780            let c = 3;
21781
21782            if a == 3 {
21783                let b = 2;
21784            } else {
21785                let c = 3;
21786            }
21787        }"
21788        .unindent(),
21789        cx,
21790    )
21791    .await;
21792
21793    assert_indent_guides(
21794        1..10,
21795        vec![
21796            indent_guide(buffer_id, 1, 9, 0),
21797            indent_guide(buffer_id, 6, 6, 1),
21798            indent_guide(buffer_id, 8, 8, 1),
21799        ],
21800        None,
21801        &mut cx,
21802    );
21803}
21804
21805#[gpui::test]
21806async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21807    let (buffer_id, mut cx) = setup_indent_guides_editor(
21808        &"
21809        fn main() {
21810            if a {
21811                b(
21812                    c,
21813                    d,
21814                )
21815            } else {
21816                e(
21817                    f
21818                )
21819            }
21820        }"
21821        .unindent(),
21822        cx,
21823    )
21824    .await;
21825
21826    assert_indent_guides(
21827        0..11,
21828        vec![
21829            indent_guide(buffer_id, 1, 10, 0),
21830            indent_guide(buffer_id, 2, 5, 1),
21831            indent_guide(buffer_id, 7, 9, 1),
21832            indent_guide(buffer_id, 3, 4, 2),
21833            indent_guide(buffer_id, 8, 8, 2),
21834        ],
21835        None,
21836        &mut cx,
21837    );
21838
21839    cx.update_editor(|editor, window, cx| {
21840        editor.fold_at(MultiBufferRow(2), window, cx);
21841        assert_eq!(
21842            editor.display_text(cx),
21843            "
21844            fn main() {
21845                if a {
21846                    b(⋯
21847                    )
21848                } else {
21849                    e(
21850                        f
21851                    )
21852                }
21853            }"
21854            .unindent()
21855        );
21856    });
21857
21858    assert_indent_guides(
21859        0..11,
21860        vec![
21861            indent_guide(buffer_id, 1, 10, 0),
21862            indent_guide(buffer_id, 2, 5, 1),
21863            indent_guide(buffer_id, 7, 9, 1),
21864            indent_guide(buffer_id, 8, 8, 2),
21865        ],
21866        None,
21867        &mut cx,
21868    );
21869}
21870
21871#[gpui::test]
21872async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21873    let (buffer_id, mut cx) = setup_indent_guides_editor(
21874        &"
21875        block1
21876            block2
21877                block3
21878                    block4
21879            block2
21880        block1
21881        block1"
21882            .unindent(),
21883        cx,
21884    )
21885    .await;
21886
21887    assert_indent_guides(
21888        1..10,
21889        vec![
21890            indent_guide(buffer_id, 1, 4, 0),
21891            indent_guide(buffer_id, 2, 3, 1),
21892            indent_guide(buffer_id, 3, 3, 2),
21893        ],
21894        None,
21895        &mut cx,
21896    );
21897}
21898
21899#[gpui::test]
21900async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21901    let (buffer_id, mut cx) = setup_indent_guides_editor(
21902        &"
21903        block1
21904            block2
21905                block3
21906
21907        block1
21908        block1"
21909            .unindent(),
21910        cx,
21911    )
21912    .await;
21913
21914    assert_indent_guides(
21915        0..6,
21916        vec![
21917            indent_guide(buffer_id, 1, 2, 0),
21918            indent_guide(buffer_id, 2, 2, 1),
21919        ],
21920        None,
21921        &mut cx,
21922    );
21923}
21924
21925#[gpui::test]
21926async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21927    let (buffer_id, mut cx) = setup_indent_guides_editor(
21928        &"
21929        function component() {
21930        \treturn (
21931        \t\t\t
21932        \t\t<div>
21933        \t\t\t<abc></abc>
21934        \t\t</div>
21935        \t)
21936        }"
21937        .unindent(),
21938        cx,
21939    )
21940    .await;
21941
21942    assert_indent_guides(
21943        0..8,
21944        vec![
21945            indent_guide(buffer_id, 1, 6, 0),
21946            indent_guide(buffer_id, 2, 5, 1),
21947            indent_guide(buffer_id, 4, 4, 2),
21948        ],
21949        None,
21950        &mut cx,
21951    );
21952}
21953
21954#[gpui::test]
21955async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21956    let (buffer_id, mut cx) = setup_indent_guides_editor(
21957        &"
21958        function component() {
21959        \treturn (
21960        \t
21961        \t\t<div>
21962        \t\t\t<abc></abc>
21963        \t\t</div>
21964        \t)
21965        }"
21966        .unindent(),
21967        cx,
21968    )
21969    .await;
21970
21971    assert_indent_guides(
21972        0..8,
21973        vec![
21974            indent_guide(buffer_id, 1, 6, 0),
21975            indent_guide(buffer_id, 2, 5, 1),
21976            indent_guide(buffer_id, 4, 4, 2),
21977        ],
21978        None,
21979        &mut cx,
21980    );
21981}
21982
21983#[gpui::test]
21984async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21985    let (buffer_id, mut cx) = setup_indent_guides_editor(
21986        &"
21987        block1
21988
21989
21990
21991            block2
21992        "
21993        .unindent(),
21994        cx,
21995    )
21996    .await;
21997
21998    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21999}
22000
22001#[gpui::test]
22002async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22003    let (buffer_id, mut cx) = setup_indent_guides_editor(
22004        &"
22005        def a:
22006        \tb = 3
22007        \tif True:
22008        \t\tc = 4
22009        \t\td = 5
22010        \tprint(b)
22011        "
22012        .unindent(),
22013        cx,
22014    )
22015    .await;
22016
22017    assert_indent_guides(
22018        0..6,
22019        vec![
22020            indent_guide(buffer_id, 1, 5, 0),
22021            indent_guide(buffer_id, 3, 4, 1),
22022        ],
22023        None,
22024        &mut cx,
22025    );
22026}
22027
22028#[gpui::test]
22029async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22030    let (buffer_id, mut cx) = setup_indent_guides_editor(
22031        &"
22032    fn main() {
22033        let a = 1;
22034    }"
22035        .unindent(),
22036        cx,
22037    )
22038    .await;
22039
22040    cx.update_editor(|editor, window, cx| {
22041        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22042            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22043        });
22044    });
22045
22046    assert_indent_guides(
22047        0..3,
22048        vec![indent_guide(buffer_id, 1, 1, 0)],
22049        Some(vec![0]),
22050        &mut cx,
22051    );
22052}
22053
22054#[gpui::test]
22055async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22056    let (buffer_id, mut cx) = setup_indent_guides_editor(
22057        &"
22058    fn main() {
22059        if 1 == 2 {
22060            let a = 1;
22061        }
22062    }"
22063        .unindent(),
22064        cx,
22065    )
22066    .await;
22067
22068    cx.update_editor(|editor, window, cx| {
22069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22070            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22071        });
22072    });
22073
22074    assert_indent_guides(
22075        0..4,
22076        vec![
22077            indent_guide(buffer_id, 1, 3, 0),
22078            indent_guide(buffer_id, 2, 2, 1),
22079        ],
22080        Some(vec![1]),
22081        &mut cx,
22082    );
22083
22084    cx.update_editor(|editor, window, cx| {
22085        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22086            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22087        });
22088    });
22089
22090    assert_indent_guides(
22091        0..4,
22092        vec![
22093            indent_guide(buffer_id, 1, 3, 0),
22094            indent_guide(buffer_id, 2, 2, 1),
22095        ],
22096        Some(vec![1]),
22097        &mut cx,
22098    );
22099
22100    cx.update_editor(|editor, window, cx| {
22101        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22102            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22103        });
22104    });
22105
22106    assert_indent_guides(
22107        0..4,
22108        vec![
22109            indent_guide(buffer_id, 1, 3, 0),
22110            indent_guide(buffer_id, 2, 2, 1),
22111        ],
22112        Some(vec![0]),
22113        &mut cx,
22114    );
22115}
22116
22117#[gpui::test]
22118async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22119    let (buffer_id, mut cx) = setup_indent_guides_editor(
22120        &"
22121    fn main() {
22122        let a = 1;
22123
22124        let b = 2;
22125    }"
22126        .unindent(),
22127        cx,
22128    )
22129    .await;
22130
22131    cx.update_editor(|editor, window, cx| {
22132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22133            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22134        });
22135    });
22136
22137    assert_indent_guides(
22138        0..5,
22139        vec![indent_guide(buffer_id, 1, 3, 0)],
22140        Some(vec![0]),
22141        &mut cx,
22142    );
22143}
22144
22145#[gpui::test]
22146async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22147    let (buffer_id, mut cx) = setup_indent_guides_editor(
22148        &"
22149    def m:
22150        a = 1
22151        pass"
22152            .unindent(),
22153        cx,
22154    )
22155    .await;
22156
22157    cx.update_editor(|editor, window, cx| {
22158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22159            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22160        });
22161    });
22162
22163    assert_indent_guides(
22164        0..3,
22165        vec![indent_guide(buffer_id, 1, 2, 0)],
22166        Some(vec![0]),
22167        &mut cx,
22168    );
22169}
22170
22171#[gpui::test]
22172async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22173    init_test(cx, |_| {});
22174    let mut cx = EditorTestContext::new(cx).await;
22175    let text = indoc! {
22176        "
22177        impl A {
22178            fn b() {
22179                0;
22180                3;
22181                5;
22182                6;
22183                7;
22184            }
22185        }
22186        "
22187    };
22188    let base_text = indoc! {
22189        "
22190        impl A {
22191            fn b() {
22192                0;
22193                1;
22194                2;
22195                3;
22196                4;
22197            }
22198            fn c() {
22199                5;
22200                6;
22201                7;
22202            }
22203        }
22204        "
22205    };
22206
22207    cx.update_editor(|editor, window, cx| {
22208        editor.set_text(text, window, cx);
22209
22210        editor.buffer().update(cx, |multibuffer, cx| {
22211            let buffer = multibuffer.as_singleton().unwrap();
22212            let diff = cx.new(|cx| {
22213                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22214            });
22215
22216            multibuffer.set_all_diff_hunks_expanded(cx);
22217            multibuffer.add_diff(diff, cx);
22218
22219            buffer.read(cx).remote_id()
22220        })
22221    });
22222    cx.run_until_parked();
22223
22224    cx.assert_state_with_diff(
22225        indoc! { "
22226          impl A {
22227              fn b() {
22228                  0;
22229        -         1;
22230        -         2;
22231                  3;
22232        -         4;
22233        -     }
22234        -     fn c() {
22235                  5;
22236                  6;
22237                  7;
22238              }
22239          }
22240          ˇ"
22241        }
22242        .to_string(),
22243    );
22244
22245    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22246        editor
22247            .snapshot(window, cx)
22248            .buffer_snapshot()
22249            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22250            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22251            .collect::<Vec<_>>()
22252    });
22253    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22254    assert_eq!(
22255        actual_guides,
22256        vec![
22257            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22258            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22259            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22260        ]
22261    );
22262}
22263
22264#[gpui::test]
22265async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22266    init_test(cx, |_| {});
22267    let mut cx = EditorTestContext::new(cx).await;
22268
22269    let diff_base = r#"
22270        a
22271        b
22272        c
22273        "#
22274    .unindent();
22275
22276    cx.set_state(
22277        &r#"
22278        ˇA
22279        b
22280        C
22281        "#
22282        .unindent(),
22283    );
22284    cx.set_head_text(&diff_base);
22285    cx.update_editor(|editor, window, cx| {
22286        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22287    });
22288    executor.run_until_parked();
22289
22290    let both_hunks_expanded = r#"
22291        - a
22292        + ˇA
22293          b
22294        - c
22295        + C
22296        "#
22297    .unindent();
22298
22299    cx.assert_state_with_diff(both_hunks_expanded.clone());
22300
22301    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22302        let snapshot = editor.snapshot(window, cx);
22303        let hunks = editor
22304            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22305            .collect::<Vec<_>>();
22306        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22307        hunks
22308            .into_iter()
22309            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22310            .collect::<Vec<_>>()
22311    });
22312    assert_eq!(hunk_ranges.len(), 2);
22313
22314    cx.update_editor(|editor, _, cx| {
22315        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22316    });
22317    executor.run_until_parked();
22318
22319    let second_hunk_expanded = r#"
22320          ˇA
22321          b
22322        - c
22323        + C
22324        "#
22325    .unindent();
22326
22327    cx.assert_state_with_diff(second_hunk_expanded);
22328
22329    cx.update_editor(|editor, _, cx| {
22330        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22331    });
22332    executor.run_until_parked();
22333
22334    cx.assert_state_with_diff(both_hunks_expanded.clone());
22335
22336    cx.update_editor(|editor, _, cx| {
22337        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22338    });
22339    executor.run_until_parked();
22340
22341    let first_hunk_expanded = r#"
22342        - a
22343        + ˇA
22344          b
22345          C
22346        "#
22347    .unindent();
22348
22349    cx.assert_state_with_diff(first_hunk_expanded);
22350
22351    cx.update_editor(|editor, _, cx| {
22352        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22353    });
22354    executor.run_until_parked();
22355
22356    cx.assert_state_with_diff(both_hunks_expanded);
22357
22358    cx.set_state(
22359        &r#"
22360        ˇA
22361        b
22362        "#
22363        .unindent(),
22364    );
22365    cx.run_until_parked();
22366
22367    // TODO this cursor position seems bad
22368    cx.assert_state_with_diff(
22369        r#"
22370        - ˇa
22371        + A
22372          b
22373        "#
22374        .unindent(),
22375    );
22376
22377    cx.update_editor(|editor, window, cx| {
22378        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22379    });
22380
22381    cx.assert_state_with_diff(
22382        r#"
22383            - ˇa
22384            + A
22385              b
22386            - c
22387            "#
22388        .unindent(),
22389    );
22390
22391    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22392        let snapshot = editor.snapshot(window, cx);
22393        let hunks = editor
22394            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22395            .collect::<Vec<_>>();
22396        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22397        hunks
22398            .into_iter()
22399            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22400            .collect::<Vec<_>>()
22401    });
22402    assert_eq!(hunk_ranges.len(), 2);
22403
22404    cx.update_editor(|editor, _, cx| {
22405        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22406    });
22407    executor.run_until_parked();
22408
22409    cx.assert_state_with_diff(
22410        r#"
22411        - ˇa
22412        + A
22413          b
22414        "#
22415        .unindent(),
22416    );
22417}
22418
22419#[gpui::test]
22420async fn test_toggle_deletion_hunk_at_start_of_file(
22421    executor: BackgroundExecutor,
22422    cx: &mut TestAppContext,
22423) {
22424    init_test(cx, |_| {});
22425    let mut cx = EditorTestContext::new(cx).await;
22426
22427    let diff_base = r#"
22428        a
22429        b
22430        c
22431        "#
22432    .unindent();
22433
22434    cx.set_state(
22435        &r#"
22436        ˇb
22437        c
22438        "#
22439        .unindent(),
22440    );
22441    cx.set_head_text(&diff_base);
22442    cx.update_editor(|editor, window, cx| {
22443        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22444    });
22445    executor.run_until_parked();
22446
22447    let hunk_expanded = r#"
22448        - a
22449          ˇb
22450          c
22451        "#
22452    .unindent();
22453
22454    cx.assert_state_with_diff(hunk_expanded.clone());
22455
22456    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22457        let snapshot = editor.snapshot(window, cx);
22458        let hunks = editor
22459            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22460            .collect::<Vec<_>>();
22461        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22462        hunks
22463            .into_iter()
22464            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22465            .collect::<Vec<_>>()
22466    });
22467    assert_eq!(hunk_ranges.len(), 1);
22468
22469    cx.update_editor(|editor, _, cx| {
22470        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22471    });
22472    executor.run_until_parked();
22473
22474    let hunk_collapsed = r#"
22475          ˇb
22476          c
22477        "#
22478    .unindent();
22479
22480    cx.assert_state_with_diff(hunk_collapsed);
22481
22482    cx.update_editor(|editor, _, cx| {
22483        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22484    });
22485    executor.run_until_parked();
22486
22487    cx.assert_state_with_diff(hunk_expanded);
22488}
22489
22490#[gpui::test]
22491async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22492    executor: BackgroundExecutor,
22493    cx: &mut TestAppContext,
22494) {
22495    init_test(cx, |_| {});
22496    let mut cx = EditorTestContext::new(cx).await;
22497
22498    cx.set_state("ˇnew\nsecond\nthird\n");
22499    cx.set_head_text("old\nsecond\nthird\n");
22500    cx.update_editor(|editor, window, cx| {
22501        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22502    });
22503    executor.run_until_parked();
22504    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22505
22506    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22507    cx.update_editor(|editor, window, cx| {
22508        let snapshot = editor.snapshot(window, cx);
22509        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22510        let hunks = editor
22511            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22512            .collect::<Vec<_>>();
22513        assert_eq!(hunks.len(), 1);
22514        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22515        editor.toggle_single_diff_hunk(hunk_range, cx)
22516    });
22517    executor.run_until_parked();
22518    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22519
22520    // Keep the editor scrolled to the top so the full hunk remains visible.
22521    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22522}
22523
22524#[gpui::test]
22525async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22526    init_test(cx, |_| {});
22527
22528    let fs = FakeFs::new(cx.executor());
22529    fs.insert_tree(
22530        path!("/test"),
22531        json!({
22532            ".git": {},
22533            "file-1": "ONE\n",
22534            "file-2": "TWO\n",
22535            "file-3": "THREE\n",
22536        }),
22537    )
22538    .await;
22539
22540    fs.set_head_for_repo(
22541        path!("/test/.git").as_ref(),
22542        &[
22543            ("file-1", "one\n".into()),
22544            ("file-2", "two\n".into()),
22545            ("file-3", "three\n".into()),
22546        ],
22547        "deadbeef",
22548    );
22549
22550    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22551    let mut buffers = vec![];
22552    for i in 1..=3 {
22553        let buffer = project
22554            .update(cx, |project, cx| {
22555                let path = format!(path!("/test/file-{}"), i);
22556                project.open_local_buffer(path, cx)
22557            })
22558            .await
22559            .unwrap();
22560        buffers.push(buffer);
22561    }
22562
22563    let multibuffer = cx.new(|cx| {
22564        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22565        multibuffer.set_all_diff_hunks_expanded(cx);
22566        for buffer in &buffers {
22567            let snapshot = buffer.read(cx).snapshot();
22568            multibuffer.set_excerpts_for_path(
22569                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22570                buffer.clone(),
22571                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22572                2,
22573                cx,
22574            );
22575        }
22576        multibuffer
22577    });
22578
22579    let editor = cx.add_window(|window, cx| {
22580        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22581    });
22582    cx.run_until_parked();
22583
22584    let snapshot = editor
22585        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22586        .unwrap();
22587    let hunks = snapshot
22588        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22589        .map(|hunk| match hunk {
22590            DisplayDiffHunk::Unfolded {
22591                display_row_range, ..
22592            } => display_row_range,
22593            DisplayDiffHunk::Folded { .. } => unreachable!(),
22594        })
22595        .collect::<Vec<_>>();
22596    assert_eq!(
22597        hunks,
22598        [
22599            DisplayRow(2)..DisplayRow(4),
22600            DisplayRow(7)..DisplayRow(9),
22601            DisplayRow(12)..DisplayRow(14),
22602        ]
22603    );
22604}
22605
22606#[gpui::test]
22607async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22608    init_test(cx, |_| {});
22609
22610    let mut cx = EditorTestContext::new(cx).await;
22611    cx.set_head_text(indoc! { "
22612        one
22613        two
22614        three
22615        four
22616        five
22617        "
22618    });
22619    cx.set_index_text(indoc! { "
22620        one
22621        two
22622        three
22623        four
22624        five
22625        "
22626    });
22627    cx.set_state(indoc! {"
22628        one
22629        TWO
22630        ˇTHREE
22631        FOUR
22632        five
22633    "});
22634    cx.run_until_parked();
22635    cx.update_editor(|editor, window, cx| {
22636        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22637    });
22638    cx.run_until_parked();
22639    cx.assert_index_text(Some(indoc! {"
22640        one
22641        TWO
22642        THREE
22643        FOUR
22644        five
22645    "}));
22646    cx.set_state(indoc! { "
22647        one
22648        TWO
22649        ˇTHREE-HUNDRED
22650        FOUR
22651        five
22652    "});
22653    cx.run_until_parked();
22654    cx.update_editor(|editor, window, cx| {
22655        let snapshot = editor.snapshot(window, cx);
22656        let hunks = editor
22657            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22658            .collect::<Vec<_>>();
22659        assert_eq!(hunks.len(), 1);
22660        assert_eq!(
22661            hunks[0].status(),
22662            DiffHunkStatus {
22663                kind: DiffHunkStatusKind::Modified,
22664                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22665            }
22666        );
22667
22668        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22669    });
22670    cx.run_until_parked();
22671    cx.assert_index_text(Some(indoc! {"
22672        one
22673        TWO
22674        THREE-HUNDRED
22675        FOUR
22676        five
22677    "}));
22678}
22679
22680#[gpui::test]
22681fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22682    init_test(cx, |_| {});
22683
22684    let editor = cx.add_window(|window, cx| {
22685        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22686        build_editor(buffer, window, cx)
22687    });
22688
22689    let render_args = Arc::new(Mutex::new(None));
22690    let snapshot = editor
22691        .update(cx, |editor, window, cx| {
22692            let snapshot = editor.buffer().read(cx).snapshot(cx);
22693            let range =
22694                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22695
22696            struct RenderArgs {
22697                row: MultiBufferRow,
22698                folded: bool,
22699                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22700            }
22701
22702            let crease = Crease::inline(
22703                range,
22704                FoldPlaceholder::test(),
22705                {
22706                    let toggle_callback = render_args.clone();
22707                    move |row, folded, callback, _window, _cx| {
22708                        *toggle_callback.lock() = Some(RenderArgs {
22709                            row,
22710                            folded,
22711                            callback,
22712                        });
22713                        div()
22714                    }
22715                },
22716                |_row, _folded, _window, _cx| div(),
22717            );
22718
22719            editor.insert_creases(Some(crease), cx);
22720            let snapshot = editor.snapshot(window, cx);
22721            let _div =
22722                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22723            snapshot
22724        })
22725        .unwrap();
22726
22727    let render_args = render_args.lock().take().unwrap();
22728    assert_eq!(render_args.row, MultiBufferRow(1));
22729    assert!(!render_args.folded);
22730    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22731
22732    cx.update_window(*editor, |_, window, cx| {
22733        (render_args.callback)(true, window, cx)
22734    })
22735    .unwrap();
22736    let snapshot = editor
22737        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22738        .unwrap();
22739    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22740
22741    cx.update_window(*editor, |_, window, cx| {
22742        (render_args.callback)(false, window, cx)
22743    })
22744    .unwrap();
22745    let snapshot = editor
22746        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22747        .unwrap();
22748    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22749}
22750
22751#[gpui::test]
22752async fn test_input_text(cx: &mut TestAppContext) {
22753    init_test(cx, |_| {});
22754    let mut cx = EditorTestContext::new(cx).await;
22755
22756    cx.set_state(
22757        &r#"ˇone
22758        two
22759
22760        three
22761        fourˇ
22762        five
22763
22764        siˇx"#
22765            .unindent(),
22766    );
22767
22768    cx.dispatch_action(HandleInput(String::new()));
22769    cx.assert_editor_state(
22770        &r#"ˇone
22771        two
22772
22773        three
22774        fourˇ
22775        five
22776
22777        siˇx"#
22778            .unindent(),
22779    );
22780
22781    cx.dispatch_action(HandleInput("AAAA".to_string()));
22782    cx.assert_editor_state(
22783        &r#"AAAAˇone
22784        two
22785
22786        three
22787        fourAAAAˇ
22788        five
22789
22790        siAAAAˇx"#
22791            .unindent(),
22792    );
22793}
22794
22795#[gpui::test]
22796async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22797    init_test(cx, |_| {});
22798
22799    let mut cx = EditorTestContext::new(cx).await;
22800    cx.set_state(
22801        r#"let foo = 1;
22802let foo = 2;
22803let foo = 3;
22804let fooˇ = 4;
22805let foo = 5;
22806let foo = 6;
22807let foo = 7;
22808let foo = 8;
22809let foo = 9;
22810let foo = 10;
22811let foo = 11;
22812let foo = 12;
22813let foo = 13;
22814let foo = 14;
22815let foo = 15;"#,
22816    );
22817
22818    cx.update_editor(|e, window, cx| {
22819        assert_eq!(
22820            e.next_scroll_position,
22821            NextScrollCursorCenterTopBottom::Center,
22822            "Default next scroll direction is center",
22823        );
22824
22825        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22826        assert_eq!(
22827            e.next_scroll_position,
22828            NextScrollCursorCenterTopBottom::Top,
22829            "After center, next scroll direction should be top",
22830        );
22831
22832        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22833        assert_eq!(
22834            e.next_scroll_position,
22835            NextScrollCursorCenterTopBottom::Bottom,
22836            "After top, next scroll direction should be bottom",
22837        );
22838
22839        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22840        assert_eq!(
22841            e.next_scroll_position,
22842            NextScrollCursorCenterTopBottom::Center,
22843            "After bottom, scrolling should start over",
22844        );
22845
22846        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22847        assert_eq!(
22848            e.next_scroll_position,
22849            NextScrollCursorCenterTopBottom::Top,
22850            "Scrolling continues if retriggered fast enough"
22851        );
22852    });
22853
22854    cx.executor()
22855        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22856    cx.executor().run_until_parked();
22857    cx.update_editor(|e, _, _| {
22858        assert_eq!(
22859            e.next_scroll_position,
22860            NextScrollCursorCenterTopBottom::Center,
22861            "If scrolling is not triggered fast enough, it should reset"
22862        );
22863    });
22864}
22865
22866#[gpui::test]
22867async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22868    init_test(cx, |_| {});
22869    let mut cx = EditorLspTestContext::new_rust(
22870        lsp::ServerCapabilities {
22871            definition_provider: Some(lsp::OneOf::Left(true)),
22872            references_provider: Some(lsp::OneOf::Left(true)),
22873            ..lsp::ServerCapabilities::default()
22874        },
22875        cx,
22876    )
22877    .await;
22878
22879    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22880        let go_to_definition = cx
22881            .lsp
22882            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22883                move |params, _| async move {
22884                    if empty_go_to_definition {
22885                        Ok(None)
22886                    } else {
22887                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22888                            uri: params.text_document_position_params.text_document.uri,
22889                            range: lsp::Range::new(
22890                                lsp::Position::new(4, 3),
22891                                lsp::Position::new(4, 6),
22892                            ),
22893                        })))
22894                    }
22895                },
22896            );
22897        let references = cx
22898            .lsp
22899            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22900                Ok(Some(vec![lsp::Location {
22901                    uri: params.text_document_position.text_document.uri,
22902                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22903                }]))
22904            });
22905        (go_to_definition, references)
22906    };
22907
22908    cx.set_state(
22909        &r#"fn one() {
22910            let mut a = ˇtwo();
22911        }
22912
22913        fn two() {}"#
22914            .unindent(),
22915    );
22916    set_up_lsp_handlers(false, &mut cx);
22917    let navigated = cx
22918        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22919        .await
22920        .expect("Failed to navigate to definition");
22921    assert_eq!(
22922        navigated,
22923        Navigated::Yes,
22924        "Should have navigated to definition from the GetDefinition response"
22925    );
22926    cx.assert_editor_state(
22927        &r#"fn one() {
22928            let mut a = two();
22929        }
22930
22931        fn «twoˇ»() {}"#
22932            .unindent(),
22933    );
22934
22935    let editors = cx.update_workspace(|workspace, _, cx| {
22936        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22937    });
22938    cx.update_editor(|_, _, test_editor_cx| {
22939        assert_eq!(
22940            editors.len(),
22941            1,
22942            "Initially, only one, test, editor should be open in the workspace"
22943        );
22944        assert_eq!(
22945            test_editor_cx.entity(),
22946            editors.last().expect("Asserted len is 1").clone()
22947        );
22948    });
22949
22950    set_up_lsp_handlers(true, &mut cx);
22951    let navigated = cx
22952        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22953        .await
22954        .expect("Failed to navigate to lookup references");
22955    assert_eq!(
22956        navigated,
22957        Navigated::Yes,
22958        "Should have navigated to references as a fallback after empty GoToDefinition response"
22959    );
22960    // We should not change the selections in the existing file,
22961    // if opening another milti buffer with the references
22962    cx.assert_editor_state(
22963        &r#"fn one() {
22964            let mut a = two();
22965        }
22966
22967        fn «twoˇ»() {}"#
22968            .unindent(),
22969    );
22970    let editors = cx.update_workspace(|workspace, _, cx| {
22971        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22972    });
22973    cx.update_editor(|_, _, test_editor_cx| {
22974        assert_eq!(
22975            editors.len(),
22976            2,
22977            "After falling back to references search, we open a new editor with the results"
22978        );
22979        let references_fallback_text = editors
22980            .into_iter()
22981            .find(|new_editor| *new_editor != test_editor_cx.entity())
22982            .expect("Should have one non-test editor now")
22983            .read(test_editor_cx)
22984            .text(test_editor_cx);
22985        assert_eq!(
22986            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22987            "Should use the range from the references response and not the GoToDefinition one"
22988        );
22989    });
22990}
22991
22992#[gpui::test]
22993async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22994    init_test(cx, |_| {});
22995    cx.update(|cx| {
22996        let mut editor_settings = EditorSettings::get_global(cx).clone();
22997        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22998        EditorSettings::override_global(editor_settings, cx);
22999    });
23000    let mut cx = EditorLspTestContext::new_rust(
23001        lsp::ServerCapabilities {
23002            definition_provider: Some(lsp::OneOf::Left(true)),
23003            references_provider: Some(lsp::OneOf::Left(true)),
23004            ..lsp::ServerCapabilities::default()
23005        },
23006        cx,
23007    )
23008    .await;
23009    let original_state = r#"fn one() {
23010        let mut a = ˇtwo();
23011    }
23012
23013    fn two() {}"#
23014        .unindent();
23015    cx.set_state(&original_state);
23016
23017    let mut go_to_definition = cx
23018        .lsp
23019        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23020            move |_, _| async move { Ok(None) },
23021        );
23022    let _references = cx
23023        .lsp
23024        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23025            panic!("Should not call for references with no go to definition fallback")
23026        });
23027
23028    let navigated = cx
23029        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23030        .await
23031        .expect("Failed to navigate to lookup references");
23032    go_to_definition
23033        .next()
23034        .await
23035        .expect("Should have called the go_to_definition handler");
23036
23037    assert_eq!(
23038        navigated,
23039        Navigated::No,
23040        "Should have navigated to references as a fallback after empty GoToDefinition response"
23041    );
23042    cx.assert_editor_state(&original_state);
23043    let editors = cx.update_workspace(|workspace, _, cx| {
23044        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23045    });
23046    cx.update_editor(|_, _, _| {
23047        assert_eq!(
23048            editors.len(),
23049            1,
23050            "After unsuccessful fallback, no other editor should have been opened"
23051        );
23052    });
23053}
23054
23055#[gpui::test]
23056async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23057    init_test(cx, |_| {});
23058    let mut cx = EditorLspTestContext::new_rust(
23059        lsp::ServerCapabilities {
23060            references_provider: Some(lsp::OneOf::Left(true)),
23061            ..lsp::ServerCapabilities::default()
23062        },
23063        cx,
23064    )
23065    .await;
23066
23067    cx.set_state(
23068        &r#"
23069        fn one() {
23070            let mut a = two();
23071        }
23072
23073        fn ˇtwo() {}"#
23074            .unindent(),
23075    );
23076    cx.lsp
23077        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23078            Ok(Some(vec![
23079                lsp::Location {
23080                    uri: params.text_document_position.text_document.uri.clone(),
23081                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23082                },
23083                lsp::Location {
23084                    uri: params.text_document_position.text_document.uri,
23085                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23086                },
23087            ]))
23088        });
23089    let navigated = cx
23090        .update_editor(|editor, window, cx| {
23091            editor.find_all_references(&FindAllReferences::default(), window, cx)
23092        })
23093        .unwrap()
23094        .await
23095        .expect("Failed to navigate to references");
23096    assert_eq!(
23097        navigated,
23098        Navigated::Yes,
23099        "Should have navigated to references from the FindAllReferences response"
23100    );
23101    cx.assert_editor_state(
23102        &r#"fn one() {
23103            let mut a = two();
23104        }
23105
23106        fn ˇtwo() {}"#
23107            .unindent(),
23108    );
23109
23110    let editors = cx.update_workspace(|workspace, _, cx| {
23111        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23112    });
23113    cx.update_editor(|_, _, _| {
23114        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23115    });
23116
23117    cx.set_state(
23118        &r#"fn one() {
23119            let mut a = ˇtwo();
23120        }
23121
23122        fn two() {}"#
23123            .unindent(),
23124    );
23125    let navigated = cx
23126        .update_editor(|editor, window, cx| {
23127            editor.find_all_references(&FindAllReferences::default(), window, cx)
23128        })
23129        .unwrap()
23130        .await
23131        .expect("Failed to navigate to references");
23132    assert_eq!(
23133        navigated,
23134        Navigated::Yes,
23135        "Should have navigated to references from the FindAllReferences response"
23136    );
23137    cx.assert_editor_state(
23138        &r#"fn one() {
23139            let mut a = ˇtwo();
23140        }
23141
23142        fn two() {}"#
23143            .unindent(),
23144    );
23145    let editors = cx.update_workspace(|workspace, _, cx| {
23146        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23147    });
23148    cx.update_editor(|_, _, _| {
23149        assert_eq!(
23150            editors.len(),
23151            2,
23152            "should have re-used the previous multibuffer"
23153        );
23154    });
23155
23156    cx.set_state(
23157        &r#"fn one() {
23158            let mut a = ˇtwo();
23159        }
23160        fn three() {}
23161        fn two() {}"#
23162            .unindent(),
23163    );
23164    cx.lsp
23165        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23166            Ok(Some(vec![
23167                lsp::Location {
23168                    uri: params.text_document_position.text_document.uri.clone(),
23169                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23170                },
23171                lsp::Location {
23172                    uri: params.text_document_position.text_document.uri,
23173                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23174                },
23175            ]))
23176        });
23177    let navigated = cx
23178        .update_editor(|editor, window, cx| {
23179            editor.find_all_references(&FindAllReferences::default(), window, cx)
23180        })
23181        .unwrap()
23182        .await
23183        .expect("Failed to navigate to references");
23184    assert_eq!(
23185        navigated,
23186        Navigated::Yes,
23187        "Should have navigated to references from the FindAllReferences response"
23188    );
23189    cx.assert_editor_state(
23190        &r#"fn one() {
23191                let mut a = ˇtwo();
23192            }
23193            fn three() {}
23194            fn two() {}"#
23195            .unindent(),
23196    );
23197    let editors = cx.update_workspace(|workspace, _, cx| {
23198        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23199    });
23200    cx.update_editor(|_, _, _| {
23201        assert_eq!(
23202            editors.len(),
23203            3,
23204            "should have used a new multibuffer as offsets changed"
23205        );
23206    });
23207}
23208#[gpui::test]
23209async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23210    init_test(cx, |_| {});
23211
23212    let language = Arc::new(Language::new(
23213        LanguageConfig::default(),
23214        Some(tree_sitter_rust::LANGUAGE.into()),
23215    ));
23216
23217    let text = r#"
23218        #[cfg(test)]
23219        mod tests() {
23220            #[test]
23221            fn runnable_1() {
23222                let a = 1;
23223            }
23224
23225            #[test]
23226            fn runnable_2() {
23227                let a = 1;
23228                let b = 2;
23229            }
23230        }
23231    "#
23232    .unindent();
23233
23234    let fs = FakeFs::new(cx.executor());
23235    fs.insert_file("/file.rs", Default::default()).await;
23236
23237    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23238    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23239    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23240    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23241    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23242
23243    let editor = cx.new_window_entity(|window, cx| {
23244        Editor::new(
23245            EditorMode::full(),
23246            multi_buffer,
23247            Some(project.clone()),
23248            window,
23249            cx,
23250        )
23251    });
23252
23253    editor.update_in(cx, |editor, window, cx| {
23254        let snapshot = editor.buffer().read(cx).snapshot(cx);
23255        editor.tasks.insert(
23256            (buffer.read(cx).remote_id(), 3),
23257            RunnableTasks {
23258                templates: vec![],
23259                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23260                column: 0,
23261                extra_variables: HashMap::default(),
23262                context_range: BufferOffset(43)..BufferOffset(85),
23263            },
23264        );
23265        editor.tasks.insert(
23266            (buffer.read(cx).remote_id(), 8),
23267            RunnableTasks {
23268                templates: vec![],
23269                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23270                column: 0,
23271                extra_variables: HashMap::default(),
23272                context_range: BufferOffset(86)..BufferOffset(191),
23273            },
23274        );
23275
23276        // Test finding task when cursor is inside function body
23277        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23278            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23279        });
23280        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23281        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23282
23283        // Test finding task when cursor is on function name
23284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23285            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23286        });
23287        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23288        assert_eq!(row, 8, "Should find task when cursor is on function name");
23289    });
23290}
23291
23292#[gpui::test]
23293async fn test_folding_buffers(cx: &mut TestAppContext) {
23294    init_test(cx, |_| {});
23295
23296    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23297    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23298    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23299
23300    let fs = FakeFs::new(cx.executor());
23301    fs.insert_tree(
23302        path!("/a"),
23303        json!({
23304            "first.rs": sample_text_1,
23305            "second.rs": sample_text_2,
23306            "third.rs": sample_text_3,
23307        }),
23308    )
23309    .await;
23310    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23311    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23312    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23313    let worktree = project.update(cx, |project, cx| {
23314        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23315        assert_eq!(worktrees.len(), 1);
23316        worktrees.pop().unwrap()
23317    });
23318    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23319
23320    let buffer_1 = project
23321        .update(cx, |project, cx| {
23322            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23323        })
23324        .await
23325        .unwrap();
23326    let buffer_2 = project
23327        .update(cx, |project, cx| {
23328            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23329        })
23330        .await
23331        .unwrap();
23332    let buffer_3 = project
23333        .update(cx, |project, cx| {
23334            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23335        })
23336        .await
23337        .unwrap();
23338
23339    let multi_buffer = cx.new(|cx| {
23340        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23341        multi_buffer.push_excerpts(
23342            buffer_1.clone(),
23343            [
23344                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23345                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23346                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23347            ],
23348            cx,
23349        );
23350        multi_buffer.push_excerpts(
23351            buffer_2.clone(),
23352            [
23353                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23354                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23355                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23356            ],
23357            cx,
23358        );
23359        multi_buffer.push_excerpts(
23360            buffer_3.clone(),
23361            [
23362                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23363                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23364                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23365            ],
23366            cx,
23367        );
23368        multi_buffer
23369    });
23370    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23371        Editor::new(
23372            EditorMode::full(),
23373            multi_buffer.clone(),
23374            Some(project.clone()),
23375            window,
23376            cx,
23377        )
23378    });
23379
23380    assert_eq!(
23381        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23382        "\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",
23383    );
23384
23385    multi_buffer_editor.update(cx, |editor, cx| {
23386        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23387    });
23388    assert_eq!(
23389        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23390        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23391        "After folding the first buffer, its text should not be displayed"
23392    );
23393
23394    multi_buffer_editor.update(cx, |editor, cx| {
23395        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23396    });
23397    assert_eq!(
23398        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23399        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23400        "After folding the second buffer, its text should not be displayed"
23401    );
23402
23403    multi_buffer_editor.update(cx, |editor, cx| {
23404        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23405    });
23406    assert_eq!(
23407        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23408        "\n\n\n\n\n",
23409        "After folding the third buffer, its text should not be displayed"
23410    );
23411
23412    // Emulate selection inside the fold logic, that should work
23413    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23414        editor
23415            .snapshot(window, cx)
23416            .next_line_boundary(Point::new(0, 4));
23417    });
23418
23419    multi_buffer_editor.update(cx, |editor, cx| {
23420        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23421    });
23422    assert_eq!(
23423        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23424        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23425        "After unfolding the second buffer, its text should be displayed"
23426    );
23427
23428    // Typing inside of buffer 1 causes that buffer to be unfolded.
23429    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23430        assert_eq!(
23431            multi_buffer
23432                .read(cx)
23433                .snapshot(cx)
23434                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23435                .collect::<String>(),
23436            "bbbb"
23437        );
23438        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23439            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23440        });
23441        editor.handle_input("B", window, cx);
23442    });
23443
23444    assert_eq!(
23445        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23446        "\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",
23447        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23448    );
23449
23450    multi_buffer_editor.update(cx, |editor, cx| {
23451        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23452    });
23453    assert_eq!(
23454        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23455        "\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",
23456        "After unfolding the all buffers, all original text should be displayed"
23457    );
23458}
23459
23460#[gpui::test]
23461async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23462    init_test(cx, |_| {});
23463
23464    let sample_text_1 = "1111\n2222\n3333".to_string();
23465    let sample_text_2 = "4444\n5555\n6666".to_string();
23466    let sample_text_3 = "7777\n8888\n9999".to_string();
23467
23468    let fs = FakeFs::new(cx.executor());
23469    fs.insert_tree(
23470        path!("/a"),
23471        json!({
23472            "first.rs": sample_text_1,
23473            "second.rs": sample_text_2,
23474            "third.rs": sample_text_3,
23475        }),
23476    )
23477    .await;
23478    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23479    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23480    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23481    let worktree = project.update(cx, |project, cx| {
23482        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23483        assert_eq!(worktrees.len(), 1);
23484        worktrees.pop().unwrap()
23485    });
23486    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23487
23488    let buffer_1 = project
23489        .update(cx, |project, cx| {
23490            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23491        })
23492        .await
23493        .unwrap();
23494    let buffer_2 = project
23495        .update(cx, |project, cx| {
23496            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23497        })
23498        .await
23499        .unwrap();
23500    let buffer_3 = project
23501        .update(cx, |project, cx| {
23502            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23503        })
23504        .await
23505        .unwrap();
23506
23507    let multi_buffer = cx.new(|cx| {
23508        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23509        multi_buffer.push_excerpts(
23510            buffer_1.clone(),
23511            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23512            cx,
23513        );
23514        multi_buffer.push_excerpts(
23515            buffer_2.clone(),
23516            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23517            cx,
23518        );
23519        multi_buffer.push_excerpts(
23520            buffer_3.clone(),
23521            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23522            cx,
23523        );
23524        multi_buffer
23525    });
23526
23527    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23528        Editor::new(
23529            EditorMode::full(),
23530            multi_buffer,
23531            Some(project.clone()),
23532            window,
23533            cx,
23534        )
23535    });
23536
23537    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23538    assert_eq!(
23539        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23540        full_text,
23541    );
23542
23543    multi_buffer_editor.update(cx, |editor, cx| {
23544        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23545    });
23546    assert_eq!(
23547        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23548        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23549        "After folding the first buffer, its text should not be displayed"
23550    );
23551
23552    multi_buffer_editor.update(cx, |editor, cx| {
23553        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23554    });
23555
23556    assert_eq!(
23557        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23558        "\n\n\n\n\n\n7777\n8888\n9999",
23559        "After folding the second buffer, its text should not be displayed"
23560    );
23561
23562    multi_buffer_editor.update(cx, |editor, cx| {
23563        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23564    });
23565    assert_eq!(
23566        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23567        "\n\n\n\n\n",
23568        "After folding the third buffer, its text should not be displayed"
23569    );
23570
23571    multi_buffer_editor.update(cx, |editor, cx| {
23572        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23573    });
23574    assert_eq!(
23575        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23576        "\n\n\n\n4444\n5555\n6666\n\n",
23577        "After unfolding the second buffer, its text should be displayed"
23578    );
23579
23580    multi_buffer_editor.update(cx, |editor, cx| {
23581        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23582    });
23583    assert_eq!(
23584        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23585        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23586        "After unfolding the first buffer, its text should be displayed"
23587    );
23588
23589    multi_buffer_editor.update(cx, |editor, cx| {
23590        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23591    });
23592    assert_eq!(
23593        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23594        full_text,
23595        "After unfolding all buffers, all original text should be displayed"
23596    );
23597}
23598
23599#[gpui::test]
23600async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23601    init_test(cx, |_| {});
23602
23603    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23604
23605    let fs = FakeFs::new(cx.executor());
23606    fs.insert_tree(
23607        path!("/a"),
23608        json!({
23609            "main.rs": sample_text,
23610        }),
23611    )
23612    .await;
23613    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23614    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23615    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23616    let worktree = project.update(cx, |project, cx| {
23617        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23618        assert_eq!(worktrees.len(), 1);
23619        worktrees.pop().unwrap()
23620    });
23621    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23622
23623    let buffer_1 = project
23624        .update(cx, |project, cx| {
23625            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23626        })
23627        .await
23628        .unwrap();
23629
23630    let multi_buffer = cx.new(|cx| {
23631        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23632        multi_buffer.push_excerpts(
23633            buffer_1.clone(),
23634            [ExcerptRange::new(
23635                Point::new(0, 0)
23636                    ..Point::new(
23637                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23638                        0,
23639                    ),
23640            )],
23641            cx,
23642        );
23643        multi_buffer
23644    });
23645    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23646        Editor::new(
23647            EditorMode::full(),
23648            multi_buffer,
23649            Some(project.clone()),
23650            window,
23651            cx,
23652        )
23653    });
23654
23655    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23656    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23657        enum TestHighlight {}
23658        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23659        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23660        editor.highlight_text::<TestHighlight>(
23661            vec![highlight_range.clone()],
23662            HighlightStyle::color(Hsla::green()),
23663            cx,
23664        );
23665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23666            s.select_ranges(Some(highlight_range))
23667        });
23668    });
23669
23670    let full_text = format!("\n\n{sample_text}");
23671    assert_eq!(
23672        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23673        full_text,
23674    );
23675}
23676
23677#[gpui::test]
23678async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23679    init_test(cx, |_| {});
23680    cx.update(|cx| {
23681        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23682            "keymaps/default-linux.json",
23683            cx,
23684        )
23685        .unwrap();
23686        cx.bind_keys(default_key_bindings);
23687    });
23688
23689    let (editor, cx) = cx.add_window_view(|window, cx| {
23690        let multi_buffer = MultiBuffer::build_multi(
23691            [
23692                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23693                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23694                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23695                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23696            ],
23697            cx,
23698        );
23699        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23700
23701        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23702        // fold all but the second buffer, so that we test navigating between two
23703        // adjacent folded buffers, as well as folded buffers at the start and
23704        // end the multibuffer
23705        editor.fold_buffer(buffer_ids[0], cx);
23706        editor.fold_buffer(buffer_ids[2], cx);
23707        editor.fold_buffer(buffer_ids[3], cx);
23708
23709        editor
23710    });
23711    cx.simulate_resize(size(px(1000.), px(1000.)));
23712
23713    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23714    cx.assert_excerpts_with_selections(indoc! {"
23715        [EXCERPT]
23716        ˇ[FOLDED]
23717        [EXCERPT]
23718        a1
23719        b1
23720        [EXCERPT]
23721        [FOLDED]
23722        [EXCERPT]
23723        [FOLDED]
23724        "
23725    });
23726    cx.simulate_keystroke("down");
23727    cx.assert_excerpts_with_selections(indoc! {"
23728        [EXCERPT]
23729        [FOLDED]
23730        [EXCERPT]
23731        ˇa1
23732        b1
23733        [EXCERPT]
23734        [FOLDED]
23735        [EXCERPT]
23736        [FOLDED]
23737        "
23738    });
23739    cx.simulate_keystroke("down");
23740    cx.assert_excerpts_with_selections(indoc! {"
23741        [EXCERPT]
23742        [FOLDED]
23743        [EXCERPT]
23744        a1
23745        ˇb1
23746        [EXCERPT]
23747        [FOLDED]
23748        [EXCERPT]
23749        [FOLDED]
23750        "
23751    });
23752    cx.simulate_keystroke("down");
23753    cx.assert_excerpts_with_selections(indoc! {"
23754        [EXCERPT]
23755        [FOLDED]
23756        [EXCERPT]
23757        a1
23758        b1
23759        ˇ[EXCERPT]
23760        [FOLDED]
23761        [EXCERPT]
23762        [FOLDED]
23763        "
23764    });
23765    cx.simulate_keystroke("down");
23766    cx.assert_excerpts_with_selections(indoc! {"
23767        [EXCERPT]
23768        [FOLDED]
23769        [EXCERPT]
23770        a1
23771        b1
23772        [EXCERPT]
23773        ˇ[FOLDED]
23774        [EXCERPT]
23775        [FOLDED]
23776        "
23777    });
23778    for _ in 0..5 {
23779        cx.simulate_keystroke("down");
23780        cx.assert_excerpts_with_selections(indoc! {"
23781            [EXCERPT]
23782            [FOLDED]
23783            [EXCERPT]
23784            a1
23785            b1
23786            [EXCERPT]
23787            [FOLDED]
23788            [EXCERPT]
23789            ˇ[FOLDED]
23790            "
23791        });
23792    }
23793
23794    cx.simulate_keystroke("up");
23795    cx.assert_excerpts_with_selections(indoc! {"
23796        [EXCERPT]
23797        [FOLDED]
23798        [EXCERPT]
23799        a1
23800        b1
23801        [EXCERPT]
23802        ˇ[FOLDED]
23803        [EXCERPT]
23804        [FOLDED]
23805        "
23806    });
23807    cx.simulate_keystroke("up");
23808    cx.assert_excerpts_with_selections(indoc! {"
23809        [EXCERPT]
23810        [FOLDED]
23811        [EXCERPT]
23812        a1
23813        b1
23814        ˇ[EXCERPT]
23815        [FOLDED]
23816        [EXCERPT]
23817        [FOLDED]
23818        "
23819    });
23820    cx.simulate_keystroke("up");
23821    cx.assert_excerpts_with_selections(indoc! {"
23822        [EXCERPT]
23823        [FOLDED]
23824        [EXCERPT]
23825        a1
23826        ˇb1
23827        [EXCERPT]
23828        [FOLDED]
23829        [EXCERPT]
23830        [FOLDED]
23831        "
23832    });
23833    cx.simulate_keystroke("up");
23834    cx.assert_excerpts_with_selections(indoc! {"
23835        [EXCERPT]
23836        [FOLDED]
23837        [EXCERPT]
23838        ˇa1
23839        b1
23840        [EXCERPT]
23841        [FOLDED]
23842        [EXCERPT]
23843        [FOLDED]
23844        "
23845    });
23846    for _ in 0..5 {
23847        cx.simulate_keystroke("up");
23848        cx.assert_excerpts_with_selections(indoc! {"
23849            [EXCERPT]
23850            ˇ[FOLDED]
23851            [EXCERPT]
23852            a1
23853            b1
23854            [EXCERPT]
23855            [FOLDED]
23856            [EXCERPT]
23857            [FOLDED]
23858            "
23859        });
23860    }
23861}
23862
23863#[gpui::test]
23864async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23865    init_test(cx, |_| {});
23866
23867    // Simple insertion
23868    assert_highlighted_edits(
23869        "Hello, world!",
23870        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23871        true,
23872        cx,
23873        |highlighted_edits, cx| {
23874            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23875            assert_eq!(highlighted_edits.highlights.len(), 1);
23876            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23877            assert_eq!(
23878                highlighted_edits.highlights[0].1.background_color,
23879                Some(cx.theme().status().created_background)
23880            );
23881        },
23882    )
23883    .await;
23884
23885    // Replacement
23886    assert_highlighted_edits(
23887        "This is a test.",
23888        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23889        false,
23890        cx,
23891        |highlighted_edits, cx| {
23892            assert_eq!(highlighted_edits.text, "That is a test.");
23893            assert_eq!(highlighted_edits.highlights.len(), 1);
23894            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23895            assert_eq!(
23896                highlighted_edits.highlights[0].1.background_color,
23897                Some(cx.theme().status().created_background)
23898            );
23899        },
23900    )
23901    .await;
23902
23903    // Multiple edits
23904    assert_highlighted_edits(
23905        "Hello, world!",
23906        vec![
23907            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23908            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23909        ],
23910        false,
23911        cx,
23912        |highlighted_edits, cx| {
23913            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23914            assert_eq!(highlighted_edits.highlights.len(), 2);
23915            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23916            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23917            assert_eq!(
23918                highlighted_edits.highlights[0].1.background_color,
23919                Some(cx.theme().status().created_background)
23920            );
23921            assert_eq!(
23922                highlighted_edits.highlights[1].1.background_color,
23923                Some(cx.theme().status().created_background)
23924            );
23925        },
23926    )
23927    .await;
23928
23929    // Multiple lines with edits
23930    assert_highlighted_edits(
23931        "First line\nSecond line\nThird line\nFourth line",
23932        vec![
23933            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23934            (
23935                Point::new(2, 0)..Point::new(2, 10),
23936                "New third line".to_string(),
23937            ),
23938            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23939        ],
23940        false,
23941        cx,
23942        |highlighted_edits, cx| {
23943            assert_eq!(
23944                highlighted_edits.text,
23945                "Second modified\nNew third line\nFourth updated line"
23946            );
23947            assert_eq!(highlighted_edits.highlights.len(), 3);
23948            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23949            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23950            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23951            for highlight in &highlighted_edits.highlights {
23952                assert_eq!(
23953                    highlight.1.background_color,
23954                    Some(cx.theme().status().created_background)
23955                );
23956            }
23957        },
23958    )
23959    .await;
23960}
23961
23962#[gpui::test]
23963async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23964    init_test(cx, |_| {});
23965
23966    // Deletion
23967    assert_highlighted_edits(
23968        "Hello, world!",
23969        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23970        true,
23971        cx,
23972        |highlighted_edits, cx| {
23973            assert_eq!(highlighted_edits.text, "Hello, world!");
23974            assert_eq!(highlighted_edits.highlights.len(), 1);
23975            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23976            assert_eq!(
23977                highlighted_edits.highlights[0].1.background_color,
23978                Some(cx.theme().status().deleted_background)
23979            );
23980        },
23981    )
23982    .await;
23983
23984    // Insertion
23985    assert_highlighted_edits(
23986        "Hello, world!",
23987        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23988        true,
23989        cx,
23990        |highlighted_edits, cx| {
23991            assert_eq!(highlighted_edits.highlights.len(), 1);
23992            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23993            assert_eq!(
23994                highlighted_edits.highlights[0].1.background_color,
23995                Some(cx.theme().status().created_background)
23996            );
23997        },
23998    )
23999    .await;
24000}
24001
24002async fn assert_highlighted_edits(
24003    text: &str,
24004    edits: Vec<(Range<Point>, String)>,
24005    include_deletions: bool,
24006    cx: &mut TestAppContext,
24007    assertion_fn: impl Fn(HighlightedText, &App),
24008) {
24009    let window = cx.add_window(|window, cx| {
24010        let buffer = MultiBuffer::build_simple(text, cx);
24011        Editor::new(EditorMode::full(), buffer, None, window, cx)
24012    });
24013    let cx = &mut VisualTestContext::from_window(*window, cx);
24014
24015    let (buffer, snapshot) = window
24016        .update(cx, |editor, _window, cx| {
24017            (
24018                editor.buffer().clone(),
24019                editor.buffer().read(cx).snapshot(cx),
24020            )
24021        })
24022        .unwrap();
24023
24024    let edits = edits
24025        .into_iter()
24026        .map(|(range, edit)| {
24027            (
24028                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24029                edit,
24030            )
24031        })
24032        .collect::<Vec<_>>();
24033
24034    let text_anchor_edits = edits
24035        .clone()
24036        .into_iter()
24037        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24038        .collect::<Vec<_>>();
24039
24040    let edit_preview = window
24041        .update(cx, |_, _window, cx| {
24042            buffer
24043                .read(cx)
24044                .as_singleton()
24045                .unwrap()
24046                .read(cx)
24047                .preview_edits(text_anchor_edits.into(), cx)
24048        })
24049        .unwrap()
24050        .await;
24051
24052    cx.update(|_window, cx| {
24053        let highlighted_edits = edit_prediction_edit_text(
24054            snapshot.as_singleton().unwrap().2,
24055            &edits,
24056            &edit_preview,
24057            include_deletions,
24058            cx,
24059        );
24060        assertion_fn(highlighted_edits, cx)
24061    });
24062}
24063
24064#[track_caller]
24065fn assert_breakpoint(
24066    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24067    path: &Arc<Path>,
24068    expected: Vec<(u32, Breakpoint)>,
24069) {
24070    if expected.is_empty() {
24071        assert!(!breakpoints.contains_key(path), "{}", path.display());
24072    } else {
24073        let mut breakpoint = breakpoints
24074            .get(path)
24075            .unwrap()
24076            .iter()
24077            .map(|breakpoint| {
24078                (
24079                    breakpoint.row,
24080                    Breakpoint {
24081                        message: breakpoint.message.clone(),
24082                        state: breakpoint.state,
24083                        condition: breakpoint.condition.clone(),
24084                        hit_condition: breakpoint.hit_condition.clone(),
24085                    },
24086                )
24087            })
24088            .collect::<Vec<_>>();
24089
24090        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24091
24092        assert_eq!(expected, breakpoint);
24093    }
24094}
24095
24096fn add_log_breakpoint_at_cursor(
24097    editor: &mut Editor,
24098    log_message: &str,
24099    window: &mut Window,
24100    cx: &mut Context<Editor>,
24101) {
24102    let (anchor, bp) = editor
24103        .breakpoints_at_cursors(window, cx)
24104        .first()
24105        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24106        .unwrap_or_else(|| {
24107            let snapshot = editor.snapshot(window, cx);
24108            let cursor_position: Point =
24109                editor.selections.newest(&snapshot.display_snapshot).head();
24110
24111            let breakpoint_position = snapshot
24112                .buffer_snapshot()
24113                .anchor_before(Point::new(cursor_position.row, 0));
24114
24115            (breakpoint_position, Breakpoint::new_log(log_message))
24116        });
24117
24118    editor.edit_breakpoint_at_anchor(
24119        anchor,
24120        bp,
24121        BreakpointEditAction::EditLogMessage(log_message.into()),
24122        cx,
24123    );
24124}
24125
24126#[gpui::test]
24127async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24128    init_test(cx, |_| {});
24129
24130    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24131    let fs = FakeFs::new(cx.executor());
24132    fs.insert_tree(
24133        path!("/a"),
24134        json!({
24135            "main.rs": sample_text,
24136        }),
24137    )
24138    .await;
24139    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24140    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24141    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24142
24143    let fs = FakeFs::new(cx.executor());
24144    fs.insert_tree(
24145        path!("/a"),
24146        json!({
24147            "main.rs": sample_text,
24148        }),
24149    )
24150    .await;
24151    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24152    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24153    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24154    let worktree_id = workspace
24155        .update(cx, |workspace, _window, cx| {
24156            workspace.project().update(cx, |project, cx| {
24157                project.worktrees(cx).next().unwrap().read(cx).id()
24158            })
24159        })
24160        .unwrap();
24161
24162    let buffer = project
24163        .update(cx, |project, cx| {
24164            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24165        })
24166        .await
24167        .unwrap();
24168
24169    let (editor, cx) = cx.add_window_view(|window, cx| {
24170        Editor::new(
24171            EditorMode::full(),
24172            MultiBuffer::build_from_buffer(buffer, cx),
24173            Some(project.clone()),
24174            window,
24175            cx,
24176        )
24177    });
24178
24179    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24180    let abs_path = project.read_with(cx, |project, cx| {
24181        project
24182            .absolute_path(&project_path, cx)
24183            .map(Arc::from)
24184            .unwrap()
24185    });
24186
24187    // assert we can add breakpoint on the first line
24188    editor.update_in(cx, |editor, window, cx| {
24189        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24190        editor.move_to_end(&MoveToEnd, window, cx);
24191        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24192    });
24193
24194    let breakpoints = editor.update(cx, |editor, cx| {
24195        editor
24196            .breakpoint_store()
24197            .as_ref()
24198            .unwrap()
24199            .read(cx)
24200            .all_source_breakpoints(cx)
24201    });
24202
24203    assert_eq!(1, breakpoints.len());
24204    assert_breakpoint(
24205        &breakpoints,
24206        &abs_path,
24207        vec![
24208            (0, Breakpoint::new_standard()),
24209            (3, Breakpoint::new_standard()),
24210        ],
24211    );
24212
24213    editor.update_in(cx, |editor, window, cx| {
24214        editor.move_to_beginning(&MoveToBeginning, window, cx);
24215        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24216    });
24217
24218    let breakpoints = editor.update(cx, |editor, cx| {
24219        editor
24220            .breakpoint_store()
24221            .as_ref()
24222            .unwrap()
24223            .read(cx)
24224            .all_source_breakpoints(cx)
24225    });
24226
24227    assert_eq!(1, breakpoints.len());
24228    assert_breakpoint(
24229        &breakpoints,
24230        &abs_path,
24231        vec![(3, Breakpoint::new_standard())],
24232    );
24233
24234    editor.update_in(cx, |editor, window, cx| {
24235        editor.move_to_end(&MoveToEnd, window, cx);
24236        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24237    });
24238
24239    let breakpoints = editor.update(cx, |editor, cx| {
24240        editor
24241            .breakpoint_store()
24242            .as_ref()
24243            .unwrap()
24244            .read(cx)
24245            .all_source_breakpoints(cx)
24246    });
24247
24248    assert_eq!(0, breakpoints.len());
24249    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24250}
24251
24252#[gpui::test]
24253async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24254    init_test(cx, |_| {});
24255
24256    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24257
24258    let fs = FakeFs::new(cx.executor());
24259    fs.insert_tree(
24260        path!("/a"),
24261        json!({
24262            "main.rs": sample_text,
24263        }),
24264    )
24265    .await;
24266    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24267    let (workspace, cx) =
24268        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24269
24270    let worktree_id = workspace.update(cx, |workspace, cx| {
24271        workspace.project().update(cx, |project, cx| {
24272            project.worktrees(cx).next().unwrap().read(cx).id()
24273        })
24274    });
24275
24276    let buffer = project
24277        .update(cx, |project, cx| {
24278            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24279        })
24280        .await
24281        .unwrap();
24282
24283    let (editor, cx) = cx.add_window_view(|window, cx| {
24284        Editor::new(
24285            EditorMode::full(),
24286            MultiBuffer::build_from_buffer(buffer, cx),
24287            Some(project.clone()),
24288            window,
24289            cx,
24290        )
24291    });
24292
24293    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24294    let abs_path = project.read_with(cx, |project, cx| {
24295        project
24296            .absolute_path(&project_path, cx)
24297            .map(Arc::from)
24298            .unwrap()
24299    });
24300
24301    editor.update_in(cx, |editor, window, cx| {
24302        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24303    });
24304
24305    let breakpoints = editor.update(cx, |editor, cx| {
24306        editor
24307            .breakpoint_store()
24308            .as_ref()
24309            .unwrap()
24310            .read(cx)
24311            .all_source_breakpoints(cx)
24312    });
24313
24314    assert_breakpoint(
24315        &breakpoints,
24316        &abs_path,
24317        vec![(0, Breakpoint::new_log("hello world"))],
24318    );
24319
24320    // Removing a log message from a log breakpoint should remove it
24321    editor.update_in(cx, |editor, window, cx| {
24322        add_log_breakpoint_at_cursor(editor, "", window, cx);
24323    });
24324
24325    let breakpoints = editor.update(cx, |editor, cx| {
24326        editor
24327            .breakpoint_store()
24328            .as_ref()
24329            .unwrap()
24330            .read(cx)
24331            .all_source_breakpoints(cx)
24332    });
24333
24334    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24335
24336    editor.update_in(cx, |editor, window, cx| {
24337        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24338        editor.move_to_end(&MoveToEnd, window, cx);
24339        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24340        // Not adding a log message to a standard breakpoint shouldn't remove it
24341        add_log_breakpoint_at_cursor(editor, "", window, cx);
24342    });
24343
24344    let breakpoints = editor.update(cx, |editor, cx| {
24345        editor
24346            .breakpoint_store()
24347            .as_ref()
24348            .unwrap()
24349            .read(cx)
24350            .all_source_breakpoints(cx)
24351    });
24352
24353    assert_breakpoint(
24354        &breakpoints,
24355        &abs_path,
24356        vec![
24357            (0, Breakpoint::new_standard()),
24358            (3, Breakpoint::new_standard()),
24359        ],
24360    );
24361
24362    editor.update_in(cx, |editor, window, cx| {
24363        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24364    });
24365
24366    let breakpoints = editor.update(cx, |editor, cx| {
24367        editor
24368            .breakpoint_store()
24369            .as_ref()
24370            .unwrap()
24371            .read(cx)
24372            .all_source_breakpoints(cx)
24373    });
24374
24375    assert_breakpoint(
24376        &breakpoints,
24377        &abs_path,
24378        vec![
24379            (0, Breakpoint::new_standard()),
24380            (3, Breakpoint::new_log("hello world")),
24381        ],
24382    );
24383
24384    editor.update_in(cx, |editor, window, cx| {
24385        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24386    });
24387
24388    let breakpoints = editor.update(cx, |editor, cx| {
24389        editor
24390            .breakpoint_store()
24391            .as_ref()
24392            .unwrap()
24393            .read(cx)
24394            .all_source_breakpoints(cx)
24395    });
24396
24397    assert_breakpoint(
24398        &breakpoints,
24399        &abs_path,
24400        vec![
24401            (0, Breakpoint::new_standard()),
24402            (3, Breakpoint::new_log("hello Earth!!")),
24403        ],
24404    );
24405}
24406
24407/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24408/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24409/// or when breakpoints were placed out of order. This tests for a regression too
24410#[gpui::test]
24411async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24412    init_test(cx, |_| {});
24413
24414    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24415    let fs = FakeFs::new(cx.executor());
24416    fs.insert_tree(
24417        path!("/a"),
24418        json!({
24419            "main.rs": sample_text,
24420        }),
24421    )
24422    .await;
24423    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24424    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24425    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24426
24427    let fs = FakeFs::new(cx.executor());
24428    fs.insert_tree(
24429        path!("/a"),
24430        json!({
24431            "main.rs": sample_text,
24432        }),
24433    )
24434    .await;
24435    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24436    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24437    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24438    let worktree_id = workspace
24439        .update(cx, |workspace, _window, cx| {
24440            workspace.project().update(cx, |project, cx| {
24441                project.worktrees(cx).next().unwrap().read(cx).id()
24442            })
24443        })
24444        .unwrap();
24445
24446    let buffer = project
24447        .update(cx, |project, cx| {
24448            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24449        })
24450        .await
24451        .unwrap();
24452
24453    let (editor, cx) = cx.add_window_view(|window, cx| {
24454        Editor::new(
24455            EditorMode::full(),
24456            MultiBuffer::build_from_buffer(buffer, cx),
24457            Some(project.clone()),
24458            window,
24459            cx,
24460        )
24461    });
24462
24463    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24464    let abs_path = project.read_with(cx, |project, cx| {
24465        project
24466            .absolute_path(&project_path, cx)
24467            .map(Arc::from)
24468            .unwrap()
24469    });
24470
24471    // assert we can add breakpoint on the first line
24472    editor.update_in(cx, |editor, window, cx| {
24473        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24474        editor.move_to_end(&MoveToEnd, window, cx);
24475        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24476        editor.move_up(&MoveUp, window, cx);
24477        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24478    });
24479
24480    let breakpoints = editor.update(cx, |editor, cx| {
24481        editor
24482            .breakpoint_store()
24483            .as_ref()
24484            .unwrap()
24485            .read(cx)
24486            .all_source_breakpoints(cx)
24487    });
24488
24489    assert_eq!(1, breakpoints.len());
24490    assert_breakpoint(
24491        &breakpoints,
24492        &abs_path,
24493        vec![
24494            (0, Breakpoint::new_standard()),
24495            (2, Breakpoint::new_standard()),
24496            (3, Breakpoint::new_standard()),
24497        ],
24498    );
24499
24500    editor.update_in(cx, |editor, window, cx| {
24501        editor.move_to_beginning(&MoveToBeginning, window, cx);
24502        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24503        editor.move_to_end(&MoveToEnd, window, cx);
24504        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24505        // Disabling a breakpoint that doesn't exist should do nothing
24506        editor.move_up(&MoveUp, window, cx);
24507        editor.move_up(&MoveUp, window, cx);
24508        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24509    });
24510
24511    let breakpoints = editor.update(cx, |editor, cx| {
24512        editor
24513            .breakpoint_store()
24514            .as_ref()
24515            .unwrap()
24516            .read(cx)
24517            .all_source_breakpoints(cx)
24518    });
24519
24520    let disable_breakpoint = {
24521        let mut bp = Breakpoint::new_standard();
24522        bp.state = BreakpointState::Disabled;
24523        bp
24524    };
24525
24526    assert_eq!(1, breakpoints.len());
24527    assert_breakpoint(
24528        &breakpoints,
24529        &abs_path,
24530        vec![
24531            (0, disable_breakpoint.clone()),
24532            (2, Breakpoint::new_standard()),
24533            (3, disable_breakpoint.clone()),
24534        ],
24535    );
24536
24537    editor.update_in(cx, |editor, window, cx| {
24538        editor.move_to_beginning(&MoveToBeginning, window, cx);
24539        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24540        editor.move_to_end(&MoveToEnd, window, cx);
24541        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24542        editor.move_up(&MoveUp, window, cx);
24543        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24544    });
24545
24546    let breakpoints = editor.update(cx, |editor, cx| {
24547        editor
24548            .breakpoint_store()
24549            .as_ref()
24550            .unwrap()
24551            .read(cx)
24552            .all_source_breakpoints(cx)
24553    });
24554
24555    assert_eq!(1, breakpoints.len());
24556    assert_breakpoint(
24557        &breakpoints,
24558        &abs_path,
24559        vec![
24560            (0, Breakpoint::new_standard()),
24561            (2, disable_breakpoint),
24562            (3, Breakpoint::new_standard()),
24563        ],
24564    );
24565}
24566
24567#[gpui::test]
24568async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24569    init_test(cx, |_| {});
24570    let capabilities = lsp::ServerCapabilities {
24571        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24572            prepare_provider: Some(true),
24573            work_done_progress_options: Default::default(),
24574        })),
24575        ..Default::default()
24576    };
24577    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24578
24579    cx.set_state(indoc! {"
24580        struct Fˇoo {}
24581    "});
24582
24583    cx.update_editor(|editor, _, cx| {
24584        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24585        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24586        editor.highlight_background::<DocumentHighlightRead>(
24587            &[highlight_range],
24588            |_, theme| theme.colors().editor_document_highlight_read_background,
24589            cx,
24590        );
24591    });
24592
24593    let mut prepare_rename_handler = cx
24594        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24595            move |_, _, _| async move {
24596                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24597                    start: lsp::Position {
24598                        line: 0,
24599                        character: 7,
24600                    },
24601                    end: lsp::Position {
24602                        line: 0,
24603                        character: 10,
24604                    },
24605                })))
24606            },
24607        );
24608    let prepare_rename_task = cx
24609        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24610        .expect("Prepare rename was not started");
24611    prepare_rename_handler.next().await.unwrap();
24612    prepare_rename_task.await.expect("Prepare rename failed");
24613
24614    let mut rename_handler =
24615        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24616            let edit = lsp::TextEdit {
24617                range: lsp::Range {
24618                    start: lsp::Position {
24619                        line: 0,
24620                        character: 7,
24621                    },
24622                    end: lsp::Position {
24623                        line: 0,
24624                        character: 10,
24625                    },
24626                },
24627                new_text: "FooRenamed".to_string(),
24628            };
24629            Ok(Some(lsp::WorkspaceEdit::new(
24630                // Specify the same edit twice
24631                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24632            )))
24633        });
24634    let rename_task = cx
24635        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24636        .expect("Confirm rename was not started");
24637    rename_handler.next().await.unwrap();
24638    rename_task.await.expect("Confirm rename failed");
24639    cx.run_until_parked();
24640
24641    // Despite two edits, only one is actually applied as those are identical
24642    cx.assert_editor_state(indoc! {"
24643        struct FooRenamedˇ {}
24644    "});
24645}
24646
24647#[gpui::test]
24648async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24649    init_test(cx, |_| {});
24650    // These capabilities indicate that the server does not support prepare rename.
24651    let capabilities = lsp::ServerCapabilities {
24652        rename_provider: Some(lsp::OneOf::Left(true)),
24653        ..Default::default()
24654    };
24655    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24656
24657    cx.set_state(indoc! {"
24658        struct Fˇoo {}
24659    "});
24660
24661    cx.update_editor(|editor, _window, cx| {
24662        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24663        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24664        editor.highlight_background::<DocumentHighlightRead>(
24665            &[highlight_range],
24666            |_, theme| theme.colors().editor_document_highlight_read_background,
24667            cx,
24668        );
24669    });
24670
24671    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24672        .expect("Prepare rename was not started")
24673        .await
24674        .expect("Prepare rename failed");
24675
24676    let mut rename_handler =
24677        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24678            let edit = lsp::TextEdit {
24679                range: lsp::Range {
24680                    start: lsp::Position {
24681                        line: 0,
24682                        character: 7,
24683                    },
24684                    end: lsp::Position {
24685                        line: 0,
24686                        character: 10,
24687                    },
24688                },
24689                new_text: "FooRenamed".to_string(),
24690            };
24691            Ok(Some(lsp::WorkspaceEdit::new(
24692                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24693            )))
24694        });
24695    let rename_task = cx
24696        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24697        .expect("Confirm rename was not started");
24698    rename_handler.next().await.unwrap();
24699    rename_task.await.expect("Confirm rename failed");
24700    cx.run_until_parked();
24701
24702    // Correct range is renamed, as `surrounding_word` is used to find it.
24703    cx.assert_editor_state(indoc! {"
24704        struct FooRenamedˇ {}
24705    "});
24706}
24707
24708#[gpui::test]
24709async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24710    init_test(cx, |_| {});
24711    let mut cx = EditorTestContext::new(cx).await;
24712
24713    let language = Arc::new(
24714        Language::new(
24715            LanguageConfig::default(),
24716            Some(tree_sitter_html::LANGUAGE.into()),
24717        )
24718        .with_brackets_query(
24719            r#"
24720            ("<" @open "/>" @close)
24721            ("</" @open ">" @close)
24722            ("<" @open ">" @close)
24723            ("\"" @open "\"" @close)
24724            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24725        "#,
24726        )
24727        .unwrap(),
24728    );
24729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24730
24731    cx.set_state(indoc! {"
24732        <span>ˇ</span>
24733    "});
24734    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24735    cx.assert_editor_state(indoc! {"
24736        <span>
24737        ˇ
24738        </span>
24739    "});
24740
24741    cx.set_state(indoc! {"
24742        <span><span></span>ˇ</span>
24743    "});
24744    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24745    cx.assert_editor_state(indoc! {"
24746        <span><span></span>
24747        ˇ</span>
24748    "});
24749
24750    cx.set_state(indoc! {"
24751        <span>ˇ
24752        </span>
24753    "});
24754    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24755    cx.assert_editor_state(indoc! {"
24756        <span>
24757        ˇ
24758        </span>
24759    "});
24760}
24761
24762#[gpui::test(iterations = 10)]
24763async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24764    init_test(cx, |_| {});
24765
24766    let fs = FakeFs::new(cx.executor());
24767    fs.insert_tree(
24768        path!("/dir"),
24769        json!({
24770            "a.ts": "a",
24771        }),
24772    )
24773    .await;
24774
24775    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24776    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24777    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24778
24779    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24780    language_registry.add(Arc::new(Language::new(
24781        LanguageConfig {
24782            name: "TypeScript".into(),
24783            matcher: LanguageMatcher {
24784                path_suffixes: vec!["ts".to_string()],
24785                ..Default::default()
24786            },
24787            ..Default::default()
24788        },
24789        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24790    )));
24791    let mut fake_language_servers = language_registry.register_fake_lsp(
24792        "TypeScript",
24793        FakeLspAdapter {
24794            capabilities: lsp::ServerCapabilities {
24795                code_lens_provider: Some(lsp::CodeLensOptions {
24796                    resolve_provider: Some(true),
24797                }),
24798                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24799                    commands: vec!["_the/command".to_string()],
24800                    ..lsp::ExecuteCommandOptions::default()
24801                }),
24802                ..lsp::ServerCapabilities::default()
24803            },
24804            ..FakeLspAdapter::default()
24805        },
24806    );
24807
24808    let editor = workspace
24809        .update(cx, |workspace, window, cx| {
24810            workspace.open_abs_path(
24811                PathBuf::from(path!("/dir/a.ts")),
24812                OpenOptions::default(),
24813                window,
24814                cx,
24815            )
24816        })
24817        .unwrap()
24818        .await
24819        .unwrap()
24820        .downcast::<Editor>()
24821        .unwrap();
24822    cx.executor().run_until_parked();
24823
24824    let fake_server = fake_language_servers.next().await.unwrap();
24825
24826    let buffer = editor.update(cx, |editor, cx| {
24827        editor
24828            .buffer()
24829            .read(cx)
24830            .as_singleton()
24831            .expect("have opened a single file by path")
24832    });
24833
24834    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24835    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24836    drop(buffer_snapshot);
24837    let actions = cx
24838        .update_window(*workspace, |_, window, cx| {
24839            project.code_actions(&buffer, anchor..anchor, window, cx)
24840        })
24841        .unwrap();
24842
24843    fake_server
24844        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24845            Ok(Some(vec![
24846                lsp::CodeLens {
24847                    range: lsp::Range::default(),
24848                    command: Some(lsp::Command {
24849                        title: "Code lens command".to_owned(),
24850                        command: "_the/command".to_owned(),
24851                        arguments: None,
24852                    }),
24853                    data: None,
24854                },
24855                lsp::CodeLens {
24856                    range: lsp::Range::default(),
24857                    command: Some(lsp::Command {
24858                        title: "Command not in capabilities".to_owned(),
24859                        command: "not in capabilities".to_owned(),
24860                        arguments: None,
24861                    }),
24862                    data: None,
24863                },
24864                lsp::CodeLens {
24865                    range: lsp::Range {
24866                        start: lsp::Position {
24867                            line: 1,
24868                            character: 1,
24869                        },
24870                        end: lsp::Position {
24871                            line: 1,
24872                            character: 1,
24873                        },
24874                    },
24875                    command: Some(lsp::Command {
24876                        title: "Command not in range".to_owned(),
24877                        command: "_the/command".to_owned(),
24878                        arguments: None,
24879                    }),
24880                    data: None,
24881                },
24882            ]))
24883        })
24884        .next()
24885        .await;
24886
24887    let actions = actions.await.unwrap();
24888    assert_eq!(
24889        actions.len(),
24890        1,
24891        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24892    );
24893    let action = actions[0].clone();
24894    let apply = project.update(cx, |project, cx| {
24895        project.apply_code_action(buffer.clone(), action, true, cx)
24896    });
24897
24898    // Resolving the code action does not populate its edits. In absence of
24899    // edits, we must execute the given command.
24900    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24901        |mut lens, _| async move {
24902            let lens_command = lens.command.as_mut().expect("should have a command");
24903            assert_eq!(lens_command.title, "Code lens command");
24904            lens_command.arguments = Some(vec![json!("the-argument")]);
24905            Ok(lens)
24906        },
24907    );
24908
24909    // While executing the command, the language server sends the editor
24910    // a `workspaceEdit` request.
24911    fake_server
24912        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24913            let fake = fake_server.clone();
24914            move |params, _| {
24915                assert_eq!(params.command, "_the/command");
24916                let fake = fake.clone();
24917                async move {
24918                    fake.server
24919                        .request::<lsp::request::ApplyWorkspaceEdit>(
24920                            lsp::ApplyWorkspaceEditParams {
24921                                label: None,
24922                                edit: lsp::WorkspaceEdit {
24923                                    changes: Some(
24924                                        [(
24925                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24926                                            vec![lsp::TextEdit {
24927                                                range: lsp::Range::new(
24928                                                    lsp::Position::new(0, 0),
24929                                                    lsp::Position::new(0, 0),
24930                                                ),
24931                                                new_text: "X".into(),
24932                                            }],
24933                                        )]
24934                                        .into_iter()
24935                                        .collect(),
24936                                    ),
24937                                    ..lsp::WorkspaceEdit::default()
24938                                },
24939                            },
24940                        )
24941                        .await
24942                        .into_response()
24943                        .unwrap();
24944                    Ok(Some(json!(null)))
24945                }
24946            }
24947        })
24948        .next()
24949        .await;
24950
24951    // Applying the code lens command returns a project transaction containing the edits
24952    // sent by the language server in its `workspaceEdit` request.
24953    let transaction = apply.await.unwrap();
24954    assert!(transaction.0.contains_key(&buffer));
24955    buffer.update(cx, |buffer, cx| {
24956        assert_eq!(buffer.text(), "Xa");
24957        buffer.undo(cx);
24958        assert_eq!(buffer.text(), "a");
24959    });
24960
24961    let actions_after_edits = cx
24962        .update_window(*workspace, |_, window, cx| {
24963            project.code_actions(&buffer, anchor..anchor, window, cx)
24964        })
24965        .unwrap()
24966        .await
24967        .unwrap();
24968    assert_eq!(
24969        actions, actions_after_edits,
24970        "For the same selection, same code lens actions should be returned"
24971    );
24972
24973    let _responses =
24974        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24975            panic!("No more code lens requests are expected");
24976        });
24977    editor.update_in(cx, |editor, window, cx| {
24978        editor.select_all(&SelectAll, window, cx);
24979    });
24980    cx.executor().run_until_parked();
24981    let new_actions = cx
24982        .update_window(*workspace, |_, window, cx| {
24983            project.code_actions(&buffer, anchor..anchor, window, cx)
24984        })
24985        .unwrap()
24986        .await
24987        .unwrap();
24988    assert_eq!(
24989        actions, new_actions,
24990        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24991    );
24992}
24993
24994#[gpui::test]
24995async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24996    init_test(cx, |_| {});
24997
24998    let fs = FakeFs::new(cx.executor());
24999    let main_text = r#"fn main() {
25000println!("1");
25001println!("2");
25002println!("3");
25003println!("4");
25004println!("5");
25005}"#;
25006    let lib_text = "mod foo {}";
25007    fs.insert_tree(
25008        path!("/a"),
25009        json!({
25010            "lib.rs": lib_text,
25011            "main.rs": main_text,
25012        }),
25013    )
25014    .await;
25015
25016    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25017    let (workspace, cx) =
25018        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25019    let worktree_id = workspace.update(cx, |workspace, cx| {
25020        workspace.project().update(cx, |project, cx| {
25021            project.worktrees(cx).next().unwrap().read(cx).id()
25022        })
25023    });
25024
25025    let expected_ranges = vec![
25026        Point::new(0, 0)..Point::new(0, 0),
25027        Point::new(1, 0)..Point::new(1, 1),
25028        Point::new(2, 0)..Point::new(2, 2),
25029        Point::new(3, 0)..Point::new(3, 3),
25030    ];
25031
25032    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25033    let editor_1 = workspace
25034        .update_in(cx, |workspace, window, cx| {
25035            workspace.open_path(
25036                (worktree_id, rel_path("main.rs")),
25037                Some(pane_1.downgrade()),
25038                true,
25039                window,
25040                cx,
25041            )
25042        })
25043        .unwrap()
25044        .await
25045        .downcast::<Editor>()
25046        .unwrap();
25047    pane_1.update(cx, |pane, cx| {
25048        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25049        open_editor.update(cx, |editor, cx| {
25050            assert_eq!(
25051                editor.display_text(cx),
25052                main_text,
25053                "Original main.rs text on initial open",
25054            );
25055            assert_eq!(
25056                editor
25057                    .selections
25058                    .all::<Point>(&editor.display_snapshot(cx))
25059                    .into_iter()
25060                    .map(|s| s.range())
25061                    .collect::<Vec<_>>(),
25062                vec![Point::zero()..Point::zero()],
25063                "Default selections on initial open",
25064            );
25065        })
25066    });
25067    editor_1.update_in(cx, |editor, window, cx| {
25068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25069            s.select_ranges(expected_ranges.clone());
25070        });
25071    });
25072
25073    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25074        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25075    });
25076    let editor_2 = workspace
25077        .update_in(cx, |workspace, window, cx| {
25078            workspace.open_path(
25079                (worktree_id, rel_path("main.rs")),
25080                Some(pane_2.downgrade()),
25081                true,
25082                window,
25083                cx,
25084            )
25085        })
25086        .unwrap()
25087        .await
25088        .downcast::<Editor>()
25089        .unwrap();
25090    pane_2.update(cx, |pane, cx| {
25091        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25092        open_editor.update(cx, |editor, cx| {
25093            assert_eq!(
25094                editor.display_text(cx),
25095                main_text,
25096                "Original main.rs text on initial open in another panel",
25097            );
25098            assert_eq!(
25099                editor
25100                    .selections
25101                    .all::<Point>(&editor.display_snapshot(cx))
25102                    .into_iter()
25103                    .map(|s| s.range())
25104                    .collect::<Vec<_>>(),
25105                vec![Point::zero()..Point::zero()],
25106                "Default selections on initial open in another panel",
25107            );
25108        })
25109    });
25110
25111    editor_2.update_in(cx, |editor, window, cx| {
25112        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25113    });
25114
25115    let _other_editor_1 = workspace
25116        .update_in(cx, |workspace, window, cx| {
25117            workspace.open_path(
25118                (worktree_id, rel_path("lib.rs")),
25119                Some(pane_1.downgrade()),
25120                true,
25121                window,
25122                cx,
25123            )
25124        })
25125        .unwrap()
25126        .await
25127        .downcast::<Editor>()
25128        .unwrap();
25129    pane_1
25130        .update_in(cx, |pane, window, cx| {
25131            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25132        })
25133        .await
25134        .unwrap();
25135    drop(editor_1);
25136    pane_1.update(cx, |pane, cx| {
25137        pane.active_item()
25138            .unwrap()
25139            .downcast::<Editor>()
25140            .unwrap()
25141            .update(cx, |editor, cx| {
25142                assert_eq!(
25143                    editor.display_text(cx),
25144                    lib_text,
25145                    "Other file should be open and active",
25146                );
25147            });
25148        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25149    });
25150
25151    let _other_editor_2 = workspace
25152        .update_in(cx, |workspace, window, cx| {
25153            workspace.open_path(
25154                (worktree_id, rel_path("lib.rs")),
25155                Some(pane_2.downgrade()),
25156                true,
25157                window,
25158                cx,
25159            )
25160        })
25161        .unwrap()
25162        .await
25163        .downcast::<Editor>()
25164        .unwrap();
25165    pane_2
25166        .update_in(cx, |pane, window, cx| {
25167            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25168        })
25169        .await
25170        .unwrap();
25171    drop(editor_2);
25172    pane_2.update(cx, |pane, cx| {
25173        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25174        open_editor.update(cx, |editor, cx| {
25175            assert_eq!(
25176                editor.display_text(cx),
25177                lib_text,
25178                "Other file should be open and active in another panel too",
25179            );
25180        });
25181        assert_eq!(
25182            pane.items().count(),
25183            1,
25184            "No other editors should be open in another pane",
25185        );
25186    });
25187
25188    let _editor_1_reopened = workspace
25189        .update_in(cx, |workspace, window, cx| {
25190            workspace.open_path(
25191                (worktree_id, rel_path("main.rs")),
25192                Some(pane_1.downgrade()),
25193                true,
25194                window,
25195                cx,
25196            )
25197        })
25198        .unwrap()
25199        .await
25200        .downcast::<Editor>()
25201        .unwrap();
25202    let _editor_2_reopened = workspace
25203        .update_in(cx, |workspace, window, cx| {
25204            workspace.open_path(
25205                (worktree_id, rel_path("main.rs")),
25206                Some(pane_2.downgrade()),
25207                true,
25208                window,
25209                cx,
25210            )
25211        })
25212        .unwrap()
25213        .await
25214        .downcast::<Editor>()
25215        .unwrap();
25216    pane_1.update(cx, |pane, cx| {
25217        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25218        open_editor.update(cx, |editor, cx| {
25219            assert_eq!(
25220                editor.display_text(cx),
25221                main_text,
25222                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25223            );
25224            assert_eq!(
25225                editor
25226                    .selections
25227                    .all::<Point>(&editor.display_snapshot(cx))
25228                    .into_iter()
25229                    .map(|s| s.range())
25230                    .collect::<Vec<_>>(),
25231                expected_ranges,
25232                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25233            );
25234        })
25235    });
25236    pane_2.update(cx, |pane, cx| {
25237        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25238        open_editor.update(cx, |editor, cx| {
25239            assert_eq!(
25240                editor.display_text(cx),
25241                r#"fn main() {
25242⋯rintln!("1");
25243⋯intln!("2");
25244⋯ntln!("3");
25245println!("4");
25246println!("5");
25247}"#,
25248                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25249            );
25250            assert_eq!(
25251                editor
25252                    .selections
25253                    .all::<Point>(&editor.display_snapshot(cx))
25254                    .into_iter()
25255                    .map(|s| s.range())
25256                    .collect::<Vec<_>>(),
25257                vec![Point::zero()..Point::zero()],
25258                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25259            );
25260        })
25261    });
25262}
25263
25264#[gpui::test]
25265async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25266    init_test(cx, |_| {});
25267
25268    let fs = FakeFs::new(cx.executor());
25269    let main_text = r#"fn main() {
25270println!("1");
25271println!("2");
25272println!("3");
25273println!("4");
25274println!("5");
25275}"#;
25276    let lib_text = "mod foo {}";
25277    fs.insert_tree(
25278        path!("/a"),
25279        json!({
25280            "lib.rs": lib_text,
25281            "main.rs": main_text,
25282        }),
25283    )
25284    .await;
25285
25286    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25287    let (workspace, cx) =
25288        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25289    let worktree_id = workspace.update(cx, |workspace, cx| {
25290        workspace.project().update(cx, |project, cx| {
25291            project.worktrees(cx).next().unwrap().read(cx).id()
25292        })
25293    });
25294
25295    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25296    let editor = workspace
25297        .update_in(cx, |workspace, window, cx| {
25298            workspace.open_path(
25299                (worktree_id, rel_path("main.rs")),
25300                Some(pane.downgrade()),
25301                true,
25302                window,
25303                cx,
25304            )
25305        })
25306        .unwrap()
25307        .await
25308        .downcast::<Editor>()
25309        .unwrap();
25310    pane.update(cx, |pane, cx| {
25311        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25312        open_editor.update(cx, |editor, cx| {
25313            assert_eq!(
25314                editor.display_text(cx),
25315                main_text,
25316                "Original main.rs text on initial open",
25317            );
25318        })
25319    });
25320    editor.update_in(cx, |editor, window, cx| {
25321        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25322    });
25323
25324    cx.update_global(|store: &mut SettingsStore, cx| {
25325        store.update_user_settings(cx, |s| {
25326            s.workspace.restore_on_file_reopen = Some(false);
25327        });
25328    });
25329    editor.update_in(cx, |editor, window, cx| {
25330        editor.fold_ranges(
25331            vec![
25332                Point::new(1, 0)..Point::new(1, 1),
25333                Point::new(2, 0)..Point::new(2, 2),
25334                Point::new(3, 0)..Point::new(3, 3),
25335            ],
25336            false,
25337            window,
25338            cx,
25339        );
25340    });
25341    pane.update_in(cx, |pane, window, cx| {
25342        pane.close_all_items(&CloseAllItems::default(), window, cx)
25343    })
25344    .await
25345    .unwrap();
25346    pane.update(cx, |pane, _| {
25347        assert!(pane.active_item().is_none());
25348    });
25349    cx.update_global(|store: &mut SettingsStore, cx| {
25350        store.update_user_settings(cx, |s| {
25351            s.workspace.restore_on_file_reopen = Some(true);
25352        });
25353    });
25354
25355    let _editor_reopened = workspace
25356        .update_in(cx, |workspace, window, cx| {
25357            workspace.open_path(
25358                (worktree_id, rel_path("main.rs")),
25359                Some(pane.downgrade()),
25360                true,
25361                window,
25362                cx,
25363            )
25364        })
25365        .unwrap()
25366        .await
25367        .downcast::<Editor>()
25368        .unwrap();
25369    pane.update(cx, |pane, cx| {
25370        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25371        open_editor.update(cx, |editor, cx| {
25372            assert_eq!(
25373                editor.display_text(cx),
25374                main_text,
25375                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25376            );
25377        })
25378    });
25379}
25380
25381#[gpui::test]
25382async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25383    struct EmptyModalView {
25384        focus_handle: gpui::FocusHandle,
25385    }
25386    impl EventEmitter<DismissEvent> for EmptyModalView {}
25387    impl Render for EmptyModalView {
25388        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25389            div()
25390        }
25391    }
25392    impl Focusable for EmptyModalView {
25393        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25394            self.focus_handle.clone()
25395        }
25396    }
25397    impl workspace::ModalView for EmptyModalView {}
25398    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25399        EmptyModalView {
25400            focus_handle: cx.focus_handle(),
25401        }
25402    }
25403
25404    init_test(cx, |_| {});
25405
25406    let fs = FakeFs::new(cx.executor());
25407    let project = Project::test(fs, [], cx).await;
25408    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25409    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25410    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25411    let editor = cx.new_window_entity(|window, cx| {
25412        Editor::new(
25413            EditorMode::full(),
25414            buffer,
25415            Some(project.clone()),
25416            window,
25417            cx,
25418        )
25419    });
25420    workspace
25421        .update(cx, |workspace, window, cx| {
25422            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25423        })
25424        .unwrap();
25425    editor.update_in(cx, |editor, window, cx| {
25426        editor.open_context_menu(&OpenContextMenu, window, cx);
25427        assert!(editor.mouse_context_menu.is_some());
25428    });
25429    workspace
25430        .update(cx, |workspace, window, cx| {
25431            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25432        })
25433        .unwrap();
25434    cx.read(|cx| {
25435        assert!(editor.read(cx).mouse_context_menu.is_none());
25436    });
25437}
25438
25439fn set_linked_edit_ranges(
25440    opening: (Point, Point),
25441    closing: (Point, Point),
25442    editor: &mut Editor,
25443    cx: &mut Context<Editor>,
25444) {
25445    let Some((buffer, _)) = editor
25446        .buffer
25447        .read(cx)
25448        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25449    else {
25450        panic!("Failed to get buffer for selection position");
25451    };
25452    let buffer = buffer.read(cx);
25453    let buffer_id = buffer.remote_id();
25454    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25455    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25456    let mut linked_ranges = HashMap::default();
25457    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25458    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25459}
25460
25461#[gpui::test]
25462async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25463    init_test(cx, |_| {});
25464
25465    let fs = FakeFs::new(cx.executor());
25466    fs.insert_file(path!("/file.html"), Default::default())
25467        .await;
25468
25469    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25470
25471    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25472    let html_language = Arc::new(Language::new(
25473        LanguageConfig {
25474            name: "HTML".into(),
25475            matcher: LanguageMatcher {
25476                path_suffixes: vec!["html".to_string()],
25477                ..LanguageMatcher::default()
25478            },
25479            brackets: BracketPairConfig {
25480                pairs: vec![BracketPair {
25481                    start: "<".into(),
25482                    end: ">".into(),
25483                    close: true,
25484                    ..Default::default()
25485                }],
25486                ..Default::default()
25487            },
25488            ..Default::default()
25489        },
25490        Some(tree_sitter_html::LANGUAGE.into()),
25491    ));
25492    language_registry.add(html_language);
25493    let mut fake_servers = language_registry.register_fake_lsp(
25494        "HTML",
25495        FakeLspAdapter {
25496            capabilities: lsp::ServerCapabilities {
25497                completion_provider: Some(lsp::CompletionOptions {
25498                    resolve_provider: Some(true),
25499                    ..Default::default()
25500                }),
25501                ..Default::default()
25502            },
25503            ..Default::default()
25504        },
25505    );
25506
25507    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25508    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25509
25510    let worktree_id = workspace
25511        .update(cx, |workspace, _window, cx| {
25512            workspace.project().update(cx, |project, cx| {
25513                project.worktrees(cx).next().unwrap().read(cx).id()
25514            })
25515        })
25516        .unwrap();
25517    project
25518        .update(cx, |project, cx| {
25519            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25520        })
25521        .await
25522        .unwrap();
25523    let editor = workspace
25524        .update(cx, |workspace, window, cx| {
25525            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25526        })
25527        .unwrap()
25528        .await
25529        .unwrap()
25530        .downcast::<Editor>()
25531        .unwrap();
25532
25533    let fake_server = fake_servers.next().await.unwrap();
25534    cx.run_until_parked();
25535    editor.update_in(cx, |editor, window, cx| {
25536        editor.set_text("<ad></ad>", window, cx);
25537        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25538            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25539        });
25540        set_linked_edit_ranges(
25541            (Point::new(0, 1), Point::new(0, 3)),
25542            (Point::new(0, 6), Point::new(0, 8)),
25543            editor,
25544            cx,
25545        );
25546    });
25547    let mut completion_handle =
25548        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25549            Ok(Some(lsp::CompletionResponse::Array(vec![
25550                lsp::CompletionItem {
25551                    label: "head".to_string(),
25552                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25553                        lsp::InsertReplaceEdit {
25554                            new_text: "head".to_string(),
25555                            insert: lsp::Range::new(
25556                                lsp::Position::new(0, 1),
25557                                lsp::Position::new(0, 3),
25558                            ),
25559                            replace: lsp::Range::new(
25560                                lsp::Position::new(0, 1),
25561                                lsp::Position::new(0, 3),
25562                            ),
25563                        },
25564                    )),
25565                    ..Default::default()
25566                },
25567            ])))
25568        });
25569    editor.update_in(cx, |editor, window, cx| {
25570        editor.show_completions(&ShowCompletions, window, cx);
25571    });
25572    cx.run_until_parked();
25573    completion_handle.next().await.unwrap();
25574    editor.update(cx, |editor, _| {
25575        assert!(
25576            editor.context_menu_visible(),
25577            "Completion menu should be visible"
25578        );
25579    });
25580    editor.update_in(cx, |editor, window, cx| {
25581        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25582    });
25583    cx.executor().run_until_parked();
25584    editor.update(cx, |editor, cx| {
25585        assert_eq!(editor.text(cx), "<head></head>");
25586    });
25587}
25588
25589#[gpui::test]
25590async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25591    init_test(cx, |_| {});
25592
25593    let mut cx = EditorTestContext::new(cx).await;
25594    let language = Arc::new(Language::new(
25595        LanguageConfig {
25596            name: "TSX".into(),
25597            matcher: LanguageMatcher {
25598                path_suffixes: vec!["tsx".to_string()],
25599                ..LanguageMatcher::default()
25600            },
25601            brackets: BracketPairConfig {
25602                pairs: vec![BracketPair {
25603                    start: "<".into(),
25604                    end: ">".into(),
25605                    close: true,
25606                    ..Default::default()
25607                }],
25608                ..Default::default()
25609            },
25610            linked_edit_characters: HashSet::from_iter(['.']),
25611            ..Default::default()
25612        },
25613        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25614    ));
25615    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25616
25617    // Test typing > does not extend linked pair
25618    cx.set_state("<divˇ<div></div>");
25619    cx.update_editor(|editor, _, cx| {
25620        set_linked_edit_ranges(
25621            (Point::new(0, 1), Point::new(0, 4)),
25622            (Point::new(0, 11), Point::new(0, 14)),
25623            editor,
25624            cx,
25625        );
25626    });
25627    cx.update_editor(|editor, window, cx| {
25628        editor.handle_input(">", window, cx);
25629    });
25630    cx.assert_editor_state("<div>ˇ<div></div>");
25631
25632    // Test typing . do extend linked pair
25633    cx.set_state("<Animatedˇ></Animated>");
25634    cx.update_editor(|editor, _, cx| {
25635        set_linked_edit_ranges(
25636            (Point::new(0, 1), Point::new(0, 9)),
25637            (Point::new(0, 12), Point::new(0, 20)),
25638            editor,
25639            cx,
25640        );
25641    });
25642    cx.update_editor(|editor, window, cx| {
25643        editor.handle_input(".", window, cx);
25644    });
25645    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25646    cx.update_editor(|editor, _, cx| {
25647        set_linked_edit_ranges(
25648            (Point::new(0, 1), Point::new(0, 10)),
25649            (Point::new(0, 13), Point::new(0, 21)),
25650            editor,
25651            cx,
25652        );
25653    });
25654    cx.update_editor(|editor, window, cx| {
25655        editor.handle_input("V", window, cx);
25656    });
25657    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25658}
25659
25660#[gpui::test]
25661async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25662    init_test(cx, |_| {});
25663
25664    let fs = FakeFs::new(cx.executor());
25665    fs.insert_tree(
25666        path!("/root"),
25667        json!({
25668            "a": {
25669                "main.rs": "fn main() {}",
25670            },
25671            "foo": {
25672                "bar": {
25673                    "external_file.rs": "pub mod external {}",
25674                }
25675            }
25676        }),
25677    )
25678    .await;
25679
25680    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25681    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25682    language_registry.add(rust_lang());
25683    let _fake_servers = language_registry.register_fake_lsp(
25684        "Rust",
25685        FakeLspAdapter {
25686            ..FakeLspAdapter::default()
25687        },
25688    );
25689    let (workspace, cx) =
25690        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25691    let worktree_id = workspace.update(cx, |workspace, cx| {
25692        workspace.project().update(cx, |project, cx| {
25693            project.worktrees(cx).next().unwrap().read(cx).id()
25694        })
25695    });
25696
25697    let assert_language_servers_count =
25698        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25699            project.update(cx, |project, cx| {
25700                let current = project
25701                    .lsp_store()
25702                    .read(cx)
25703                    .as_local()
25704                    .unwrap()
25705                    .language_servers
25706                    .len();
25707                assert_eq!(expected, current, "{context}");
25708            });
25709        };
25710
25711    assert_language_servers_count(
25712        0,
25713        "No servers should be running before any file is open",
25714        cx,
25715    );
25716    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25717    let main_editor = workspace
25718        .update_in(cx, |workspace, window, cx| {
25719            workspace.open_path(
25720                (worktree_id, rel_path("main.rs")),
25721                Some(pane.downgrade()),
25722                true,
25723                window,
25724                cx,
25725            )
25726        })
25727        .unwrap()
25728        .await
25729        .downcast::<Editor>()
25730        .unwrap();
25731    pane.update(cx, |pane, cx| {
25732        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25733        open_editor.update(cx, |editor, cx| {
25734            assert_eq!(
25735                editor.display_text(cx),
25736                "fn main() {}",
25737                "Original main.rs text on initial open",
25738            );
25739        });
25740        assert_eq!(open_editor, main_editor);
25741    });
25742    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25743
25744    let external_editor = workspace
25745        .update_in(cx, |workspace, window, cx| {
25746            workspace.open_abs_path(
25747                PathBuf::from("/root/foo/bar/external_file.rs"),
25748                OpenOptions::default(),
25749                window,
25750                cx,
25751            )
25752        })
25753        .await
25754        .expect("opening external file")
25755        .downcast::<Editor>()
25756        .expect("downcasted external file's open element to editor");
25757    pane.update(cx, |pane, cx| {
25758        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25759        open_editor.update(cx, |editor, cx| {
25760            assert_eq!(
25761                editor.display_text(cx),
25762                "pub mod external {}",
25763                "External file is open now",
25764            );
25765        });
25766        assert_eq!(open_editor, external_editor);
25767    });
25768    assert_language_servers_count(
25769        1,
25770        "Second, external, *.rs file should join the existing server",
25771        cx,
25772    );
25773
25774    pane.update_in(cx, |pane, window, cx| {
25775        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25776    })
25777    .await
25778    .unwrap();
25779    pane.update_in(cx, |pane, window, cx| {
25780        pane.navigate_backward(&Default::default(), window, cx);
25781    });
25782    cx.run_until_parked();
25783    pane.update(cx, |pane, cx| {
25784        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25785        open_editor.update(cx, |editor, cx| {
25786            assert_eq!(
25787                editor.display_text(cx),
25788                "pub mod external {}",
25789                "External file is open now",
25790            );
25791        });
25792    });
25793    assert_language_servers_count(
25794        1,
25795        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25796        cx,
25797    );
25798
25799    cx.update(|_, cx| {
25800        workspace::reload(cx);
25801    });
25802    assert_language_servers_count(
25803        1,
25804        "After reloading the worktree with local and external files opened, only one project should be started",
25805        cx,
25806    );
25807}
25808
25809#[gpui::test]
25810async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25811    init_test(cx, |_| {});
25812
25813    let mut cx = EditorTestContext::new(cx).await;
25814    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25816
25817    // test cursor move to start of each line on tab
25818    // for `if`, `elif`, `else`, `while`, `with` and `for`
25819    cx.set_state(indoc! {"
25820        def main():
25821        ˇ    for item in items:
25822        ˇ        while item.active:
25823        ˇ            if item.value > 10:
25824        ˇ                continue
25825        ˇ            elif item.value < 0:
25826        ˇ                break
25827        ˇ            else:
25828        ˇ                with item.context() as ctx:
25829        ˇ                    yield count
25830        ˇ        else:
25831        ˇ            log('while else')
25832        ˇ    else:
25833        ˇ        log('for else')
25834    "});
25835    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25836    cx.wait_for_autoindent_applied().await;
25837    cx.assert_editor_state(indoc! {"
25838        def main():
25839            ˇfor item in items:
25840                ˇwhile item.active:
25841                    ˇif item.value > 10:
25842                        ˇcontinue
25843                    ˇelif item.value < 0:
25844                        ˇbreak
25845                    ˇelse:
25846                        ˇwith item.context() as ctx:
25847                            ˇyield count
25848                ˇelse:
25849                    ˇlog('while else')
25850            ˇelse:
25851                ˇlog('for else')
25852    "});
25853    // test relative indent is preserved when tab
25854    // for `if`, `elif`, `else`, `while`, `with` and `for`
25855    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25856    cx.wait_for_autoindent_applied().await;
25857    cx.assert_editor_state(indoc! {"
25858        def main():
25859                ˇfor item in items:
25860                    ˇwhile item.active:
25861                        ˇif item.value > 10:
25862                            ˇcontinue
25863                        ˇelif item.value < 0:
25864                            ˇbreak
25865                        ˇelse:
25866                            ˇwith item.context() as ctx:
25867                                ˇyield count
25868                    ˇelse:
25869                        ˇlog('while else')
25870                ˇelse:
25871                    ˇlog('for else')
25872    "});
25873
25874    // test cursor move to start of each line on tab
25875    // for `try`, `except`, `else`, `finally`, `match` and `def`
25876    cx.set_state(indoc! {"
25877        def main():
25878        ˇ    try:
25879        ˇ        fetch()
25880        ˇ    except ValueError:
25881        ˇ        handle_error()
25882        ˇ    else:
25883        ˇ        match value:
25884        ˇ            case _:
25885        ˇ    finally:
25886        ˇ        def status():
25887        ˇ            return 0
25888    "});
25889    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25890    cx.wait_for_autoindent_applied().await;
25891    cx.assert_editor_state(indoc! {"
25892        def main():
25893            ˇtry:
25894                ˇfetch()
25895            ˇexcept ValueError:
25896                ˇhandle_error()
25897            ˇelse:
25898                ˇmatch value:
25899                    ˇcase _:
25900            ˇfinally:
25901                ˇdef status():
25902                    ˇreturn 0
25903    "});
25904    // test relative indent is preserved when tab
25905    // for `try`, `except`, `else`, `finally`, `match` and `def`
25906    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25907    cx.wait_for_autoindent_applied().await;
25908    cx.assert_editor_state(indoc! {"
25909        def main():
25910                ˇtry:
25911                    ˇfetch()
25912                ˇexcept ValueError:
25913                    ˇhandle_error()
25914                ˇelse:
25915                    ˇmatch value:
25916                        ˇcase _:
25917                ˇfinally:
25918                    ˇdef status():
25919                        ˇreturn 0
25920    "});
25921}
25922
25923#[gpui::test]
25924async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25925    init_test(cx, |_| {});
25926
25927    let mut cx = EditorTestContext::new(cx).await;
25928    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25929    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25930
25931    // test `else` auto outdents when typed inside `if` block
25932    cx.set_state(indoc! {"
25933        def main():
25934            if i == 2:
25935                return
25936                ˇ
25937    "});
25938    cx.update_editor(|editor, window, cx| {
25939        editor.handle_input("else:", window, cx);
25940    });
25941    cx.wait_for_autoindent_applied().await;
25942    cx.assert_editor_state(indoc! {"
25943        def main():
25944            if i == 2:
25945                return
25946            else:ˇ
25947    "});
25948
25949    // test `except` auto outdents when typed inside `try` block
25950    cx.set_state(indoc! {"
25951        def main():
25952            try:
25953                i = 2
25954                ˇ
25955    "});
25956    cx.update_editor(|editor, window, cx| {
25957        editor.handle_input("except:", window, cx);
25958    });
25959    cx.wait_for_autoindent_applied().await;
25960    cx.assert_editor_state(indoc! {"
25961        def main():
25962            try:
25963                i = 2
25964            except:ˇ
25965    "});
25966
25967    // test `else` auto outdents when typed inside `except` block
25968    cx.set_state(indoc! {"
25969        def main():
25970            try:
25971                i = 2
25972            except:
25973                j = 2
25974                ˇ
25975    "});
25976    cx.update_editor(|editor, window, cx| {
25977        editor.handle_input("else:", window, cx);
25978    });
25979    cx.wait_for_autoindent_applied().await;
25980    cx.assert_editor_state(indoc! {"
25981        def main():
25982            try:
25983                i = 2
25984            except:
25985                j = 2
25986            else:ˇ
25987    "});
25988
25989    // test `finally` auto outdents when typed inside `else` block
25990    cx.set_state(indoc! {"
25991        def main():
25992            try:
25993                i = 2
25994            except:
25995                j = 2
25996            else:
25997                k = 2
25998                ˇ
25999    "});
26000    cx.update_editor(|editor, window, cx| {
26001        editor.handle_input("finally:", window, cx);
26002    });
26003    cx.wait_for_autoindent_applied().await;
26004    cx.assert_editor_state(indoc! {"
26005        def main():
26006            try:
26007                i = 2
26008            except:
26009                j = 2
26010            else:
26011                k = 2
26012            finally:ˇ
26013    "});
26014
26015    // test `else` does not outdents when typed inside `except` block right after for block
26016    cx.set_state(indoc! {"
26017        def main():
26018            try:
26019                i = 2
26020            except:
26021                for i in range(n):
26022                    pass
26023                ˇ
26024    "});
26025    cx.update_editor(|editor, window, cx| {
26026        editor.handle_input("else:", window, cx);
26027    });
26028    cx.wait_for_autoindent_applied().await;
26029    cx.assert_editor_state(indoc! {"
26030        def main():
26031            try:
26032                i = 2
26033            except:
26034                for i in range(n):
26035                    pass
26036                else:ˇ
26037    "});
26038
26039    // test `finally` auto outdents when typed inside `else` block right after for block
26040    cx.set_state(indoc! {"
26041        def main():
26042            try:
26043                i = 2
26044            except:
26045                j = 2
26046            else:
26047                for i in range(n):
26048                    pass
26049                ˇ
26050    "});
26051    cx.update_editor(|editor, window, cx| {
26052        editor.handle_input("finally:", window, cx);
26053    });
26054    cx.wait_for_autoindent_applied().await;
26055    cx.assert_editor_state(indoc! {"
26056        def main():
26057            try:
26058                i = 2
26059            except:
26060                j = 2
26061            else:
26062                for i in range(n):
26063                    pass
26064            finally:ˇ
26065    "});
26066
26067    // test `except` outdents to inner "try" block
26068    cx.set_state(indoc! {"
26069        def main():
26070            try:
26071                i = 2
26072                if i == 2:
26073                    try:
26074                        i = 3
26075                        ˇ
26076    "});
26077    cx.update_editor(|editor, window, cx| {
26078        editor.handle_input("except:", window, cx);
26079    });
26080    cx.wait_for_autoindent_applied().await;
26081    cx.assert_editor_state(indoc! {"
26082        def main():
26083            try:
26084                i = 2
26085                if i == 2:
26086                    try:
26087                        i = 3
26088                    except:ˇ
26089    "});
26090
26091    // test `except` outdents to outer "try" block
26092    cx.set_state(indoc! {"
26093        def main():
26094            try:
26095                i = 2
26096                if i == 2:
26097                    try:
26098                        i = 3
26099                ˇ
26100    "});
26101    cx.update_editor(|editor, window, cx| {
26102        editor.handle_input("except:", window, cx);
26103    });
26104    cx.wait_for_autoindent_applied().await;
26105    cx.assert_editor_state(indoc! {"
26106        def main():
26107            try:
26108                i = 2
26109                if i == 2:
26110                    try:
26111                        i = 3
26112            except:ˇ
26113    "});
26114
26115    // test `else` stays at correct indent when typed after `for` block
26116    cx.set_state(indoc! {"
26117        def main():
26118            for i in range(10):
26119                if i == 3:
26120                    break
26121            ˇ
26122    "});
26123    cx.update_editor(|editor, window, cx| {
26124        editor.handle_input("else:", window, cx);
26125    });
26126    cx.wait_for_autoindent_applied().await;
26127    cx.assert_editor_state(indoc! {"
26128        def main():
26129            for i in range(10):
26130                if i == 3:
26131                    break
26132            else:ˇ
26133    "});
26134
26135    // test does not outdent on typing after line with square brackets
26136    cx.set_state(indoc! {"
26137        def f() -> list[str]:
26138            ˇ
26139    "});
26140    cx.update_editor(|editor, window, cx| {
26141        editor.handle_input("a", window, cx);
26142    });
26143    cx.wait_for_autoindent_applied().await;
26144    cx.assert_editor_state(indoc! {"
26145        def f() -> list[str]:
2614626147    "});
26148
26149    // test does not outdent on typing : after case keyword
26150    cx.set_state(indoc! {"
26151        match 1:
26152            caseˇ
26153    "});
26154    cx.update_editor(|editor, window, cx| {
26155        editor.handle_input(":", window, cx);
26156    });
26157    cx.wait_for_autoindent_applied().await;
26158    cx.assert_editor_state(indoc! {"
26159        match 1:
26160            case:ˇ
26161    "});
26162}
26163
26164#[gpui::test]
26165async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26166    init_test(cx, |_| {});
26167    update_test_language_settings(cx, |settings| {
26168        settings.defaults.extend_comment_on_newline = Some(false);
26169    });
26170    let mut cx = EditorTestContext::new(cx).await;
26171    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26172    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26173
26174    // test correct indent after newline on comment
26175    cx.set_state(indoc! {"
26176        # COMMENT:ˇ
26177    "});
26178    cx.update_editor(|editor, window, cx| {
26179        editor.newline(&Newline, window, cx);
26180    });
26181    cx.wait_for_autoindent_applied().await;
26182    cx.assert_editor_state(indoc! {"
26183        # COMMENT:
26184        ˇ
26185    "});
26186
26187    // test correct indent after newline in brackets
26188    cx.set_state(indoc! {"
26189        {ˇ}
26190    "});
26191    cx.update_editor(|editor, window, cx| {
26192        editor.newline(&Newline, window, cx);
26193    });
26194    cx.wait_for_autoindent_applied().await;
26195    cx.assert_editor_state(indoc! {"
26196        {
26197            ˇ
26198        }
26199    "});
26200
26201    cx.set_state(indoc! {"
26202        (ˇ)
26203    "});
26204    cx.update_editor(|editor, window, cx| {
26205        editor.newline(&Newline, window, cx);
26206    });
26207    cx.run_until_parked();
26208    cx.assert_editor_state(indoc! {"
26209        (
26210            ˇ
26211        )
26212    "});
26213
26214    // do not indent after empty lists or dictionaries
26215    cx.set_state(indoc! {"
26216        a = []ˇ
26217    "});
26218    cx.update_editor(|editor, window, cx| {
26219        editor.newline(&Newline, window, cx);
26220    });
26221    cx.run_until_parked();
26222    cx.assert_editor_state(indoc! {"
26223        a = []
26224        ˇ
26225    "});
26226}
26227
26228#[gpui::test]
26229async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26230    init_test(cx, |_| {});
26231
26232    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26233    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26234    language_registry.add(markdown_lang());
26235    language_registry.add(python_lang);
26236
26237    let mut cx = EditorTestContext::new(cx).await;
26238    cx.update_buffer(|buffer, cx| {
26239        buffer.set_language_registry(language_registry);
26240        buffer.set_language(Some(markdown_lang()), cx);
26241    });
26242
26243    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26244    cx.set_state(indoc! {"
26245        # Heading
26246
26247        ```python
26248        def main():
26249            if condition:
26250                pass
26251                ˇ
26252        ```
26253    "});
26254    cx.update_editor(|editor, window, cx| {
26255        editor.handle_input("else:", window, cx);
26256    });
26257    cx.run_until_parked();
26258    cx.assert_editor_state(indoc! {"
26259        # Heading
26260
26261        ```python
26262        def main():
26263            if condition:
26264                pass
26265            else:ˇ
26266        ```
26267    "});
26268}
26269
26270#[gpui::test]
26271async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26272    init_test(cx, |_| {});
26273
26274    let mut cx = EditorTestContext::new(cx).await;
26275    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26276    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26277
26278    // test cursor move to start of each line on tab
26279    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26280    cx.set_state(indoc! {"
26281        function main() {
26282        ˇ    for item in $items; do
26283        ˇ        while [ -n \"$item\" ]; do
26284        ˇ            if [ \"$value\" -gt 10 ]; then
26285        ˇ                continue
26286        ˇ            elif [ \"$value\" -lt 0 ]; then
26287        ˇ                break
26288        ˇ            else
26289        ˇ                echo \"$item\"
26290        ˇ            fi
26291        ˇ        done
26292        ˇ    done
26293        ˇ}
26294    "});
26295    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26296    cx.wait_for_autoindent_applied().await;
26297    cx.assert_editor_state(indoc! {"
26298        function main() {
26299            ˇfor item in $items; do
26300                ˇwhile [ -n \"$item\" ]; do
26301                    ˇif [ \"$value\" -gt 10 ]; then
26302                        ˇcontinue
26303                    ˇelif [ \"$value\" -lt 0 ]; then
26304                        ˇbreak
26305                    ˇelse
26306                        ˇecho \"$item\"
26307                    ˇfi
26308                ˇdone
26309            ˇdone
26310        ˇ}
26311    "});
26312    // test relative indent is preserved when tab
26313    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26314    cx.wait_for_autoindent_applied().await;
26315    cx.assert_editor_state(indoc! {"
26316        function main() {
26317                ˇfor item in $items; do
26318                    ˇwhile [ -n \"$item\" ]; do
26319                        ˇif [ \"$value\" -gt 10 ]; then
26320                            ˇcontinue
26321                        ˇelif [ \"$value\" -lt 0 ]; then
26322                            ˇbreak
26323                        ˇelse
26324                            ˇecho \"$item\"
26325                        ˇfi
26326                    ˇdone
26327                ˇdone
26328            ˇ}
26329    "});
26330
26331    // test cursor move to start of each line on tab
26332    // for `case` statement with patterns
26333    cx.set_state(indoc! {"
26334        function handle() {
26335        ˇ    case \"$1\" in
26336        ˇ        start)
26337        ˇ            echo \"a\"
26338        ˇ            ;;
26339        ˇ        stop)
26340        ˇ            echo \"b\"
26341        ˇ            ;;
26342        ˇ        *)
26343        ˇ            echo \"c\"
26344        ˇ            ;;
26345        ˇ    esac
26346        ˇ}
26347    "});
26348    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26349    cx.wait_for_autoindent_applied().await;
26350    cx.assert_editor_state(indoc! {"
26351        function handle() {
26352            ˇcase \"$1\" in
26353                ˇstart)
26354                    ˇecho \"a\"
26355                    ˇ;;
26356                ˇstop)
26357                    ˇecho \"b\"
26358                    ˇ;;
26359                ˇ*)
26360                    ˇecho \"c\"
26361                    ˇ;;
26362            ˇesac
26363        ˇ}
26364    "});
26365}
26366
26367#[gpui::test]
26368async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26369    init_test(cx, |_| {});
26370
26371    let mut cx = EditorTestContext::new(cx).await;
26372    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26373    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26374
26375    // test indents on comment insert
26376    cx.set_state(indoc! {"
26377        function main() {
26378        ˇ    for item in $items; do
26379        ˇ        while [ -n \"$item\" ]; do
26380        ˇ            if [ \"$value\" -gt 10 ]; then
26381        ˇ                continue
26382        ˇ            elif [ \"$value\" -lt 0 ]; then
26383        ˇ                break
26384        ˇ            else
26385        ˇ                echo \"$item\"
26386        ˇ            fi
26387        ˇ        done
26388        ˇ    done
26389        ˇ}
26390    "});
26391    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26392    cx.wait_for_autoindent_applied().await;
26393    cx.assert_editor_state(indoc! {"
26394        function main() {
26395        #ˇ    for item in $items; do
26396        #ˇ        while [ -n \"$item\" ]; do
26397        #ˇ            if [ \"$value\" -gt 10 ]; then
26398        #ˇ                continue
26399        #ˇ            elif [ \"$value\" -lt 0 ]; then
26400        #ˇ                break
26401        #ˇ            else
26402        #ˇ                echo \"$item\"
26403        #ˇ            fi
26404        #ˇ        done
26405        #ˇ    done
26406        #ˇ}
26407    "});
26408}
26409
26410#[gpui::test]
26411async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26412    init_test(cx, |_| {});
26413
26414    let mut cx = EditorTestContext::new(cx).await;
26415    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26416    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26417
26418    // test `else` auto outdents when typed inside `if` block
26419    cx.set_state(indoc! {"
26420        if [ \"$1\" = \"test\" ]; then
26421            echo \"foo bar\"
26422            ˇ
26423    "});
26424    cx.update_editor(|editor, window, cx| {
26425        editor.handle_input("else", window, cx);
26426    });
26427    cx.wait_for_autoindent_applied().await;
26428    cx.assert_editor_state(indoc! {"
26429        if [ \"$1\" = \"test\" ]; then
26430            echo \"foo bar\"
26431        elseˇ
26432    "});
26433
26434    // test `elif` auto outdents when typed inside `if` block
26435    cx.set_state(indoc! {"
26436        if [ \"$1\" = \"test\" ]; then
26437            echo \"foo bar\"
26438            ˇ
26439    "});
26440    cx.update_editor(|editor, window, cx| {
26441        editor.handle_input("elif", window, cx);
26442    });
26443    cx.wait_for_autoindent_applied().await;
26444    cx.assert_editor_state(indoc! {"
26445        if [ \"$1\" = \"test\" ]; then
26446            echo \"foo bar\"
26447        elifˇ
26448    "});
26449
26450    // test `fi` auto outdents when typed inside `else` block
26451    cx.set_state(indoc! {"
26452        if [ \"$1\" = \"test\" ]; then
26453            echo \"foo bar\"
26454        else
26455            echo \"bar baz\"
26456            ˇ
26457    "});
26458    cx.update_editor(|editor, window, cx| {
26459        editor.handle_input("fi", window, cx);
26460    });
26461    cx.wait_for_autoindent_applied().await;
26462    cx.assert_editor_state(indoc! {"
26463        if [ \"$1\" = \"test\" ]; then
26464            echo \"foo bar\"
26465        else
26466            echo \"bar baz\"
26467        fiˇ
26468    "});
26469
26470    // test `done` auto outdents when typed inside `while` block
26471    cx.set_state(indoc! {"
26472        while read line; do
26473            echo \"$line\"
26474            ˇ
26475    "});
26476    cx.update_editor(|editor, window, cx| {
26477        editor.handle_input("done", window, cx);
26478    });
26479    cx.wait_for_autoindent_applied().await;
26480    cx.assert_editor_state(indoc! {"
26481        while read line; do
26482            echo \"$line\"
26483        doneˇ
26484    "});
26485
26486    // test `done` auto outdents when typed inside `for` block
26487    cx.set_state(indoc! {"
26488        for file in *.txt; do
26489            cat \"$file\"
26490            ˇ
26491    "});
26492    cx.update_editor(|editor, window, cx| {
26493        editor.handle_input("done", window, cx);
26494    });
26495    cx.wait_for_autoindent_applied().await;
26496    cx.assert_editor_state(indoc! {"
26497        for file in *.txt; do
26498            cat \"$file\"
26499        doneˇ
26500    "});
26501
26502    // test `esac` auto outdents when typed inside `case` block
26503    cx.set_state(indoc! {"
26504        case \"$1\" in
26505            start)
26506                echo \"foo bar\"
26507                ;;
26508            stop)
26509                echo \"bar baz\"
26510                ;;
26511            ˇ
26512    "});
26513    cx.update_editor(|editor, window, cx| {
26514        editor.handle_input("esac", window, cx);
26515    });
26516    cx.wait_for_autoindent_applied().await;
26517    cx.assert_editor_state(indoc! {"
26518        case \"$1\" in
26519            start)
26520                echo \"foo bar\"
26521                ;;
26522            stop)
26523                echo \"bar baz\"
26524                ;;
26525        esacˇ
26526    "});
26527
26528    // test `*)` auto outdents when typed inside `case` block
26529    cx.set_state(indoc! {"
26530        case \"$1\" in
26531            start)
26532                echo \"foo bar\"
26533                ;;
26534                ˇ
26535    "});
26536    cx.update_editor(|editor, window, cx| {
26537        editor.handle_input("*)", window, cx);
26538    });
26539    cx.wait_for_autoindent_applied().await;
26540    cx.assert_editor_state(indoc! {"
26541        case \"$1\" in
26542            start)
26543                echo \"foo bar\"
26544                ;;
26545            *)ˇ
26546    "});
26547
26548    // test `fi` outdents to correct level with nested if blocks
26549    cx.set_state(indoc! {"
26550        if [ \"$1\" = \"test\" ]; then
26551            echo \"outer if\"
26552            if [ \"$2\" = \"debug\" ]; then
26553                echo \"inner if\"
26554                ˇ
26555    "});
26556    cx.update_editor(|editor, window, cx| {
26557        editor.handle_input("fi", window, cx);
26558    });
26559    cx.wait_for_autoindent_applied().await;
26560    cx.assert_editor_state(indoc! {"
26561        if [ \"$1\" = \"test\" ]; then
26562            echo \"outer if\"
26563            if [ \"$2\" = \"debug\" ]; then
26564                echo \"inner if\"
26565            fiˇ
26566    "});
26567}
26568
26569#[gpui::test]
26570async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26571    init_test(cx, |_| {});
26572    update_test_language_settings(cx, |settings| {
26573        settings.defaults.extend_comment_on_newline = Some(false);
26574    });
26575    let mut cx = EditorTestContext::new(cx).await;
26576    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26577    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26578
26579    // test correct indent after newline on comment
26580    cx.set_state(indoc! {"
26581        # COMMENT:ˇ
26582    "});
26583    cx.update_editor(|editor, window, cx| {
26584        editor.newline(&Newline, window, cx);
26585    });
26586    cx.wait_for_autoindent_applied().await;
26587    cx.assert_editor_state(indoc! {"
26588        # COMMENT:
26589        ˇ
26590    "});
26591
26592    // test correct indent after newline after `then`
26593    cx.set_state(indoc! {"
26594
26595        if [ \"$1\" = \"test\" ]; thenˇ
26596    "});
26597    cx.update_editor(|editor, window, cx| {
26598        editor.newline(&Newline, window, cx);
26599    });
26600    cx.wait_for_autoindent_applied().await;
26601    cx.assert_editor_state(indoc! {"
26602
26603        if [ \"$1\" = \"test\" ]; then
26604            ˇ
26605    "});
26606
26607    // test correct indent after newline after `else`
26608    cx.set_state(indoc! {"
26609        if [ \"$1\" = \"test\" ]; then
26610        elseˇ
26611    "});
26612    cx.update_editor(|editor, window, cx| {
26613        editor.newline(&Newline, window, cx);
26614    });
26615    cx.wait_for_autoindent_applied().await;
26616    cx.assert_editor_state(indoc! {"
26617        if [ \"$1\" = \"test\" ]; then
26618        else
26619            ˇ
26620    "});
26621
26622    // test correct indent after newline after `elif`
26623    cx.set_state(indoc! {"
26624        if [ \"$1\" = \"test\" ]; then
26625        elifˇ
26626    "});
26627    cx.update_editor(|editor, window, cx| {
26628        editor.newline(&Newline, window, cx);
26629    });
26630    cx.wait_for_autoindent_applied().await;
26631    cx.assert_editor_state(indoc! {"
26632        if [ \"$1\" = \"test\" ]; then
26633        elif
26634            ˇ
26635    "});
26636
26637    // test correct indent after newline after `do`
26638    cx.set_state(indoc! {"
26639        for file in *.txt; doˇ
26640    "});
26641    cx.update_editor(|editor, window, cx| {
26642        editor.newline(&Newline, window, cx);
26643    });
26644    cx.wait_for_autoindent_applied().await;
26645    cx.assert_editor_state(indoc! {"
26646        for file in *.txt; do
26647            ˇ
26648    "});
26649
26650    // test correct indent after newline after case pattern
26651    cx.set_state(indoc! {"
26652        case \"$1\" in
26653            start)ˇ
26654    "});
26655    cx.update_editor(|editor, window, cx| {
26656        editor.newline(&Newline, window, cx);
26657    });
26658    cx.wait_for_autoindent_applied().await;
26659    cx.assert_editor_state(indoc! {"
26660        case \"$1\" in
26661            start)
26662                ˇ
26663    "});
26664
26665    // test correct indent after newline after case pattern
26666    cx.set_state(indoc! {"
26667        case \"$1\" in
26668            start)
26669                ;;
26670            *)ˇ
26671    "});
26672    cx.update_editor(|editor, window, cx| {
26673        editor.newline(&Newline, window, cx);
26674    });
26675    cx.wait_for_autoindent_applied().await;
26676    cx.assert_editor_state(indoc! {"
26677        case \"$1\" in
26678            start)
26679                ;;
26680            *)
26681                ˇ
26682    "});
26683
26684    // test correct indent after newline after function opening brace
26685    cx.set_state(indoc! {"
26686        function test() {ˇ}
26687    "});
26688    cx.update_editor(|editor, window, cx| {
26689        editor.newline(&Newline, window, cx);
26690    });
26691    cx.wait_for_autoindent_applied().await;
26692    cx.assert_editor_state(indoc! {"
26693        function test() {
26694            ˇ
26695        }
26696    "});
26697
26698    // test no extra indent after semicolon on same line
26699    cx.set_state(indoc! {"
26700        echo \"test\"26701    "});
26702    cx.update_editor(|editor, window, cx| {
26703        editor.newline(&Newline, window, cx);
26704    });
26705    cx.wait_for_autoindent_applied().await;
26706    cx.assert_editor_state(indoc! {"
26707        echo \"test\";
26708        ˇ
26709    "});
26710}
26711
26712fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26713    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26714    point..point
26715}
26716
26717#[track_caller]
26718fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26719    let (text, ranges) = marked_text_ranges(marked_text, true);
26720    assert_eq!(editor.text(cx), text);
26721    assert_eq!(
26722        editor.selections.ranges(&editor.display_snapshot(cx)),
26723        ranges
26724            .iter()
26725            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26726            .collect::<Vec<_>>(),
26727        "Assert selections are {}",
26728        marked_text
26729    );
26730}
26731
26732pub fn handle_signature_help_request(
26733    cx: &mut EditorLspTestContext,
26734    mocked_response: lsp::SignatureHelp,
26735) -> impl Future<Output = ()> + use<> {
26736    let mut request =
26737        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26738            let mocked_response = mocked_response.clone();
26739            async move { Ok(Some(mocked_response)) }
26740        });
26741
26742    async move {
26743        request.next().await;
26744    }
26745}
26746
26747#[track_caller]
26748pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26749    cx.update_editor(|editor, _, _| {
26750        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26751            let entries = menu.entries.borrow();
26752            let entries = entries
26753                .iter()
26754                .map(|entry| entry.string.as_str())
26755                .collect::<Vec<_>>();
26756            assert_eq!(entries, expected);
26757        } else {
26758            panic!("Expected completions menu");
26759        }
26760    });
26761}
26762
26763#[gpui::test]
26764async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26765    init_test(cx, |_| {});
26766    let mut cx = EditorLspTestContext::new_rust(
26767        lsp::ServerCapabilities {
26768            completion_provider: Some(lsp::CompletionOptions {
26769                ..Default::default()
26770            }),
26771            ..Default::default()
26772        },
26773        cx,
26774    )
26775    .await;
26776    cx.lsp
26777        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26778            Ok(Some(lsp::CompletionResponse::Array(vec![
26779                lsp::CompletionItem {
26780                    label: "unsafe".into(),
26781                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26782                        range: lsp::Range {
26783                            start: lsp::Position {
26784                                line: 0,
26785                                character: 9,
26786                            },
26787                            end: lsp::Position {
26788                                line: 0,
26789                                character: 11,
26790                            },
26791                        },
26792                        new_text: "unsafe".to_string(),
26793                    })),
26794                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26795                    ..Default::default()
26796                },
26797            ])))
26798        });
26799
26800    cx.update_editor(|editor, _, cx| {
26801        editor.project().unwrap().update(cx, |project, cx| {
26802            project.snippets().update(cx, |snippets, _cx| {
26803                snippets.add_snippet_for_test(
26804                    None,
26805                    PathBuf::from("test_snippets.json"),
26806                    vec![
26807                        Arc::new(project::snippet_provider::Snippet {
26808                            prefix: vec![
26809                                "unlimited word count".to_string(),
26810                                "unlimit word count".to_string(),
26811                                "unlimited unknown".to_string(),
26812                            ],
26813                            body: "this is many words".to_string(),
26814                            description: Some("description".to_string()),
26815                            name: "multi-word snippet test".to_string(),
26816                        }),
26817                        Arc::new(project::snippet_provider::Snippet {
26818                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26819                            body: "fewer words".to_string(),
26820                            description: Some("alt description".to_string()),
26821                            name: "other name".to_string(),
26822                        }),
26823                        Arc::new(project::snippet_provider::Snippet {
26824                            prefix: vec!["ab aa".to_string()],
26825                            body: "abcd".to_string(),
26826                            description: None,
26827                            name: "alphabet".to_string(),
26828                        }),
26829                    ],
26830                );
26831            });
26832        })
26833    });
26834
26835    let get_completions = |cx: &mut EditorLspTestContext| {
26836        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26837            Some(CodeContextMenu::Completions(context_menu)) => {
26838                let entries = context_menu.entries.borrow();
26839                entries
26840                    .iter()
26841                    .map(|entry| entry.string.clone())
26842                    .collect_vec()
26843            }
26844            _ => vec![],
26845        })
26846    };
26847
26848    // snippets:
26849    //  @foo
26850    //  foo bar
26851    //
26852    // when typing:
26853    //
26854    // when typing:
26855    //  - if I type a symbol "open the completions with snippets only"
26856    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26857    //
26858    // stuff we need:
26859    //  - filtering logic change?
26860    //  - remember how far back the completion started.
26861
26862    let test_cases: &[(&str, &[&str])] = &[
26863        (
26864            "un",
26865            &[
26866                "unsafe",
26867                "unlimit word count",
26868                "unlimited unknown",
26869                "unlimited word count",
26870                "unsnip",
26871            ],
26872        ),
26873        (
26874            "u ",
26875            &[
26876                "unlimit word count",
26877                "unlimited unknown",
26878                "unlimited word count",
26879            ],
26880        ),
26881        ("u a", &["ab aa", "unsafe"]), // unsAfe
26882        (
26883            "u u",
26884            &[
26885                "unsafe",
26886                "unlimit word count",
26887                "unlimited unknown", // ranked highest among snippets
26888                "unlimited word count",
26889                "unsnip",
26890            ],
26891        ),
26892        ("uw c", &["unlimit word count", "unlimited word count"]),
26893        (
26894            "u w",
26895            &[
26896                "unlimit word count",
26897                "unlimited word count",
26898                "unlimited unknown",
26899            ],
26900        ),
26901        ("u w ", &["unlimit word count", "unlimited word count"]),
26902        (
26903            "u ",
26904            &[
26905                "unlimit word count",
26906                "unlimited unknown",
26907                "unlimited word count",
26908            ],
26909        ),
26910        ("wor", &[]),
26911        ("uf", &["unsafe"]),
26912        ("af", &["unsafe"]),
26913        ("afu", &[]),
26914        (
26915            "ue",
26916            &["unsafe", "unlimited unknown", "unlimited word count"],
26917        ),
26918        ("@", &["@few"]),
26919        ("@few", &["@few"]),
26920        ("@ ", &[]),
26921        ("a@", &["@few"]),
26922        ("a@f", &["@few", "unsafe"]),
26923        ("a@fw", &["@few"]),
26924        ("a", &["ab aa", "unsafe"]),
26925        ("aa", &["ab aa"]),
26926        ("aaa", &["ab aa"]),
26927        ("ab", &["ab aa"]),
26928        ("ab ", &["ab aa"]),
26929        ("ab a", &["ab aa", "unsafe"]),
26930        ("ab ab", &["ab aa"]),
26931        ("ab ab aa", &["ab aa"]),
26932    ];
26933
26934    for &(input_to_simulate, expected_completions) in test_cases {
26935        cx.set_state("fn a() { ˇ }\n");
26936        for c in input_to_simulate.split("") {
26937            cx.simulate_input(c);
26938            cx.run_until_parked();
26939        }
26940        let expected_completions = expected_completions
26941            .iter()
26942            .map(|s| s.to_string())
26943            .collect_vec();
26944        assert_eq!(
26945            get_completions(&mut cx),
26946            expected_completions,
26947            "< actual / expected >, input = {input_to_simulate:?}",
26948        );
26949    }
26950}
26951
26952/// Handle completion request passing a marked string specifying where the completion
26953/// should be triggered from using '|' character, what range should be replaced, and what completions
26954/// should be returned using '<' and '>' to delimit the range.
26955///
26956/// Also see `handle_completion_request_with_insert_and_replace`.
26957#[track_caller]
26958pub fn handle_completion_request(
26959    marked_string: &str,
26960    completions: Vec<&'static str>,
26961    is_incomplete: bool,
26962    counter: Arc<AtomicUsize>,
26963    cx: &mut EditorLspTestContext,
26964) -> impl Future<Output = ()> {
26965    let complete_from_marker: TextRangeMarker = '|'.into();
26966    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26967    let (_, mut marked_ranges) = marked_text_ranges_by(
26968        marked_string,
26969        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26970    );
26971
26972    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26973        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26974    ));
26975    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26976    let replace_range =
26977        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26978
26979    let mut request =
26980        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26981            let completions = completions.clone();
26982            counter.fetch_add(1, atomic::Ordering::Release);
26983            async move {
26984                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26985                assert_eq!(
26986                    params.text_document_position.position,
26987                    complete_from_position
26988                );
26989                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26990                    is_incomplete,
26991                    item_defaults: None,
26992                    items: completions
26993                        .iter()
26994                        .map(|completion_text| lsp::CompletionItem {
26995                            label: completion_text.to_string(),
26996                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26997                                range: replace_range,
26998                                new_text: completion_text.to_string(),
26999                            })),
27000                            ..Default::default()
27001                        })
27002                        .collect(),
27003                })))
27004            }
27005        });
27006
27007    async move {
27008        request.next().await;
27009    }
27010}
27011
27012/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27013/// given instead, which also contains an `insert` range.
27014///
27015/// This function uses markers to define ranges:
27016/// - `|` marks the cursor position
27017/// - `<>` marks the replace range
27018/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27019pub fn handle_completion_request_with_insert_and_replace(
27020    cx: &mut EditorLspTestContext,
27021    marked_string: &str,
27022    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27023    counter: Arc<AtomicUsize>,
27024) -> impl Future<Output = ()> {
27025    let complete_from_marker: TextRangeMarker = '|'.into();
27026    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27027    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27028
27029    let (_, mut marked_ranges) = marked_text_ranges_by(
27030        marked_string,
27031        vec![
27032            complete_from_marker.clone(),
27033            replace_range_marker.clone(),
27034            insert_range_marker.clone(),
27035        ],
27036    );
27037
27038    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27039        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27040    ));
27041    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27042    let replace_range =
27043        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27044
27045    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27046        Some(ranges) if !ranges.is_empty() => {
27047            let range1 = ranges[0].clone();
27048            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27049        }
27050        _ => lsp::Range {
27051            start: replace_range.start,
27052            end: complete_from_position,
27053        },
27054    };
27055
27056    let mut request =
27057        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27058            let completions = completions.clone();
27059            counter.fetch_add(1, atomic::Ordering::Release);
27060            async move {
27061                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27062                assert_eq!(
27063                    params.text_document_position.position, complete_from_position,
27064                    "marker `|` position doesn't match",
27065                );
27066                Ok(Some(lsp::CompletionResponse::Array(
27067                    completions
27068                        .iter()
27069                        .map(|(label, new_text)| lsp::CompletionItem {
27070                            label: label.to_string(),
27071                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27072                                lsp::InsertReplaceEdit {
27073                                    insert: insert_range,
27074                                    replace: replace_range,
27075                                    new_text: new_text.to_string(),
27076                                },
27077                            )),
27078                            ..Default::default()
27079                        })
27080                        .collect(),
27081                )))
27082            }
27083        });
27084
27085    async move {
27086        request.next().await;
27087    }
27088}
27089
27090fn handle_resolve_completion_request(
27091    cx: &mut EditorLspTestContext,
27092    edits: Option<Vec<(&'static str, &'static str)>>,
27093) -> impl Future<Output = ()> {
27094    let edits = edits.map(|edits| {
27095        edits
27096            .iter()
27097            .map(|(marked_string, new_text)| {
27098                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27099                let replace_range = cx.to_lsp_range(
27100                    MultiBufferOffset(marked_ranges[0].start)
27101                        ..MultiBufferOffset(marked_ranges[0].end),
27102                );
27103                lsp::TextEdit::new(replace_range, new_text.to_string())
27104            })
27105            .collect::<Vec<_>>()
27106    });
27107
27108    let mut request =
27109        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27110            let edits = edits.clone();
27111            async move {
27112                Ok(lsp::CompletionItem {
27113                    additional_text_edits: edits,
27114                    ..Default::default()
27115                })
27116            }
27117        });
27118
27119    async move {
27120        request.next().await;
27121    }
27122}
27123
27124pub(crate) fn update_test_language_settings(
27125    cx: &mut TestAppContext,
27126    f: impl Fn(&mut AllLanguageSettingsContent),
27127) {
27128    cx.update(|cx| {
27129        SettingsStore::update_global(cx, |store, cx| {
27130            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27131        });
27132    });
27133}
27134
27135pub(crate) fn update_test_project_settings(
27136    cx: &mut TestAppContext,
27137    f: impl Fn(&mut ProjectSettingsContent),
27138) {
27139    cx.update(|cx| {
27140        SettingsStore::update_global(cx, |store, cx| {
27141            store.update_user_settings(cx, |settings| f(&mut settings.project));
27142        });
27143    });
27144}
27145
27146pub(crate) fn update_test_editor_settings(
27147    cx: &mut TestAppContext,
27148    f: impl Fn(&mut EditorSettingsContent),
27149) {
27150    cx.update(|cx| {
27151        SettingsStore::update_global(cx, |store, cx| {
27152            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27153        })
27154    })
27155}
27156
27157pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27158    cx.update(|cx| {
27159        assets::Assets.load_test_fonts(cx);
27160        let store = SettingsStore::test(cx);
27161        cx.set_global(store);
27162        theme::init(theme::LoadThemes::JustBase, cx);
27163        release_channel::init(semver::Version::new(0, 0, 0), cx);
27164        crate::init(cx);
27165    });
27166    zlog::init_test();
27167    update_test_language_settings(cx, f);
27168}
27169
27170#[track_caller]
27171fn assert_hunk_revert(
27172    not_reverted_text_with_selections: &str,
27173    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27174    expected_reverted_text_with_selections: &str,
27175    base_text: &str,
27176    cx: &mut EditorLspTestContext,
27177) {
27178    cx.set_state(not_reverted_text_with_selections);
27179    cx.set_head_text(base_text);
27180    cx.executor().run_until_parked();
27181
27182    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27183        let snapshot = editor.snapshot(window, cx);
27184        let reverted_hunk_statuses = snapshot
27185            .buffer_snapshot()
27186            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27187            .map(|hunk| hunk.status().kind)
27188            .collect::<Vec<_>>();
27189
27190        editor.git_restore(&Default::default(), window, cx);
27191        reverted_hunk_statuses
27192    });
27193    cx.executor().run_until_parked();
27194    cx.assert_editor_state(expected_reverted_text_with_selections);
27195    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27196}
27197
27198#[gpui::test(iterations = 10)]
27199async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27200    init_test(cx, |_| {});
27201
27202    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27203    let counter = diagnostic_requests.clone();
27204
27205    let fs = FakeFs::new(cx.executor());
27206    fs.insert_tree(
27207        path!("/a"),
27208        json!({
27209            "first.rs": "fn main() { let a = 5; }",
27210            "second.rs": "// Test file",
27211        }),
27212    )
27213    .await;
27214
27215    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27216    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27217    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27218
27219    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27220    language_registry.add(rust_lang());
27221    let mut fake_servers = language_registry.register_fake_lsp(
27222        "Rust",
27223        FakeLspAdapter {
27224            capabilities: lsp::ServerCapabilities {
27225                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27226                    lsp::DiagnosticOptions {
27227                        identifier: None,
27228                        inter_file_dependencies: true,
27229                        workspace_diagnostics: true,
27230                        work_done_progress_options: Default::default(),
27231                    },
27232                )),
27233                ..Default::default()
27234            },
27235            ..Default::default()
27236        },
27237    );
27238
27239    let editor = workspace
27240        .update(cx, |workspace, window, cx| {
27241            workspace.open_abs_path(
27242                PathBuf::from(path!("/a/first.rs")),
27243                OpenOptions::default(),
27244                window,
27245                cx,
27246            )
27247        })
27248        .unwrap()
27249        .await
27250        .unwrap()
27251        .downcast::<Editor>()
27252        .unwrap();
27253    let fake_server = fake_servers.next().await.unwrap();
27254    let server_id = fake_server.server.server_id();
27255    let mut first_request = fake_server
27256        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27257            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27258            let result_id = Some(new_result_id.to_string());
27259            assert_eq!(
27260                params.text_document.uri,
27261                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27262            );
27263            async move {
27264                Ok(lsp::DocumentDiagnosticReportResult::Report(
27265                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27266                        related_documents: None,
27267                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27268                            items: Vec::new(),
27269                            result_id,
27270                        },
27271                    }),
27272                ))
27273            }
27274        });
27275
27276    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27277        project.update(cx, |project, cx| {
27278            let buffer_id = editor
27279                .read(cx)
27280                .buffer()
27281                .read(cx)
27282                .as_singleton()
27283                .expect("created a singleton buffer")
27284                .read(cx)
27285                .remote_id();
27286            let buffer_result_id = project
27287                .lsp_store()
27288                .read(cx)
27289                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27290            assert_eq!(expected, buffer_result_id);
27291        });
27292    };
27293
27294    ensure_result_id(None, cx);
27295    cx.executor().advance_clock(Duration::from_millis(60));
27296    cx.executor().run_until_parked();
27297    assert_eq!(
27298        diagnostic_requests.load(atomic::Ordering::Acquire),
27299        1,
27300        "Opening file should trigger diagnostic request"
27301    );
27302    first_request
27303        .next()
27304        .await
27305        .expect("should have sent the first diagnostics pull request");
27306    ensure_result_id(Some(SharedString::new("1")), cx);
27307
27308    // Editing should trigger diagnostics
27309    editor.update_in(cx, |editor, window, cx| {
27310        editor.handle_input("2", window, cx)
27311    });
27312    cx.executor().advance_clock(Duration::from_millis(60));
27313    cx.executor().run_until_parked();
27314    assert_eq!(
27315        diagnostic_requests.load(atomic::Ordering::Acquire),
27316        2,
27317        "Editing should trigger diagnostic request"
27318    );
27319    ensure_result_id(Some(SharedString::new("2")), cx);
27320
27321    // Moving cursor should not trigger diagnostic request
27322    editor.update_in(cx, |editor, window, cx| {
27323        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27324            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27325        });
27326    });
27327    cx.executor().advance_clock(Duration::from_millis(60));
27328    cx.executor().run_until_parked();
27329    assert_eq!(
27330        diagnostic_requests.load(atomic::Ordering::Acquire),
27331        2,
27332        "Cursor movement should not trigger diagnostic request"
27333    );
27334    ensure_result_id(Some(SharedString::new("2")), cx);
27335    // Multiple rapid edits should be debounced
27336    for _ in 0..5 {
27337        editor.update_in(cx, |editor, window, cx| {
27338            editor.handle_input("x", window, cx)
27339        });
27340    }
27341    cx.executor().advance_clock(Duration::from_millis(60));
27342    cx.executor().run_until_parked();
27343
27344    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27345    assert!(
27346        final_requests <= 4,
27347        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27348    );
27349    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27350}
27351
27352#[gpui::test]
27353async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27354    // Regression test for issue #11671
27355    // Previously, adding a cursor after moving multiple cursors would reset
27356    // the cursor count instead of adding to the existing cursors.
27357    init_test(cx, |_| {});
27358    let mut cx = EditorTestContext::new(cx).await;
27359
27360    // Create a simple buffer with cursor at start
27361    cx.set_state(indoc! {"
27362        ˇaaaa
27363        bbbb
27364        cccc
27365        dddd
27366        eeee
27367        ffff
27368        gggg
27369        hhhh"});
27370
27371    // Add 2 cursors below (so we have 3 total)
27372    cx.update_editor(|editor, window, cx| {
27373        editor.add_selection_below(&Default::default(), window, cx);
27374        editor.add_selection_below(&Default::default(), window, cx);
27375    });
27376
27377    // Verify we have 3 cursors
27378    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27379    assert_eq!(
27380        initial_count, 3,
27381        "Should have 3 cursors after adding 2 below"
27382    );
27383
27384    // Move down one line
27385    cx.update_editor(|editor, window, cx| {
27386        editor.move_down(&MoveDown, window, cx);
27387    });
27388
27389    // Add another cursor below
27390    cx.update_editor(|editor, window, cx| {
27391        editor.add_selection_below(&Default::default(), window, cx);
27392    });
27393
27394    // Should now have 4 cursors (3 original + 1 new)
27395    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27396    assert_eq!(
27397        final_count, 4,
27398        "Should have 4 cursors after moving and adding another"
27399    );
27400}
27401
27402#[gpui::test]
27403async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27404    init_test(cx, |_| {});
27405
27406    let mut cx = EditorTestContext::new(cx).await;
27407
27408    cx.set_state(indoc!(
27409        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27410           Second line here"#
27411    ));
27412
27413    cx.update_editor(|editor, window, cx| {
27414        // Enable soft wrapping with a narrow width to force soft wrapping and
27415        // confirm that more than 2 rows are being displayed.
27416        editor.set_wrap_width(Some(100.0.into()), cx);
27417        assert!(editor.display_text(cx).lines().count() > 2);
27418
27419        editor.add_selection_below(
27420            &AddSelectionBelow {
27421                skip_soft_wrap: true,
27422            },
27423            window,
27424            cx,
27425        );
27426
27427        assert_eq!(
27428            display_ranges(editor, cx),
27429            &[
27430                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27431                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27432            ]
27433        );
27434
27435        editor.add_selection_above(
27436            &AddSelectionAbove {
27437                skip_soft_wrap: true,
27438            },
27439            window,
27440            cx,
27441        );
27442
27443        assert_eq!(
27444            display_ranges(editor, cx),
27445            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27446        );
27447
27448        editor.add_selection_below(
27449            &AddSelectionBelow {
27450                skip_soft_wrap: false,
27451            },
27452            window,
27453            cx,
27454        );
27455
27456        assert_eq!(
27457            display_ranges(editor, cx),
27458            &[
27459                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27460                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27461            ]
27462        );
27463
27464        editor.add_selection_above(
27465            &AddSelectionAbove {
27466                skip_soft_wrap: false,
27467            },
27468            window,
27469            cx,
27470        );
27471
27472        assert_eq!(
27473            display_ranges(editor, cx),
27474            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27475        );
27476    });
27477
27478    // Set up text where selections are in the middle of a soft-wrapped line.
27479    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27480    // selection should be at the same buffer column, not the same pixel
27481    // position.
27482    cx.set_state(indoc!(
27483        r#"1. Very long line to show «howˇ» a wrapped line would look
27484           2. Very long line to show how a wrapped line would look"#
27485    ));
27486
27487    cx.update_editor(|editor, window, cx| {
27488        // Enable soft wrapping with a narrow width to force soft wrapping and
27489        // confirm that more than 2 rows are being displayed.
27490        editor.set_wrap_width(Some(100.0.into()), cx);
27491        assert!(editor.display_text(cx).lines().count() > 2);
27492
27493        editor.add_selection_below(
27494            &AddSelectionBelow {
27495                skip_soft_wrap: true,
27496            },
27497            window,
27498            cx,
27499        );
27500
27501        // Assert that there's now 2 selections, both selecting the same column
27502        // range in the buffer row.
27503        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27504        let selections = editor.selections.all::<Point>(&display_map);
27505        assert_eq!(selections.len(), 2);
27506        assert_eq!(selections[0].start.column, selections[1].start.column);
27507        assert_eq!(selections[0].end.column, selections[1].end.column);
27508    });
27509}
27510
27511#[gpui::test]
27512async fn test_insert_snippet(cx: &mut TestAppContext) {
27513    init_test(cx, |_| {});
27514    let mut cx = EditorTestContext::new(cx).await;
27515
27516    cx.update_editor(|editor, _, cx| {
27517        editor.project().unwrap().update(cx, |project, cx| {
27518            project.snippets().update(cx, |snippets, _cx| {
27519                let snippet = project::snippet_provider::Snippet {
27520                    prefix: vec![], // no prefix needed!
27521                    body: "an Unspecified".to_string(),
27522                    description: Some("shhhh it's a secret".to_string()),
27523                    name: "super secret snippet".to_string(),
27524                };
27525                snippets.add_snippet_for_test(
27526                    None,
27527                    PathBuf::from("test_snippets.json"),
27528                    vec![Arc::new(snippet)],
27529                );
27530
27531                let snippet = project::snippet_provider::Snippet {
27532                    prefix: vec![], // no prefix needed!
27533                    body: " Location".to_string(),
27534                    description: Some("the word 'location'".to_string()),
27535                    name: "location word".to_string(),
27536                };
27537                snippets.add_snippet_for_test(
27538                    Some("Markdown".to_string()),
27539                    PathBuf::from("test_snippets.json"),
27540                    vec![Arc::new(snippet)],
27541                );
27542            });
27543        })
27544    });
27545
27546    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27547
27548    cx.update_editor(|editor, window, cx| {
27549        editor.insert_snippet_at_selections(
27550            &InsertSnippet {
27551                language: None,
27552                name: Some("super secret snippet".to_string()),
27553                snippet: None,
27554            },
27555            window,
27556            cx,
27557        );
27558
27559        // Language is specified in the action,
27560        // so the buffer language does not need to match
27561        editor.insert_snippet_at_selections(
27562            &InsertSnippet {
27563                language: Some("Markdown".to_string()),
27564                name: Some("location word".to_string()),
27565                snippet: None,
27566            },
27567            window,
27568            cx,
27569        );
27570
27571        editor.insert_snippet_at_selections(
27572            &InsertSnippet {
27573                language: None,
27574                name: None,
27575                snippet: Some("$0 after".to_string()),
27576            },
27577            window,
27578            cx,
27579        );
27580    });
27581
27582    cx.assert_editor_state(
27583        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27584    );
27585}
27586
27587#[gpui::test(iterations = 10)]
27588async fn test_document_colors(cx: &mut TestAppContext) {
27589    let expected_color = Rgba {
27590        r: 0.33,
27591        g: 0.33,
27592        b: 0.33,
27593        a: 0.33,
27594    };
27595
27596    init_test(cx, |_| {});
27597
27598    let fs = FakeFs::new(cx.executor());
27599    fs.insert_tree(
27600        path!("/a"),
27601        json!({
27602            "first.rs": "fn main() { let a = 5; }",
27603        }),
27604    )
27605    .await;
27606
27607    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27608    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27609    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27610
27611    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27612    language_registry.add(rust_lang());
27613    let mut fake_servers = language_registry.register_fake_lsp(
27614        "Rust",
27615        FakeLspAdapter {
27616            capabilities: lsp::ServerCapabilities {
27617                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27618                ..lsp::ServerCapabilities::default()
27619            },
27620            name: "rust-analyzer",
27621            ..FakeLspAdapter::default()
27622        },
27623    );
27624    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27625        "Rust",
27626        FakeLspAdapter {
27627            capabilities: lsp::ServerCapabilities {
27628                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27629                ..lsp::ServerCapabilities::default()
27630            },
27631            name: "not-rust-analyzer",
27632            ..FakeLspAdapter::default()
27633        },
27634    );
27635
27636    let editor = workspace
27637        .update(cx, |workspace, window, cx| {
27638            workspace.open_abs_path(
27639                PathBuf::from(path!("/a/first.rs")),
27640                OpenOptions::default(),
27641                window,
27642                cx,
27643            )
27644        })
27645        .unwrap()
27646        .await
27647        .unwrap()
27648        .downcast::<Editor>()
27649        .unwrap();
27650    let fake_language_server = fake_servers.next().await.unwrap();
27651    let fake_language_server_without_capabilities =
27652        fake_servers_without_capabilities.next().await.unwrap();
27653    let requests_made = Arc::new(AtomicUsize::new(0));
27654    let closure_requests_made = Arc::clone(&requests_made);
27655    let mut color_request_handle = fake_language_server
27656        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27657            let requests_made = Arc::clone(&closure_requests_made);
27658            async move {
27659                assert_eq!(
27660                    params.text_document.uri,
27661                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27662                );
27663                requests_made.fetch_add(1, atomic::Ordering::Release);
27664                Ok(vec![
27665                    lsp::ColorInformation {
27666                        range: lsp::Range {
27667                            start: lsp::Position {
27668                                line: 0,
27669                                character: 0,
27670                            },
27671                            end: lsp::Position {
27672                                line: 0,
27673                                character: 1,
27674                            },
27675                        },
27676                        color: lsp::Color {
27677                            red: 0.33,
27678                            green: 0.33,
27679                            blue: 0.33,
27680                            alpha: 0.33,
27681                        },
27682                    },
27683                    lsp::ColorInformation {
27684                        range: lsp::Range {
27685                            start: lsp::Position {
27686                                line: 0,
27687                                character: 0,
27688                            },
27689                            end: lsp::Position {
27690                                line: 0,
27691                                character: 1,
27692                            },
27693                        },
27694                        color: lsp::Color {
27695                            red: 0.33,
27696                            green: 0.33,
27697                            blue: 0.33,
27698                            alpha: 0.33,
27699                        },
27700                    },
27701                ])
27702            }
27703        });
27704
27705    let _handle = fake_language_server_without_capabilities
27706        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27707            panic!("Should not be called");
27708        });
27709    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27710    color_request_handle.next().await.unwrap();
27711    cx.run_until_parked();
27712    assert_eq!(
27713        1,
27714        requests_made.load(atomic::Ordering::Acquire),
27715        "Should query for colors once per editor open"
27716    );
27717    editor.update_in(cx, |editor, _, cx| {
27718        assert_eq!(
27719            vec![expected_color],
27720            extract_color_inlays(editor, cx),
27721            "Should have an initial inlay"
27722        );
27723    });
27724
27725    // opening another file in a split should not influence the LSP query counter
27726    workspace
27727        .update(cx, |workspace, window, cx| {
27728            assert_eq!(
27729                workspace.panes().len(),
27730                1,
27731                "Should have one pane with one editor"
27732            );
27733            workspace.move_item_to_pane_in_direction(
27734                &MoveItemToPaneInDirection {
27735                    direction: SplitDirection::Right,
27736                    focus: false,
27737                    clone: true,
27738                },
27739                window,
27740                cx,
27741            );
27742        })
27743        .unwrap();
27744    cx.run_until_parked();
27745    workspace
27746        .update(cx, |workspace, _, cx| {
27747            let panes = workspace.panes();
27748            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27749            for pane in panes {
27750                let editor = pane
27751                    .read(cx)
27752                    .active_item()
27753                    .and_then(|item| item.downcast::<Editor>())
27754                    .expect("Should have opened an editor in each split");
27755                let editor_file = editor
27756                    .read(cx)
27757                    .buffer()
27758                    .read(cx)
27759                    .as_singleton()
27760                    .expect("test deals with singleton buffers")
27761                    .read(cx)
27762                    .file()
27763                    .expect("test buffese should have a file")
27764                    .path();
27765                assert_eq!(
27766                    editor_file.as_ref(),
27767                    rel_path("first.rs"),
27768                    "Both editors should be opened for the same file"
27769                )
27770            }
27771        })
27772        .unwrap();
27773
27774    cx.executor().advance_clock(Duration::from_millis(500));
27775    let save = editor.update_in(cx, |editor, window, cx| {
27776        editor.move_to_end(&MoveToEnd, window, cx);
27777        editor.handle_input("dirty", window, cx);
27778        editor.save(
27779            SaveOptions {
27780                format: true,
27781                autosave: true,
27782            },
27783            project.clone(),
27784            window,
27785            cx,
27786        )
27787    });
27788    save.await.unwrap();
27789
27790    color_request_handle.next().await.unwrap();
27791    cx.run_until_parked();
27792    assert_eq!(
27793        2,
27794        requests_made.load(atomic::Ordering::Acquire),
27795        "Should query for colors once per save (deduplicated) and once per formatting after save"
27796    );
27797
27798    drop(editor);
27799    let close = workspace
27800        .update(cx, |workspace, window, cx| {
27801            workspace.active_pane().update(cx, |pane, cx| {
27802                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27803            })
27804        })
27805        .unwrap();
27806    close.await.unwrap();
27807    let close = workspace
27808        .update(cx, |workspace, window, cx| {
27809            workspace.active_pane().update(cx, |pane, cx| {
27810                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27811            })
27812        })
27813        .unwrap();
27814    close.await.unwrap();
27815    assert_eq!(
27816        2,
27817        requests_made.load(atomic::Ordering::Acquire),
27818        "After saving and closing all editors, no extra requests should be made"
27819    );
27820    workspace
27821        .update(cx, |workspace, _, cx| {
27822            assert!(
27823                workspace.active_item(cx).is_none(),
27824                "Should close all editors"
27825            )
27826        })
27827        .unwrap();
27828
27829    workspace
27830        .update(cx, |workspace, window, cx| {
27831            workspace.active_pane().update(cx, |pane, cx| {
27832                pane.navigate_backward(&workspace::GoBack, window, cx);
27833            })
27834        })
27835        .unwrap();
27836    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27837    cx.run_until_parked();
27838    let editor = workspace
27839        .update(cx, |workspace, _, cx| {
27840            workspace
27841                .active_item(cx)
27842                .expect("Should have reopened the editor again after navigating back")
27843                .downcast::<Editor>()
27844                .expect("Should be an editor")
27845        })
27846        .unwrap();
27847
27848    assert_eq!(
27849        2,
27850        requests_made.load(atomic::Ordering::Acquire),
27851        "Cache should be reused on buffer close and reopen"
27852    );
27853    editor.update(cx, |editor, cx| {
27854        assert_eq!(
27855            vec![expected_color],
27856            extract_color_inlays(editor, cx),
27857            "Should have an initial inlay"
27858        );
27859    });
27860
27861    drop(color_request_handle);
27862    let closure_requests_made = Arc::clone(&requests_made);
27863    let mut empty_color_request_handle = fake_language_server
27864        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27865            let requests_made = Arc::clone(&closure_requests_made);
27866            async move {
27867                assert_eq!(
27868                    params.text_document.uri,
27869                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27870                );
27871                requests_made.fetch_add(1, atomic::Ordering::Release);
27872                Ok(Vec::new())
27873            }
27874        });
27875    let save = editor.update_in(cx, |editor, window, cx| {
27876        editor.move_to_end(&MoveToEnd, window, cx);
27877        editor.handle_input("dirty_again", window, cx);
27878        editor.save(
27879            SaveOptions {
27880                format: false,
27881                autosave: true,
27882            },
27883            project.clone(),
27884            window,
27885            cx,
27886        )
27887    });
27888    save.await.unwrap();
27889
27890    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27891    empty_color_request_handle.next().await.unwrap();
27892    cx.run_until_parked();
27893    assert_eq!(
27894        3,
27895        requests_made.load(atomic::Ordering::Acquire),
27896        "Should query for colors once per save only, as formatting was not requested"
27897    );
27898    editor.update(cx, |editor, cx| {
27899        assert_eq!(
27900            Vec::<Rgba>::new(),
27901            extract_color_inlays(editor, cx),
27902            "Should clear all colors when the server returns an empty response"
27903        );
27904    });
27905}
27906
27907#[gpui::test]
27908async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27909    init_test(cx, |_| {});
27910    let (editor, cx) = cx.add_window_view(Editor::single_line);
27911    editor.update_in(cx, |editor, window, cx| {
27912        editor.set_text("oops\n\nwow\n", window, cx)
27913    });
27914    cx.run_until_parked();
27915    editor.update(cx, |editor, cx| {
27916        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27917    });
27918    editor.update(cx, |editor, cx| {
27919        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27920    });
27921    cx.run_until_parked();
27922    editor.update(cx, |editor, cx| {
27923        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27924    });
27925}
27926
27927#[gpui::test]
27928async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27929    init_test(cx, |_| {});
27930
27931    cx.update(|cx| {
27932        register_project_item::<Editor>(cx);
27933    });
27934
27935    let fs = FakeFs::new(cx.executor());
27936    fs.insert_tree("/root1", json!({})).await;
27937    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27938        .await;
27939
27940    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27941    let (workspace, cx) =
27942        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27943
27944    let worktree_id = project.update(cx, |project, cx| {
27945        project.worktrees(cx).next().unwrap().read(cx).id()
27946    });
27947
27948    let handle = workspace
27949        .update_in(cx, |workspace, window, cx| {
27950            let project_path = (worktree_id, rel_path("one.pdf"));
27951            workspace.open_path(project_path, None, true, window, cx)
27952        })
27953        .await
27954        .unwrap();
27955    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
27956    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
27957    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
27958    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
27959}
27960
27961#[gpui::test]
27962async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27963    init_test(cx, |_| {});
27964
27965    let language = Arc::new(Language::new(
27966        LanguageConfig::default(),
27967        Some(tree_sitter_rust::LANGUAGE.into()),
27968    ));
27969
27970    // Test hierarchical sibling navigation
27971    let text = r#"
27972        fn outer() {
27973            if condition {
27974                let a = 1;
27975            }
27976            let b = 2;
27977        }
27978
27979        fn another() {
27980            let c = 3;
27981        }
27982    "#;
27983
27984    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27985    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27986    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27987
27988    // Wait for parsing to complete
27989    editor
27990        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27991        .await;
27992
27993    editor.update_in(cx, |editor, window, cx| {
27994        // Start by selecting "let a = 1;" inside the if block
27995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27996            s.select_display_ranges([
27997                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27998            ]);
27999        });
28000
28001        let initial_selection = editor
28002            .selections
28003            .display_ranges(&editor.display_snapshot(cx));
28004        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28005
28006        // Test select next sibling - should move up levels to find the next sibling
28007        // Since "let a = 1;" has no siblings in the if block, it should move up
28008        // to find "let b = 2;" which is a sibling of the if block
28009        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28010        let next_selection = editor
28011            .selections
28012            .display_ranges(&editor.display_snapshot(cx));
28013
28014        // Should have a selection and it should be different from the initial
28015        assert_eq!(
28016            next_selection.len(),
28017            1,
28018            "Should have one selection after next"
28019        );
28020        assert_ne!(
28021            next_selection[0], initial_selection[0],
28022            "Next sibling selection should be different"
28023        );
28024
28025        // Test hierarchical navigation by going to the end of the current function
28026        // and trying to navigate to the next function
28027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28028            s.select_display_ranges([
28029                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28030            ]);
28031        });
28032
28033        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28034        let function_next_selection = editor
28035            .selections
28036            .display_ranges(&editor.display_snapshot(cx));
28037
28038        // Should move to the next function
28039        assert_eq!(
28040            function_next_selection.len(),
28041            1,
28042            "Should have one selection after function next"
28043        );
28044
28045        // Test select previous sibling navigation
28046        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28047        let prev_selection = editor
28048            .selections
28049            .display_ranges(&editor.display_snapshot(cx));
28050
28051        // Should have a selection and it should be different
28052        assert_eq!(
28053            prev_selection.len(),
28054            1,
28055            "Should have one selection after prev"
28056        );
28057        assert_ne!(
28058            prev_selection[0], function_next_selection[0],
28059            "Previous sibling selection should be different from next"
28060        );
28061    });
28062}
28063
28064#[gpui::test]
28065async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28066    init_test(cx, |_| {});
28067
28068    let mut cx = EditorTestContext::new(cx).await;
28069    cx.set_state(
28070        "let ˇvariable = 42;
28071let another = variable + 1;
28072let result = variable * 2;",
28073    );
28074
28075    // Set up document highlights manually (simulating LSP response)
28076    cx.update_editor(|editor, _window, cx| {
28077        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28078
28079        // Create highlights for "variable" occurrences
28080        let highlight_ranges = [
28081            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28082            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28083            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28084        ];
28085
28086        let anchor_ranges: Vec<_> = highlight_ranges
28087            .iter()
28088            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28089            .collect();
28090
28091        editor.highlight_background::<DocumentHighlightRead>(
28092            &anchor_ranges,
28093            |_, theme| theme.colors().editor_document_highlight_read_background,
28094            cx,
28095        );
28096    });
28097
28098    // Go to next highlight - should move to second "variable"
28099    cx.update_editor(|editor, window, cx| {
28100        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28101    });
28102    cx.assert_editor_state(
28103        "let variable = 42;
28104let another = ˇvariable + 1;
28105let result = variable * 2;",
28106    );
28107
28108    // Go to next highlight - should move to third "variable"
28109    cx.update_editor(|editor, window, cx| {
28110        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28111    });
28112    cx.assert_editor_state(
28113        "let variable = 42;
28114let another = variable + 1;
28115let result = ˇvariable * 2;",
28116    );
28117
28118    // Go to next highlight - should stay at third "variable" (no wrap-around)
28119    cx.update_editor(|editor, window, cx| {
28120        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28121    });
28122    cx.assert_editor_state(
28123        "let variable = 42;
28124let another = variable + 1;
28125let result = ˇvariable * 2;",
28126    );
28127
28128    // Now test going backwards from third position
28129    cx.update_editor(|editor, window, cx| {
28130        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28131    });
28132    cx.assert_editor_state(
28133        "let variable = 42;
28134let another = ˇvariable + 1;
28135let result = variable * 2;",
28136    );
28137
28138    // Go to previous highlight - should move to first "variable"
28139    cx.update_editor(|editor, window, cx| {
28140        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28141    });
28142    cx.assert_editor_state(
28143        "let ˇvariable = 42;
28144let another = variable + 1;
28145let result = variable * 2;",
28146    );
28147
28148    // Go to previous highlight - should stay on first "variable"
28149    cx.update_editor(|editor, window, cx| {
28150        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28151    });
28152    cx.assert_editor_state(
28153        "let ˇvariable = 42;
28154let another = variable + 1;
28155let result = variable * 2;",
28156    );
28157}
28158
28159#[gpui::test]
28160async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28161    cx: &mut gpui::TestAppContext,
28162) {
28163    init_test(cx, |_| {});
28164
28165    let url = "https://zed.dev";
28166
28167    let markdown_language = Arc::new(Language::new(
28168        LanguageConfig {
28169            name: "Markdown".into(),
28170            ..LanguageConfig::default()
28171        },
28172        None,
28173    ));
28174
28175    let mut cx = EditorTestContext::new(cx).await;
28176    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28177    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28178
28179    cx.update_editor(|editor, window, cx| {
28180        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28181        editor.paste(&Paste, window, cx);
28182    });
28183
28184    cx.assert_editor_state(&format!(
28185        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28186    ));
28187}
28188
28189#[gpui::test]
28190async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28191    init_test(cx, |_| {});
28192
28193    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28194    let mut cx = EditorTestContext::new(cx).await;
28195
28196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28197
28198    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28199    cx.set_state(&indoc! {"
28200        - [ ] Item 1
28201            - [ ] Item 1.a
28202        - [ˇ] Item 2
28203            - [ˇ] Item 2.a
28204            - [ˇ] Item 2.b
28205        "
28206    });
28207    cx.update_editor(|editor, window, cx| {
28208        editor.handle_input("x", window, cx);
28209    });
28210    cx.run_until_parked();
28211    cx.assert_editor_state(indoc! {"
28212        - [ ] Item 1
28213            - [ ] Item 1.a
28214        - [xˇ] Item 2
28215            - [xˇ] Item 2.a
28216            - [xˇ] Item 2.b
28217        "
28218    });
28219
28220    // Case 2: Test adding new line after nested list continues the list with unchecked task
28221    cx.set_state(&indoc! {"
28222        - [ ] Item 1
28223            - [ ] Item 1.a
28224        - [x] Item 2
28225            - [x] Item 2.a
28226            - [x] Item 2.bˇ"
28227    });
28228    cx.update_editor(|editor, window, cx| {
28229        editor.newline(&Newline, window, cx);
28230    });
28231    cx.assert_editor_state(indoc! {"
28232        - [ ] Item 1
28233            - [ ] Item 1.a
28234        - [x] Item 2
28235            - [x] Item 2.a
28236            - [x] Item 2.b
28237            - [ ] ˇ"
28238    });
28239
28240    // Case 3: Test adding content to continued list item
28241    cx.update_editor(|editor, window, cx| {
28242        editor.handle_input("Item 2.c", window, cx);
28243    });
28244    cx.run_until_parked();
28245    cx.assert_editor_state(indoc! {"
28246        - [ ] Item 1
28247            - [ ] Item 1.a
28248        - [x] Item 2
28249            - [x] Item 2.a
28250            - [x] Item 2.b
28251            - [ ] Item 2.cˇ"
28252    });
28253
28254    // Case 4: Test adding new line after nested ordered list continues with next number
28255    cx.set_state(indoc! {"
28256        1. Item 1
28257            1. Item 1.a
28258        2. Item 2
28259            1. Item 2.a
28260            2. Item 2.bˇ"
28261    });
28262    cx.update_editor(|editor, window, cx| {
28263        editor.newline(&Newline, window, cx);
28264    });
28265    cx.assert_editor_state(indoc! {"
28266        1. Item 1
28267            1. Item 1.a
28268        2. Item 2
28269            1. Item 2.a
28270            2. Item 2.b
28271            3. ˇ"
28272    });
28273
28274    // Case 5: Adding content to continued ordered list item
28275    cx.update_editor(|editor, window, cx| {
28276        editor.handle_input("Item 2.c", window, cx);
28277    });
28278    cx.run_until_parked();
28279    cx.assert_editor_state(indoc! {"
28280        1. Item 1
28281            1. Item 1.a
28282        2. Item 2
28283            1. Item 2.a
28284            2. Item 2.b
28285            3. Item 2.cˇ"
28286    });
28287
28288    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28289    cx.set_state(indoc! {"
28290        - Item 1
28291            - Item 1.a
28292            - Item 1.a
28293        ˇ"});
28294    cx.update_editor(|editor, window, cx| {
28295        editor.handle_input("-", window, cx);
28296    });
28297    cx.run_until_parked();
28298    cx.assert_editor_state(indoc! {"
28299        - Item 1
28300            - Item 1.a
28301            - Item 1.a
28302"});
28303
28304    // Case 7: Test blockquote newline preserves something
28305    cx.set_state(indoc! {"
28306        > Item 1ˇ"
28307    });
28308    cx.update_editor(|editor, window, cx| {
28309        editor.newline(&Newline, window, cx);
28310    });
28311    cx.assert_editor_state(indoc! {"
28312        > Item 1
28313        ˇ"
28314    });
28315}
28316
28317#[gpui::test]
28318async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28319    cx: &mut gpui::TestAppContext,
28320) {
28321    init_test(cx, |_| {});
28322
28323    let url = "https://zed.dev";
28324
28325    let markdown_language = Arc::new(Language::new(
28326        LanguageConfig {
28327            name: "Markdown".into(),
28328            ..LanguageConfig::default()
28329        },
28330        None,
28331    ));
28332
28333    let mut cx = EditorTestContext::new(cx).await;
28334    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28335    cx.set_state(&format!(
28336        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28337    ));
28338
28339    cx.update_editor(|editor, window, cx| {
28340        editor.copy(&Copy, window, cx);
28341    });
28342
28343    cx.set_state(&format!(
28344        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28345    ));
28346
28347    cx.update_editor(|editor, window, cx| {
28348        editor.paste(&Paste, window, cx);
28349    });
28350
28351    cx.assert_editor_state(&format!(
28352        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28353    ));
28354}
28355
28356#[gpui::test]
28357async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28358    cx: &mut gpui::TestAppContext,
28359) {
28360    init_test(cx, |_| {});
28361
28362    let url = "https://zed.dev";
28363
28364    let markdown_language = Arc::new(Language::new(
28365        LanguageConfig {
28366            name: "Markdown".into(),
28367            ..LanguageConfig::default()
28368        },
28369        None,
28370    ));
28371
28372    let mut cx = EditorTestContext::new(cx).await;
28373    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28374    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28375
28376    cx.update_editor(|editor, window, cx| {
28377        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28378        editor.paste(&Paste, window, cx);
28379    });
28380
28381    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28382}
28383
28384#[gpui::test]
28385async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28386    cx: &mut gpui::TestAppContext,
28387) {
28388    init_test(cx, |_| {});
28389
28390    let text = "Awesome";
28391
28392    let markdown_language = Arc::new(Language::new(
28393        LanguageConfig {
28394            name: "Markdown".into(),
28395            ..LanguageConfig::default()
28396        },
28397        None,
28398    ));
28399
28400    let mut cx = EditorTestContext::new(cx).await;
28401    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28402    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28403
28404    cx.update_editor(|editor, window, cx| {
28405        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28406        editor.paste(&Paste, window, cx);
28407    });
28408
28409    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28410}
28411
28412#[gpui::test]
28413async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28414    cx: &mut gpui::TestAppContext,
28415) {
28416    init_test(cx, |_| {});
28417
28418    let url = "https://zed.dev";
28419
28420    let markdown_language = Arc::new(Language::new(
28421        LanguageConfig {
28422            name: "Rust".into(),
28423            ..LanguageConfig::default()
28424        },
28425        None,
28426    ));
28427
28428    let mut cx = EditorTestContext::new(cx).await;
28429    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28430    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28431
28432    cx.update_editor(|editor, window, cx| {
28433        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28434        editor.paste(&Paste, window, cx);
28435    });
28436
28437    cx.assert_editor_state(&format!(
28438        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28439    ));
28440}
28441
28442#[gpui::test]
28443async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28444    cx: &mut TestAppContext,
28445) {
28446    init_test(cx, |_| {});
28447
28448    let url = "https://zed.dev";
28449
28450    let markdown_language = Arc::new(Language::new(
28451        LanguageConfig {
28452            name: "Markdown".into(),
28453            ..LanguageConfig::default()
28454        },
28455        None,
28456    ));
28457
28458    let (editor, cx) = cx.add_window_view(|window, cx| {
28459        let multi_buffer = MultiBuffer::build_multi(
28460            [
28461                ("this will embed -> link", vec![Point::row_range(0..1)]),
28462                ("this will replace -> link", vec![Point::row_range(0..1)]),
28463            ],
28464            cx,
28465        );
28466        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28468            s.select_ranges(vec![
28469                Point::new(0, 19)..Point::new(0, 23),
28470                Point::new(1, 21)..Point::new(1, 25),
28471            ])
28472        });
28473        let first_buffer_id = multi_buffer
28474            .read(cx)
28475            .excerpt_buffer_ids()
28476            .into_iter()
28477            .next()
28478            .unwrap();
28479        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28480        first_buffer.update(cx, |buffer, cx| {
28481            buffer.set_language(Some(markdown_language.clone()), cx);
28482        });
28483
28484        editor
28485    });
28486    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28487
28488    cx.update_editor(|editor, window, cx| {
28489        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28490        editor.paste(&Paste, window, cx);
28491    });
28492
28493    cx.assert_editor_state(&format!(
28494        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28495    ));
28496}
28497
28498#[gpui::test]
28499async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28500    init_test(cx, |_| {});
28501
28502    let fs = FakeFs::new(cx.executor());
28503    fs.insert_tree(
28504        path!("/project"),
28505        json!({
28506            "first.rs": "# First Document\nSome content here.",
28507            "second.rs": "Plain text content for second file.",
28508        }),
28509    )
28510    .await;
28511
28512    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28513    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28514    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28515
28516    let language = rust_lang();
28517    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28518    language_registry.add(language.clone());
28519    let mut fake_servers = language_registry.register_fake_lsp(
28520        "Rust",
28521        FakeLspAdapter {
28522            ..FakeLspAdapter::default()
28523        },
28524    );
28525
28526    let buffer1 = project
28527        .update(cx, |project, cx| {
28528            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28529        })
28530        .await
28531        .unwrap();
28532    let buffer2 = project
28533        .update(cx, |project, cx| {
28534            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28535        })
28536        .await
28537        .unwrap();
28538
28539    let multi_buffer = cx.new(|cx| {
28540        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28541        multi_buffer.set_excerpts_for_path(
28542            PathKey::for_buffer(&buffer1, cx),
28543            buffer1.clone(),
28544            [Point::zero()..buffer1.read(cx).max_point()],
28545            3,
28546            cx,
28547        );
28548        multi_buffer.set_excerpts_for_path(
28549            PathKey::for_buffer(&buffer2, cx),
28550            buffer2.clone(),
28551            [Point::zero()..buffer1.read(cx).max_point()],
28552            3,
28553            cx,
28554        );
28555        multi_buffer
28556    });
28557
28558    let (editor, cx) = cx.add_window_view(|window, cx| {
28559        Editor::new(
28560            EditorMode::full(),
28561            multi_buffer,
28562            Some(project.clone()),
28563            window,
28564            cx,
28565        )
28566    });
28567
28568    let fake_language_server = fake_servers.next().await.unwrap();
28569
28570    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28571
28572    let save = editor.update_in(cx, |editor, window, cx| {
28573        assert!(editor.is_dirty(cx));
28574
28575        editor.save(
28576            SaveOptions {
28577                format: true,
28578                autosave: true,
28579            },
28580            project,
28581            window,
28582            cx,
28583        )
28584    });
28585    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28586    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28587    let mut done_edit_rx = Some(done_edit_rx);
28588    let mut start_edit_tx = Some(start_edit_tx);
28589
28590    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28591        start_edit_tx.take().unwrap().send(()).unwrap();
28592        let done_edit_rx = done_edit_rx.take().unwrap();
28593        async move {
28594            done_edit_rx.await.unwrap();
28595            Ok(None)
28596        }
28597    });
28598
28599    start_edit_rx.await.unwrap();
28600    buffer2
28601        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28602        .unwrap();
28603
28604    done_edit_tx.send(()).unwrap();
28605
28606    save.await.unwrap();
28607    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28608}
28609
28610#[track_caller]
28611fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28612    editor
28613        .all_inlays(cx)
28614        .into_iter()
28615        .filter_map(|inlay| inlay.get_color())
28616        .map(Rgba::from)
28617        .collect()
28618}
28619
28620#[gpui::test]
28621fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28622    init_test(cx, |_| {});
28623
28624    let editor = cx.add_window(|window, cx| {
28625        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28626        build_editor(buffer, window, cx)
28627    });
28628
28629    editor
28630        .update(cx, |editor, window, cx| {
28631            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28632                s.select_display_ranges([
28633                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28634                ])
28635            });
28636
28637            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28638
28639            assert_eq!(
28640                editor.display_text(cx),
28641                "line1\nline2\nline2",
28642                "Duplicating last line upward should create duplicate above, not on same line"
28643            );
28644
28645            assert_eq!(
28646                editor
28647                    .selections
28648                    .display_ranges(&editor.display_snapshot(cx)),
28649                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28650                "Selection should move to the duplicated line"
28651            );
28652        })
28653        .unwrap();
28654}
28655
28656#[gpui::test]
28657async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28658    init_test(cx, |_| {});
28659
28660    let mut cx = EditorTestContext::new(cx).await;
28661
28662    cx.set_state("line1\nline2ˇ");
28663
28664    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28665
28666    let clipboard_text = cx
28667        .read_from_clipboard()
28668        .and_then(|item| item.text().as_deref().map(str::to_string));
28669
28670    assert_eq!(
28671        clipboard_text,
28672        Some("line2\n".to_string()),
28673        "Copying a line without trailing newline should include a newline"
28674    );
28675
28676    cx.set_state("line1\nˇ");
28677
28678    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28679
28680    cx.assert_editor_state("line1\nline2\nˇ");
28681}
28682
28683#[gpui::test]
28684async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28685    init_test(cx, |_| {});
28686
28687    let mut cx = EditorTestContext::new(cx).await;
28688
28689    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28690
28691    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28692
28693    let clipboard_text = cx
28694        .read_from_clipboard()
28695        .and_then(|item| item.text().as_deref().map(str::to_string));
28696
28697    assert_eq!(
28698        clipboard_text,
28699        Some("line1\nline2\nline3\n".to_string()),
28700        "Copying multiple lines should include a single newline between lines"
28701    );
28702
28703    cx.set_state("lineA\nˇ");
28704
28705    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28706
28707    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28708}
28709
28710#[gpui::test]
28711async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28712    init_test(cx, |_| {});
28713
28714    let mut cx = EditorTestContext::new(cx).await;
28715
28716    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28717
28718    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28719
28720    let clipboard_text = cx
28721        .read_from_clipboard()
28722        .and_then(|item| item.text().as_deref().map(str::to_string));
28723
28724    assert_eq!(
28725        clipboard_text,
28726        Some("line1\nline2\nline3\n".to_string()),
28727        "Copying multiple lines should include a single newline between lines"
28728    );
28729
28730    cx.set_state("lineA\nˇ");
28731
28732    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28733
28734    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28735}
28736
28737#[gpui::test]
28738async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28739    init_test(cx, |_| {});
28740
28741    let mut cx = EditorTestContext::new(cx).await;
28742
28743    cx.set_state("line1\nline2ˇ");
28744    cx.update_editor(|e, window, cx| {
28745        e.set_mode(EditorMode::SingleLine);
28746        assert!(e.key_context(window, cx).contains("end_of_input"));
28747    });
28748    cx.set_state("ˇline1\nline2");
28749    cx.update_editor(|e, window, cx| {
28750        assert!(!e.key_context(window, cx).contains("end_of_input"));
28751    });
28752    cx.set_state("line1ˇ\nline2");
28753    cx.update_editor(|e, window, cx| {
28754        assert!(!e.key_context(window, cx).contains("end_of_input"));
28755    });
28756}
28757
28758#[gpui::test]
28759async fn test_sticky_scroll(cx: &mut TestAppContext) {
28760    init_test(cx, |_| {});
28761    let mut cx = EditorTestContext::new(cx).await;
28762
28763    let buffer = indoc! {"
28764            ˇfn foo() {
28765                let abc = 123;
28766            }
28767            struct Bar;
28768            impl Bar {
28769                fn new() -> Self {
28770                    Self
28771                }
28772            }
28773            fn baz() {
28774            }
28775        "};
28776    cx.set_state(&buffer);
28777
28778    cx.update_editor(|e, _, cx| {
28779        e.buffer()
28780            .read(cx)
28781            .as_singleton()
28782            .unwrap()
28783            .update(cx, |buffer, cx| {
28784                buffer.set_language(Some(rust_lang()), cx);
28785            })
28786    });
28787
28788    let mut sticky_headers = |offset: ScrollOffset| {
28789        cx.update_editor(|e, window, cx| {
28790            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28791            let style = e.style(cx).clone();
28792            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28793                .into_iter()
28794                .map(
28795                    |StickyHeader {
28796                         start_point,
28797                         offset,
28798                         ..
28799                     }| { (start_point, offset) },
28800                )
28801                .collect::<Vec<_>>()
28802        })
28803    };
28804
28805    let fn_foo = Point { row: 0, column: 0 };
28806    let impl_bar = Point { row: 4, column: 0 };
28807    let fn_new = Point { row: 5, column: 4 };
28808
28809    assert_eq!(sticky_headers(0.0), vec![]);
28810    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28811    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28812    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28813    assert_eq!(sticky_headers(2.0), vec![]);
28814    assert_eq!(sticky_headers(2.5), vec![]);
28815    assert_eq!(sticky_headers(3.0), vec![]);
28816    assert_eq!(sticky_headers(3.5), vec![]);
28817    assert_eq!(sticky_headers(4.0), vec![]);
28818    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28819    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28820    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28821    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28822    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28823    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28824    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28825    assert_eq!(sticky_headers(8.0), vec![]);
28826    assert_eq!(sticky_headers(8.5), vec![]);
28827    assert_eq!(sticky_headers(9.0), vec![]);
28828    assert_eq!(sticky_headers(9.5), vec![]);
28829    assert_eq!(sticky_headers(10.0), vec![]);
28830}
28831
28832#[gpui::test]
28833fn test_relative_line_numbers(cx: &mut TestAppContext) {
28834    init_test(cx, |_| {});
28835
28836    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
28837    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
28838    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
28839
28840    let multibuffer = cx.new(|cx| {
28841        let mut multibuffer = MultiBuffer::new(ReadWrite);
28842        multibuffer.push_excerpts(
28843            buffer_1.clone(),
28844            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28845            cx,
28846        );
28847        multibuffer.push_excerpts(
28848            buffer_2.clone(),
28849            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28850            cx,
28851        );
28852        multibuffer.push_excerpts(
28853            buffer_3.clone(),
28854            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28855            cx,
28856        );
28857        multibuffer
28858    });
28859
28860    // wrapped contents of multibuffer:
28861    //    aaa
28862    //    aaa
28863    //    aaa
28864    //    a
28865    //    bbb
28866    //
28867    //    ccc
28868    //    ccc
28869    //    ccc
28870    //    c
28871    //    ddd
28872    //
28873    //    eee
28874    //    fff
28875    //    fff
28876    //    fff
28877    //    f
28878
28879    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
28880    _ = editor.update(cx, |editor, window, cx| {
28881        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
28882
28883        // includes trailing newlines.
28884        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
28885        let expected_wrapped_line_numbers = [
28886            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
28887        ];
28888
28889        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28890            s.select_ranges([
28891                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
28892            ]);
28893        });
28894
28895        let snapshot = editor.snapshot(window, cx);
28896
28897        // these are all 0-indexed
28898        let base_display_row = DisplayRow(11);
28899        let base_row = 3;
28900        let wrapped_base_row = 7;
28901
28902        // test not counting wrapped lines
28903        let expected_relative_numbers = expected_line_numbers
28904            .into_iter()
28905            .enumerate()
28906            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
28907            .collect_vec();
28908        let actual_relative_numbers = snapshot
28909            .calculate_relative_line_numbers(
28910                &(DisplayRow(0)..DisplayRow(24)),
28911                base_display_row,
28912                false,
28913            )
28914            .into_iter()
28915            .sorted()
28916            .collect_vec();
28917        assert_eq!(expected_relative_numbers, actual_relative_numbers);
28918        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
28919        for (display_row, relative_number) in expected_relative_numbers {
28920            assert_eq!(
28921                relative_number,
28922                snapshot
28923                    .relative_line_delta(display_row, base_display_row, false)
28924                    .unsigned_abs() as u32,
28925            );
28926        }
28927
28928        // test counting wrapped lines
28929        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
28930            .into_iter()
28931            .enumerate()
28932            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
28933            .filter(|(row, _)| *row != base_display_row)
28934            .collect_vec();
28935        let actual_relative_numbers = snapshot
28936            .calculate_relative_line_numbers(
28937                &(DisplayRow(0)..DisplayRow(24)),
28938                base_display_row,
28939                true,
28940            )
28941            .into_iter()
28942            .sorted()
28943            .collect_vec();
28944        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
28945        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
28946        for (display_row, relative_number) in expected_wrapped_relative_numbers {
28947            assert_eq!(
28948                relative_number,
28949                snapshot
28950                    .relative_line_delta(display_row, base_display_row, true)
28951                    .unsigned_abs() as u32,
28952            );
28953        }
28954    });
28955}
28956
28957#[gpui::test]
28958async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28959    init_test(cx, |_| {});
28960    cx.update(|cx| {
28961        SettingsStore::update_global(cx, |store, cx| {
28962            store.update_user_settings(cx, |settings| {
28963                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28964                    enabled: Some(true),
28965                })
28966            });
28967        });
28968    });
28969    let mut cx = EditorTestContext::new(cx).await;
28970
28971    let line_height = cx.update_editor(|editor, window, cx| {
28972        editor
28973            .style(cx)
28974            .text
28975            .line_height_in_pixels(window.rem_size())
28976    });
28977
28978    let buffer = indoc! {"
28979            ˇfn foo() {
28980                let abc = 123;
28981            }
28982            struct Bar;
28983            impl Bar {
28984                fn new() -> Self {
28985                    Self
28986                }
28987            }
28988            fn baz() {
28989            }
28990        "};
28991    cx.set_state(&buffer);
28992
28993    cx.update_editor(|e, _, cx| {
28994        e.buffer()
28995            .read(cx)
28996            .as_singleton()
28997            .unwrap()
28998            .update(cx, |buffer, cx| {
28999                buffer.set_language(Some(rust_lang()), cx);
29000            })
29001    });
29002
29003    let fn_foo = || empty_range(0, 0);
29004    let impl_bar = || empty_range(4, 0);
29005    let fn_new = || empty_range(5, 4);
29006
29007    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29008        cx.update_editor(|e, window, cx| {
29009            e.scroll(
29010                gpui::Point {
29011                    x: 0.,
29012                    y: scroll_offset,
29013                },
29014                None,
29015                window,
29016                cx,
29017            );
29018        });
29019        cx.simulate_click(
29020            gpui::Point {
29021                x: px(0.),
29022                y: click_offset as f32 * line_height,
29023            },
29024            Modifiers::none(),
29025        );
29026        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29027    };
29028
29029    assert_eq!(
29030        scroll_and_click(
29031            4.5, // impl Bar is halfway off the screen
29032            0.0  // click top of screen
29033        ),
29034        // scrolled to impl Bar
29035        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29036    );
29037
29038    assert_eq!(
29039        scroll_and_click(
29040            4.5,  // impl Bar is halfway off the screen
29041            0.25  // click middle of impl Bar
29042        ),
29043        // scrolled to impl Bar
29044        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29045    );
29046
29047    assert_eq!(
29048        scroll_and_click(
29049            4.5, // impl Bar is halfway off the screen
29050            1.5  // click below impl Bar (e.g. fn new())
29051        ),
29052        // scrolled to fn new() - this is below the impl Bar header which has persisted
29053        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29054    );
29055
29056    assert_eq!(
29057        scroll_and_click(
29058            5.5,  // fn new is halfway underneath impl Bar
29059            0.75  // click on the overlap of impl Bar and fn new()
29060        ),
29061        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29062    );
29063
29064    assert_eq!(
29065        scroll_and_click(
29066            5.5,  // fn new is halfway underneath impl Bar
29067            1.25  // click on the visible part of fn new()
29068        ),
29069        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29070    );
29071
29072    assert_eq!(
29073        scroll_and_click(
29074            1.5, // fn foo is halfway off the screen
29075            0.0  // click top of screen
29076        ),
29077        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29078    );
29079
29080    assert_eq!(
29081        scroll_and_click(
29082            1.5,  // fn foo is halfway off the screen
29083            0.75  // click visible part of let abc...
29084        )
29085        .0,
29086        // no change in scroll
29087        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29088        (gpui::Point { x: 0., y: 1.5 })
29089    );
29090}
29091
29092#[gpui::test]
29093async fn test_next_prev_reference(cx: &mut TestAppContext) {
29094    const CYCLE_POSITIONS: &[&'static str] = &[
29095        indoc! {"
29096            fn foo() {
29097                let ˇabc = 123;
29098                let x = abc + 1;
29099                let y = abc + 2;
29100                let z = abc + 2;
29101            }
29102        "},
29103        indoc! {"
29104            fn foo() {
29105                let abc = 123;
29106                let x = ˇabc + 1;
29107                let y = abc + 2;
29108                let z = abc + 2;
29109            }
29110        "},
29111        indoc! {"
29112            fn foo() {
29113                let abc = 123;
29114                let x = abc + 1;
29115                let y = ˇabc + 2;
29116                let z = abc + 2;
29117            }
29118        "},
29119        indoc! {"
29120            fn foo() {
29121                let abc = 123;
29122                let x = abc + 1;
29123                let y = abc + 2;
29124                let z = ˇabc + 2;
29125            }
29126        "},
29127    ];
29128
29129    init_test(cx, |_| {});
29130
29131    let mut cx = EditorLspTestContext::new_rust(
29132        lsp::ServerCapabilities {
29133            references_provider: Some(lsp::OneOf::Left(true)),
29134            ..Default::default()
29135        },
29136        cx,
29137    )
29138    .await;
29139
29140    // importantly, the cursor is in the middle
29141    cx.set_state(indoc! {"
29142        fn foo() {
29143            let aˇbc = 123;
29144            let x = abc + 1;
29145            let y = abc + 2;
29146            let z = abc + 2;
29147        }
29148    "});
29149
29150    let reference_ranges = [
29151        lsp::Position::new(1, 8),
29152        lsp::Position::new(2, 12),
29153        lsp::Position::new(3, 12),
29154        lsp::Position::new(4, 12),
29155    ]
29156    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29157
29158    cx.lsp
29159        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29160            Ok(Some(
29161                reference_ranges
29162                    .map(|range| lsp::Location {
29163                        uri: params.text_document_position.text_document.uri.clone(),
29164                        range,
29165                    })
29166                    .to_vec(),
29167            ))
29168        });
29169
29170    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29171        cx.update_editor(|editor, window, cx| {
29172            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29173        })
29174        .unwrap()
29175        .await
29176        .unwrap()
29177    };
29178
29179    _move(Direction::Next, 1, &mut cx).await;
29180    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29181
29182    _move(Direction::Next, 1, &mut cx).await;
29183    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29184
29185    _move(Direction::Next, 1, &mut cx).await;
29186    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29187
29188    // loops back to the start
29189    _move(Direction::Next, 1, &mut cx).await;
29190    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29191
29192    // loops back to the end
29193    _move(Direction::Prev, 1, &mut cx).await;
29194    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29195
29196    _move(Direction::Prev, 1, &mut cx).await;
29197    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29198
29199    _move(Direction::Prev, 1, &mut cx).await;
29200    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29201
29202    _move(Direction::Prev, 1, &mut cx).await;
29203    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29204
29205    _move(Direction::Next, 3, &mut cx).await;
29206    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29207
29208    _move(Direction::Prev, 2, &mut cx).await;
29209    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29210}
29211
29212#[gpui::test]
29213async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29214    init_test(cx, |_| {});
29215
29216    let (editor, cx) = cx.add_window_view(|window, cx| {
29217        let multi_buffer = MultiBuffer::build_multi(
29218            [
29219                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29220                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29221            ],
29222            cx,
29223        );
29224        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29225    });
29226
29227    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29228    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29229
29230    cx.assert_excerpts_with_selections(indoc! {"
29231        [EXCERPT]
29232        ˇ1
29233        2
29234        3
29235        [EXCERPT]
29236        1
29237        2
29238        3
29239        "});
29240
29241    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29242    cx.update_editor(|editor, window, cx| {
29243        editor.change_selections(None.into(), window, cx, |s| {
29244            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29245        });
29246    });
29247    cx.assert_excerpts_with_selections(indoc! {"
29248        [EXCERPT]
29249        1
2925029251        3
29252        [EXCERPT]
29253        1
29254        2
29255        3
29256        "});
29257
29258    cx.update_editor(|editor, window, cx| {
29259        editor
29260            .select_all_matches(&SelectAllMatches, window, cx)
29261            .unwrap();
29262    });
29263    cx.assert_excerpts_with_selections(indoc! {"
29264        [EXCERPT]
29265        1
2926629267        3
29268        [EXCERPT]
29269        1
2927029271        3
29272        "});
29273
29274    cx.update_editor(|editor, window, cx| {
29275        editor.handle_input("X", window, cx);
29276    });
29277    cx.assert_excerpts_with_selections(indoc! {"
29278        [EXCERPT]
29279        1
2928029281        3
29282        [EXCERPT]
29283        1
2928429285        3
29286        "});
29287
29288    // Scenario 2: Select "2", then fold second buffer before insertion
29289    cx.update_multibuffer(|mb, cx| {
29290        for buffer_id in buffer_ids.iter() {
29291            let buffer = mb.buffer(*buffer_id).unwrap();
29292            buffer.update(cx, |buffer, cx| {
29293                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29294            });
29295        }
29296    });
29297
29298    // Select "2" and select all matches
29299    cx.update_editor(|editor, window, cx| {
29300        editor.change_selections(None.into(), window, cx, |s| {
29301            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29302        });
29303        editor
29304            .select_all_matches(&SelectAllMatches, window, cx)
29305            .unwrap();
29306    });
29307
29308    // Fold second buffer - should remove selections from folded buffer
29309    cx.update_editor(|editor, _, cx| {
29310        editor.fold_buffer(buffer_ids[1], cx);
29311    });
29312    cx.assert_excerpts_with_selections(indoc! {"
29313        [EXCERPT]
29314        1
2931529316        3
29317        [EXCERPT]
29318        [FOLDED]
29319        "});
29320
29321    // Insert text - should only affect first buffer
29322    cx.update_editor(|editor, window, cx| {
29323        editor.handle_input("Y", window, cx);
29324    });
29325    cx.update_editor(|editor, _, cx| {
29326        editor.unfold_buffer(buffer_ids[1], cx);
29327    });
29328    cx.assert_excerpts_with_selections(indoc! {"
29329        [EXCERPT]
29330        1
2933129332        3
29333        [EXCERPT]
29334        1
29335        2
29336        3
29337        "});
29338
29339    // Scenario 3: Select "2", then fold first buffer before insertion
29340    cx.update_multibuffer(|mb, cx| {
29341        for buffer_id in buffer_ids.iter() {
29342            let buffer = mb.buffer(*buffer_id).unwrap();
29343            buffer.update(cx, |buffer, cx| {
29344                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29345            });
29346        }
29347    });
29348
29349    // Select "2" and select all matches
29350    cx.update_editor(|editor, window, cx| {
29351        editor.change_selections(None.into(), window, cx, |s| {
29352            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29353        });
29354        editor
29355            .select_all_matches(&SelectAllMatches, window, cx)
29356            .unwrap();
29357    });
29358
29359    // Fold first buffer - should remove selections from folded buffer
29360    cx.update_editor(|editor, _, cx| {
29361        editor.fold_buffer(buffer_ids[0], cx);
29362    });
29363    cx.assert_excerpts_with_selections(indoc! {"
29364        [EXCERPT]
29365        [FOLDED]
29366        [EXCERPT]
29367        1
2936829369        3
29370        "});
29371
29372    // Insert text - should only affect second buffer
29373    cx.update_editor(|editor, window, cx| {
29374        editor.handle_input("Z", window, cx);
29375    });
29376    cx.update_editor(|editor, _, cx| {
29377        editor.unfold_buffer(buffer_ids[0], cx);
29378    });
29379    cx.assert_excerpts_with_selections(indoc! {"
29380        [EXCERPT]
29381        1
29382        2
29383        3
29384        [EXCERPT]
29385        1
2938629387        3
29388        "});
29389
29390    // Test correct folded header is selected upon fold
29391    cx.update_editor(|editor, _, cx| {
29392        editor.fold_buffer(buffer_ids[0], cx);
29393        editor.fold_buffer(buffer_ids[1], cx);
29394    });
29395    cx.assert_excerpts_with_selections(indoc! {"
29396        [EXCERPT]
29397        [FOLDED]
29398        [EXCERPT]
29399        ˇ[FOLDED]
29400        "});
29401
29402    // Test selection inside folded buffer unfolds it on type
29403    cx.update_editor(|editor, window, cx| {
29404        editor.handle_input("W", window, cx);
29405    });
29406    cx.update_editor(|editor, _, cx| {
29407        editor.unfold_buffer(buffer_ids[0], cx);
29408    });
29409    cx.assert_excerpts_with_selections(indoc! {"
29410        [EXCERPT]
29411        1
29412        2
29413        3
29414        [EXCERPT]
29415        Wˇ1
29416        Z
29417        3
29418        "});
29419}
29420
29421#[gpui::test]
29422async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29423    init_test(cx, |_| {});
29424
29425    let (editor, cx) = cx.add_window_view(|window, cx| {
29426        let multi_buffer = MultiBuffer::build_multi(
29427            [
29428                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29429                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29430            ],
29431            cx,
29432        );
29433        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29434    });
29435
29436    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29437
29438    cx.assert_excerpts_with_selections(indoc! {"
29439        [EXCERPT]
29440        ˇ1
29441        2
29442        3
29443        [EXCERPT]
29444        1
29445        2
29446        3
29447        4
29448        5
29449        6
29450        7
29451        8
29452        9
29453        "});
29454
29455    cx.update_editor(|editor, window, cx| {
29456        editor.change_selections(None.into(), window, cx, |s| {
29457            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29458        });
29459    });
29460
29461    cx.assert_excerpts_with_selections(indoc! {"
29462        [EXCERPT]
29463        1
29464        2
29465        3
29466        [EXCERPT]
29467        1
29468        2
29469        3
29470        4
29471        5
29472        6
29473        ˇ7
29474        8
29475        9
29476        "});
29477
29478    cx.update_editor(|editor, _window, cx| {
29479        editor.set_vertical_scroll_margin(0, cx);
29480    });
29481
29482    cx.update_editor(|editor, window, cx| {
29483        assert_eq!(editor.vertical_scroll_margin(), 0);
29484        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29485        assert_eq!(
29486            editor.snapshot(window, cx).scroll_position(),
29487            gpui::Point::new(0., 12.0)
29488        );
29489    });
29490
29491    cx.update_editor(|editor, _window, cx| {
29492        editor.set_vertical_scroll_margin(3, cx);
29493    });
29494
29495    cx.update_editor(|editor, window, cx| {
29496        assert_eq!(editor.vertical_scroll_margin(), 3);
29497        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29498        assert_eq!(
29499            editor.snapshot(window, cx).scroll_position(),
29500            gpui::Point::new(0., 9.0)
29501        );
29502    });
29503}
29504
29505#[gpui::test]
29506async fn test_find_references_single_case(cx: &mut TestAppContext) {
29507    init_test(cx, |_| {});
29508    let mut cx = EditorLspTestContext::new_rust(
29509        lsp::ServerCapabilities {
29510            references_provider: Some(lsp::OneOf::Left(true)),
29511            ..lsp::ServerCapabilities::default()
29512        },
29513        cx,
29514    )
29515    .await;
29516
29517    let before = indoc!(
29518        r#"
29519        fn main() {
29520            let aˇbc = 123;
29521            let xyz = abc;
29522        }
29523        "#
29524    );
29525    let after = indoc!(
29526        r#"
29527        fn main() {
29528            let abc = 123;
29529            let xyz = ˇabc;
29530        }
29531        "#
29532    );
29533
29534    cx.lsp
29535        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29536            Ok(Some(vec![
29537                lsp::Location {
29538                    uri: params.text_document_position.text_document.uri.clone(),
29539                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29540                },
29541                lsp::Location {
29542                    uri: params.text_document_position.text_document.uri,
29543                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29544                },
29545            ]))
29546        });
29547
29548    cx.set_state(before);
29549
29550    let action = FindAllReferences {
29551        always_open_multibuffer: false,
29552    };
29553
29554    let navigated = cx
29555        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29556        .expect("should have spawned a task")
29557        .await
29558        .unwrap();
29559
29560    assert_eq!(navigated, Navigated::No);
29561
29562    cx.run_until_parked();
29563
29564    cx.assert_editor_state(after);
29565}
29566
29567#[gpui::test]
29568async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29569    init_test(cx, |settings| {
29570        settings.defaults.tab_size = Some(2.try_into().unwrap());
29571    });
29572
29573    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29574    let mut cx = EditorTestContext::new(cx).await;
29575    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29576
29577    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29578    cx.set_state(indoc! {"
29579        - [ ] taskˇ
29580    "});
29581    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29582    cx.wait_for_autoindent_applied().await;
29583    cx.assert_editor_state(indoc! {"
29584        - [ ] task
29585        - [ ] ˇ
29586    "});
29587
29588    // Case 2: Works with checked task items too
29589    cx.set_state(indoc! {"
29590        - [x] completed taskˇ
29591    "});
29592    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29593    cx.wait_for_autoindent_applied().await;
29594    cx.assert_editor_state(indoc! {"
29595        - [x] completed task
29596        - [ ] ˇ
29597    "});
29598
29599    // Case 2.1: Works with uppercase checked marker too
29600    cx.set_state(indoc! {"
29601        - [X] completed taskˇ
29602    "});
29603    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29604    cx.wait_for_autoindent_applied().await;
29605    cx.assert_editor_state(indoc! {"
29606        - [X] completed task
29607        - [ ] ˇ
29608    "});
29609
29610    // Case 3: Cursor position doesn't matter - content after marker is what counts
29611    cx.set_state(indoc! {"
29612        - [ ] taˇsk
29613    "});
29614    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29615    cx.wait_for_autoindent_applied().await;
29616    cx.assert_editor_state(indoc! {"
29617        - [ ] ta
29618        - [ ] ˇsk
29619    "});
29620
29621    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29622    cx.set_state(indoc! {"
29623        - [ ]  ˇ
29624    "});
29625    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29626    cx.wait_for_autoindent_applied().await;
29627    cx.assert_editor_state(
29628        indoc! {"
29629        - [ ]$$
29630        ˇ
29631    "}
29632        .replace("$", " ")
29633        .as_str(),
29634    );
29635
29636    // Case 5: Adding newline with content adds marker preserving indentation
29637    cx.set_state(indoc! {"
29638        - [ ] task
29639          - [ ] indentedˇ
29640    "});
29641    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29642    cx.wait_for_autoindent_applied().await;
29643    cx.assert_editor_state(indoc! {"
29644        - [ ] task
29645          - [ ] indented
29646          - [ ] ˇ
29647    "});
29648
29649    // Case 6: Adding newline with cursor right after prefix, unindents
29650    cx.set_state(indoc! {"
29651        - [ ] task
29652          - [ ] sub task
29653            - [ ] ˇ
29654    "});
29655    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29656    cx.wait_for_autoindent_applied().await;
29657    cx.assert_editor_state(indoc! {"
29658        - [ ] task
29659          - [ ] sub task
29660          - [ ] ˇ
29661    "});
29662    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29663    cx.wait_for_autoindent_applied().await;
29664
29665    // Case 7: Adding newline with cursor right after prefix, removes marker
29666    cx.assert_editor_state(indoc! {"
29667        - [ ] task
29668          - [ ] sub task
29669        - [ ] ˇ
29670    "});
29671    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29672    cx.wait_for_autoindent_applied().await;
29673    cx.assert_editor_state(indoc! {"
29674        - [ ] task
29675          - [ ] sub task
29676        ˇ
29677    "});
29678
29679    // Case 8: Cursor before or inside prefix does not add marker
29680    cx.set_state(indoc! {"
29681        ˇ- [ ] task
29682    "});
29683    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29684    cx.wait_for_autoindent_applied().await;
29685    cx.assert_editor_state(indoc! {"
29686
29687        ˇ- [ ] task
29688    "});
29689
29690    cx.set_state(indoc! {"
29691        - [ˇ ] task
29692    "});
29693    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29694    cx.wait_for_autoindent_applied().await;
29695    cx.assert_editor_state(indoc! {"
29696        - [
29697        ˇ
29698        ] task
29699    "});
29700}
29701
29702#[gpui::test]
29703async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29704    init_test(cx, |settings| {
29705        settings.defaults.tab_size = Some(2.try_into().unwrap());
29706    });
29707
29708    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29709    let mut cx = EditorTestContext::new(cx).await;
29710    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29711
29712    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29713    cx.set_state(indoc! {"
29714        - itemˇ
29715    "});
29716    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29717    cx.wait_for_autoindent_applied().await;
29718    cx.assert_editor_state(indoc! {"
29719        - item
29720        - ˇ
29721    "});
29722
29723    // Case 2: Works with different markers
29724    cx.set_state(indoc! {"
29725        * starred itemˇ
29726    "});
29727    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29728    cx.wait_for_autoindent_applied().await;
29729    cx.assert_editor_state(indoc! {"
29730        * starred item
29731        * ˇ
29732    "});
29733
29734    cx.set_state(indoc! {"
29735        + plus itemˇ
29736    "});
29737    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29738    cx.wait_for_autoindent_applied().await;
29739    cx.assert_editor_state(indoc! {"
29740        + plus item
29741        + ˇ
29742    "});
29743
29744    // Case 3: Cursor position doesn't matter - content after marker is what counts
29745    cx.set_state(indoc! {"
29746        - itˇem
29747    "});
29748    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29749    cx.wait_for_autoindent_applied().await;
29750    cx.assert_editor_state(indoc! {"
29751        - it
29752        - ˇem
29753    "});
29754
29755    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29756    cx.set_state(indoc! {"
29757        -  ˇ
29758    "});
29759    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29760    cx.wait_for_autoindent_applied().await;
29761    cx.assert_editor_state(
29762        indoc! {"
29763        - $
29764        ˇ
29765    "}
29766        .replace("$", " ")
29767        .as_str(),
29768    );
29769
29770    // Case 5: Adding newline with content adds marker preserving indentation
29771    cx.set_state(indoc! {"
29772        - item
29773          - indentedˇ
29774    "});
29775    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29776    cx.wait_for_autoindent_applied().await;
29777    cx.assert_editor_state(indoc! {"
29778        - item
29779          - indented
29780          - ˇ
29781    "});
29782
29783    // Case 6: Adding newline with cursor right after marker, unindents
29784    cx.set_state(indoc! {"
29785        - item
29786          - sub item
29787            - ˇ
29788    "});
29789    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29790    cx.wait_for_autoindent_applied().await;
29791    cx.assert_editor_state(indoc! {"
29792        - item
29793          - sub item
29794          - ˇ
29795    "});
29796    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29797    cx.wait_for_autoindent_applied().await;
29798
29799    // Case 7: Adding newline with cursor right after marker, removes marker
29800    cx.assert_editor_state(indoc! {"
29801        - item
29802          - sub item
29803        - ˇ
29804    "});
29805    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29806    cx.wait_for_autoindent_applied().await;
29807    cx.assert_editor_state(indoc! {"
29808        - item
29809          - sub item
29810        ˇ
29811    "});
29812
29813    // Case 8: Cursor before or inside prefix does not add marker
29814    cx.set_state(indoc! {"
29815        ˇ- item
29816    "});
29817    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29818    cx.wait_for_autoindent_applied().await;
29819    cx.assert_editor_state(indoc! {"
29820
29821        ˇ- item
29822    "});
29823
29824    cx.set_state(indoc! {"
29825        -ˇ item
29826    "});
29827    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29828    cx.wait_for_autoindent_applied().await;
29829    cx.assert_editor_state(indoc! {"
29830        -
29831        ˇitem
29832    "});
29833}
29834
29835#[gpui::test]
29836async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
29837    init_test(cx, |settings| {
29838        settings.defaults.tab_size = Some(2.try_into().unwrap());
29839    });
29840
29841    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29842    let mut cx = EditorTestContext::new(cx).await;
29843    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29844
29845    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29846    cx.set_state(indoc! {"
29847        1. first itemˇ
29848    "});
29849    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29850    cx.wait_for_autoindent_applied().await;
29851    cx.assert_editor_state(indoc! {"
29852        1. first item
29853        2. ˇ
29854    "});
29855
29856    // Case 2: Works with larger numbers
29857    cx.set_state(indoc! {"
29858        10. tenth itemˇ
29859    "});
29860    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29861    cx.wait_for_autoindent_applied().await;
29862    cx.assert_editor_state(indoc! {"
29863        10. tenth item
29864        11. ˇ
29865    "});
29866
29867    // Case 3: Cursor position doesn't matter - content after marker is what counts
29868    cx.set_state(indoc! {"
29869        1. itˇem
29870    "});
29871    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29872    cx.wait_for_autoindent_applied().await;
29873    cx.assert_editor_state(indoc! {"
29874        1. it
29875        2. ˇem
29876    "});
29877
29878    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29879    cx.set_state(indoc! {"
29880        1.  ˇ
29881    "});
29882    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29883    cx.wait_for_autoindent_applied().await;
29884    cx.assert_editor_state(
29885        indoc! {"
29886        1. $
29887        ˇ
29888    "}
29889        .replace("$", " ")
29890        .as_str(),
29891    );
29892
29893    // Case 5: Adding newline with content adds marker preserving indentation
29894    cx.set_state(indoc! {"
29895        1. item
29896          2. indentedˇ
29897    "});
29898    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29899    cx.wait_for_autoindent_applied().await;
29900    cx.assert_editor_state(indoc! {"
29901        1. item
29902          2. indented
29903          3. ˇ
29904    "});
29905
29906    // Case 6: Adding newline with cursor right after marker, unindents
29907    cx.set_state(indoc! {"
29908        1. item
29909          2. sub item
29910            3. ˇ
29911    "});
29912    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29913    cx.wait_for_autoindent_applied().await;
29914    cx.assert_editor_state(indoc! {"
29915        1. item
29916          2. sub item
29917          1. ˇ
29918    "});
29919    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29920    cx.wait_for_autoindent_applied().await;
29921
29922    // Case 7: Adding newline with cursor right after marker, removes marker
29923    cx.assert_editor_state(indoc! {"
29924        1. item
29925          2. sub item
29926        1. ˇ
29927    "});
29928    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29929    cx.wait_for_autoindent_applied().await;
29930    cx.assert_editor_state(indoc! {"
29931        1. item
29932          2. sub item
29933        ˇ
29934    "});
29935
29936    // Case 8: Cursor before or inside prefix does not add marker
29937    cx.set_state(indoc! {"
29938        ˇ1. item
29939    "});
29940    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29941    cx.wait_for_autoindent_applied().await;
29942    cx.assert_editor_state(indoc! {"
29943
29944        ˇ1. item
29945    "});
29946
29947    cx.set_state(indoc! {"
29948        1ˇ. item
29949    "});
29950    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29951    cx.wait_for_autoindent_applied().await;
29952    cx.assert_editor_state(indoc! {"
29953        1
29954        ˇ. item
29955    "});
29956}
29957
29958#[gpui::test]
29959async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
29960    init_test(cx, |settings| {
29961        settings.defaults.tab_size = Some(2.try_into().unwrap());
29962    });
29963
29964    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29965    let mut cx = EditorTestContext::new(cx).await;
29966    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29967
29968    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29969    cx.set_state(indoc! {"
29970        1. first item
29971          1. sub first item
29972          2. sub second item
29973          3. ˇ
29974    "});
29975    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29976    cx.wait_for_autoindent_applied().await;
29977    cx.assert_editor_state(indoc! {"
29978        1. first item
29979          1. sub first item
29980          2. sub second item
29981        1. ˇ
29982    "});
29983}
29984
29985#[gpui::test]
29986async fn test_tab_list_indent(cx: &mut TestAppContext) {
29987    init_test(cx, |settings| {
29988        settings.defaults.tab_size = Some(2.try_into().unwrap());
29989    });
29990
29991    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29992    let mut cx = EditorTestContext::new(cx).await;
29993    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29994
29995    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
29996    cx.set_state(indoc! {"
29997        - ˇitem
29998    "});
29999    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30000    cx.wait_for_autoindent_applied().await;
30001    let expected = indoc! {"
30002        $$- ˇitem
30003    "};
30004    cx.assert_editor_state(expected.replace("$", " ").as_str());
30005
30006    // Case 2: Task list - cursor after prefix
30007    cx.set_state(indoc! {"
30008        - [ ] ˇtask
30009    "});
30010    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30011    cx.wait_for_autoindent_applied().await;
30012    let expected = indoc! {"
30013        $$- [ ] ˇtask
30014    "};
30015    cx.assert_editor_state(expected.replace("$", " ").as_str());
30016
30017    // Case 3: Ordered list - cursor after prefix
30018    cx.set_state(indoc! {"
30019        1. ˇfirst
30020    "});
30021    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30022    cx.wait_for_autoindent_applied().await;
30023    let expected = indoc! {"
30024        $$1. ˇfirst
30025    "};
30026    cx.assert_editor_state(expected.replace("$", " ").as_str());
30027
30028    // Case 4: With existing indentation - adds more indent
30029    let initial = indoc! {"
30030        $$- ˇitem
30031    "};
30032    cx.set_state(initial.replace("$", " ").as_str());
30033    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30034    cx.wait_for_autoindent_applied().await;
30035    let expected = indoc! {"
30036        $$$$- ˇitem
30037    "};
30038    cx.assert_editor_state(expected.replace("$", " ").as_str());
30039
30040    // Case 5: Empty list item
30041    cx.set_state(indoc! {"
30042        - ˇ
30043    "});
30044    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30045    cx.wait_for_autoindent_applied().await;
30046    let expected = indoc! {"
30047        $$- ˇ
30048    "};
30049    cx.assert_editor_state(expected.replace("$", " ").as_str());
30050
30051    // Case 6: Cursor at end of line with content
30052    cx.set_state(indoc! {"
30053        - itemˇ
30054    "});
30055    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30056    cx.wait_for_autoindent_applied().await;
30057    let expected = indoc! {"
30058        $$- itemˇ
30059    "};
30060    cx.assert_editor_state(expected.replace("$", " ").as_str());
30061
30062    // Case 7: Cursor at start of list item, indents it
30063    cx.set_state(indoc! {"
30064        - item
30065        ˇ  - sub item
30066    "});
30067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30068    cx.wait_for_autoindent_applied().await;
30069    let expected = indoc! {"
30070        - item
30071          ˇ  - sub item
30072    "};
30073    cx.assert_editor_state(expected);
30074
30075    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30076    cx.update_editor(|_, _, cx| {
30077        SettingsStore::update_global(cx, |store, cx| {
30078            store.update_user_settings(cx, |settings| {
30079                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30080            });
30081        });
30082    });
30083    cx.set_state(indoc! {"
30084        - item
30085        ˇ  - sub item
30086    "});
30087    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30088    cx.wait_for_autoindent_applied().await;
30089    let expected = indoc! {"
30090        - item
30091          ˇ- sub item
30092    "};
30093    cx.assert_editor_state(expected);
30094}
30095
30096#[gpui::test]
30097async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30098    init_test(cx, |_| {});
30099    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), None, None, cx));
30100
30101    cx.update(|cx| {
30102        SettingsStore::update_global(cx, |store, cx| {
30103            store.update_user_settings(cx, |settings| {
30104                settings.project.all_languages.defaults.inlay_hints =
30105                    Some(InlayHintSettingsContent {
30106                        enabled: Some(true),
30107                        ..InlayHintSettingsContent::default()
30108                    });
30109            });
30110        });
30111    });
30112
30113    let fs = FakeFs::new(cx.executor());
30114    fs.insert_tree(
30115        path!("/project"),
30116        json!({
30117            ".zed": {
30118                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30119            },
30120            "main.rs": "fn main() {}"
30121        }),
30122    )
30123    .await;
30124
30125    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30126    let server_name = "override-rust-analyzer";
30127    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30128
30129    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30130    language_registry.add(rust_lang());
30131
30132    let capabilities = lsp::ServerCapabilities {
30133        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30134        ..lsp::ServerCapabilities::default()
30135    };
30136    let mut fake_language_servers = language_registry.register_fake_lsp(
30137        "Rust",
30138        FakeLspAdapter {
30139            name: server_name,
30140            capabilities,
30141            initializer: Some(Box::new({
30142                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30143                move |fake_server| {
30144                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30145                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30146                        move |_params, _| {
30147                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30148                            async move {
30149                                Ok(Some(vec![lsp::InlayHint {
30150                                    position: lsp::Position::new(0, 0),
30151                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30152                                    kind: None,
30153                                    text_edits: None,
30154                                    tooltip: None,
30155                                    padding_left: None,
30156                                    padding_right: None,
30157                                    data: None,
30158                                }]))
30159                            }
30160                        },
30161                    );
30162                }
30163            })),
30164            ..FakeLspAdapter::default()
30165        },
30166    );
30167
30168    cx.run_until_parked();
30169
30170    let worktree_id = project.read_with(cx, |project, cx| {
30171        project
30172            .worktrees(cx)
30173            .next()
30174            .map(|wt| wt.read(cx).id())
30175            .expect("should have a worktree")
30176    });
30177    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30178
30179    let trusted_worktrees =
30180        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30181
30182    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30183        store.can_trust(&worktree_store, worktree_id, cx)
30184    });
30185    assert!(!can_trust, "worktree should be restricted initially");
30186
30187    let buffer_before_approval = project
30188        .update(cx, |project, cx| {
30189            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30190        })
30191        .await
30192        .unwrap();
30193
30194    let (editor, cx) = cx.add_window_view(|window, cx| {
30195        Editor::new(
30196            EditorMode::full(),
30197            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30198            Some(project.clone()),
30199            window,
30200            cx,
30201        )
30202    });
30203    cx.run_until_parked();
30204    let fake_language_server = fake_language_servers.next();
30205
30206    cx.read(|cx| {
30207        let file = buffer_before_approval.read(cx).file();
30208        assert_eq!(
30209            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30210                .language_servers,
30211            ["...".to_string()],
30212            "local .zed/settings.json must not apply before trust approval"
30213        )
30214    });
30215
30216    editor.update_in(cx, |editor, window, cx| {
30217        editor.handle_input("1", window, cx);
30218    });
30219    cx.run_until_parked();
30220    cx.executor()
30221        .advance_clock(std::time::Duration::from_secs(1));
30222    assert_eq!(
30223        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30224        0,
30225        "inlay hints must not be queried before trust approval"
30226    );
30227
30228    trusted_worktrees.update(cx, |store, cx| {
30229        store.trust(
30230            &worktree_store,
30231            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30232            cx,
30233        );
30234    });
30235    cx.run_until_parked();
30236
30237    cx.read(|cx| {
30238        let file = buffer_before_approval.read(cx).file();
30239        assert_eq!(
30240            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30241                .language_servers,
30242            ["override-rust-analyzer".to_string()],
30243            "local .zed/settings.json should apply after trust approval"
30244        )
30245    });
30246    let _fake_language_server = fake_language_server.await.unwrap();
30247    editor.update_in(cx, |editor, window, cx| {
30248        editor.handle_input("1", window, cx);
30249    });
30250    cx.run_until_parked();
30251    cx.executor()
30252        .advance_clock(std::time::Duration::from_secs(1));
30253    assert!(
30254        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30255        "inlay hints should be queried after trust approval"
30256    );
30257
30258    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30259        store.can_trust(&worktree_store, worktree_id, cx)
30260    });
30261    assert!(can_trust_after, "worktree should be trusted after trust()");
30262}
30263
30264#[gpui::test]
30265fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30266    // This test reproduces a bug where drawing an editor at a position above the viewport
30267    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30268    // causes an infinite loop in blocks_in_range.
30269    //
30270    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30271    // the content mask intersection produces visible_bounds with origin at the viewport top.
30272    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30273    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30274    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30275    init_test(cx, |_| {});
30276
30277    let window = cx.add_window(|_, _| gpui::Empty);
30278    let mut cx = VisualTestContext::from_window(*window, cx);
30279
30280    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30281    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30282
30283    // Simulate a small viewport (500x500 pixels at origin 0,0)
30284    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30285
30286    // Draw the editor at a very negative Y position, simulating an editor that's been
30287    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30288    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30289    // This should NOT hang - it should just render nothing.
30290    cx.draw(
30291        gpui::point(px(0.), px(-10000.)),
30292        gpui::size(px(500.), px(3000.)),
30293        |_, _| editor.clone(),
30294    );
30295
30296    // If we get here without hanging, the test passes
30297}
30298
30299#[gpui::test]
30300async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30301    init_test(cx, |_| {});
30302
30303    let fs = FakeFs::new(cx.executor());
30304    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30305        .await;
30306
30307    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30308    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30309    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30310
30311    let editor = workspace
30312        .update(cx, |workspace, window, cx| {
30313            workspace.open_abs_path(
30314                PathBuf::from(path!("/root/file.txt")),
30315                OpenOptions::default(),
30316                window,
30317                cx,
30318            )
30319        })
30320        .unwrap()
30321        .await
30322        .unwrap()
30323        .downcast::<Editor>()
30324        .unwrap();
30325
30326    // Enable diff review button mode
30327    editor.update(cx, |editor, cx| {
30328        editor.set_show_diff_review_button(true, cx);
30329    });
30330
30331    // Initially, no indicator should be present
30332    editor.update(cx, |editor, _cx| {
30333        assert!(
30334            editor.gutter_diff_review_indicator.0.is_none(),
30335            "Indicator should be None initially"
30336        );
30337    });
30338}
30339
30340#[gpui::test]
30341async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30342    init_test(cx, |_| {});
30343
30344    // Register DisableAiSettings and set disable_ai to true
30345    cx.update(|cx| {
30346        project::DisableAiSettings::register(cx);
30347        project::DisableAiSettings::override_global(
30348            project::DisableAiSettings { disable_ai: true },
30349            cx,
30350        );
30351    });
30352
30353    let fs = FakeFs::new(cx.executor());
30354    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30355        .await;
30356
30357    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30358    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30359    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30360
30361    let editor = workspace
30362        .update(cx, |workspace, window, cx| {
30363            workspace.open_abs_path(
30364                PathBuf::from(path!("/root/file.txt")),
30365                OpenOptions::default(),
30366                window,
30367                cx,
30368            )
30369        })
30370        .unwrap()
30371        .await
30372        .unwrap()
30373        .downcast::<Editor>()
30374        .unwrap();
30375
30376    // Enable diff review button mode
30377    editor.update(cx, |editor, cx| {
30378        editor.set_show_diff_review_button(true, cx);
30379    });
30380
30381    // Verify AI is disabled
30382    cx.read(|cx| {
30383        assert!(
30384            project::DisableAiSettings::get_global(cx).disable_ai,
30385            "AI should be disabled"
30386        );
30387    });
30388
30389    // The indicator should not be created when AI is disabled
30390    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30391    editor.update(cx, |editor, _cx| {
30392        assert!(
30393            editor.gutter_diff_review_indicator.0.is_none(),
30394            "Indicator should be None when AI is disabled"
30395        );
30396    });
30397}
30398
30399#[gpui::test]
30400async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30401    init_test(cx, |_| {});
30402
30403    // Register DisableAiSettings and set disable_ai to false
30404    cx.update(|cx| {
30405        project::DisableAiSettings::register(cx);
30406        project::DisableAiSettings::override_global(
30407            project::DisableAiSettings { disable_ai: false },
30408            cx,
30409        );
30410    });
30411
30412    let fs = FakeFs::new(cx.executor());
30413    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30414        .await;
30415
30416    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30417    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30418    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30419
30420    let editor = workspace
30421        .update(cx, |workspace, window, cx| {
30422            workspace.open_abs_path(
30423                PathBuf::from(path!("/root/file.txt")),
30424                OpenOptions::default(),
30425                window,
30426                cx,
30427            )
30428        })
30429        .unwrap()
30430        .await
30431        .unwrap()
30432        .downcast::<Editor>()
30433        .unwrap();
30434
30435    // Enable diff review button mode
30436    editor.update(cx, |editor, cx| {
30437        editor.set_show_diff_review_button(true, cx);
30438    });
30439
30440    // Verify AI is enabled
30441    cx.read(|cx| {
30442        assert!(
30443            !project::DisableAiSettings::get_global(cx).disable_ai,
30444            "AI should be enabled"
30445        );
30446    });
30447
30448    // The show_diff_review_button flag should be true
30449    editor.update(cx, |editor, _cx| {
30450        assert!(
30451            editor.show_diff_review_button(),
30452            "show_diff_review_button should be true"
30453        );
30454    });
30455}
30456
30457#[gpui::test]
30458async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
30459    init_test(cx, |_| {});
30460
30461    let language = Arc::new(Language::new(
30462        LanguageConfig::default(),
30463        Some(tree_sitter_rust::LANGUAGE.into()),
30464    ));
30465
30466    let text = r#"
30467        fn main() {
30468            let x = foo(1, 2);
30469        }
30470    "#
30471    .unindent();
30472
30473    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
30474    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30475    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
30476
30477    editor
30478        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
30479        .await;
30480
30481    // Test case 1: Move to end of syntax nodes
30482    editor.update_in(cx, |editor, window, cx| {
30483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30484            s.select_display_ranges([
30485                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
30486            ]);
30487        });
30488    });
30489    editor.update(cx, |editor, cx| {
30490        assert_text_with_selections(
30491            editor,
30492            indoc! {r#"
30493                fn main() {
30494                    let x = foo(ˇ1, 2);
30495                }
30496            "#},
30497            cx,
30498        );
30499    });
30500    editor.update_in(cx, |editor, window, cx| {
30501        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30502    });
30503    editor.update(cx, |editor, cx| {
30504        assert_text_with_selections(
30505            editor,
30506            indoc! {r#"
30507                fn main() {
30508                    let x = foo(1ˇ, 2);
30509                }
30510            "#},
30511            cx,
30512        );
30513    });
30514    editor.update_in(cx, |editor, window, cx| {
30515        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30516    });
30517    editor.update(cx, |editor, cx| {
30518        assert_text_with_selections(
30519            editor,
30520            indoc! {r#"
30521                fn main() {
30522                    let x = foo(1, 2)ˇ;
30523                }
30524            "#},
30525            cx,
30526        );
30527    });
30528    editor.update_in(cx, |editor, window, cx| {
30529        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30530    });
30531    editor.update(cx, |editor, cx| {
30532        assert_text_with_selections(
30533            editor,
30534            indoc! {r#"
30535                fn main() {
30536                    let x = foo(1, 2);ˇ
30537                }
30538            "#},
30539            cx,
30540        );
30541    });
30542    editor.update_in(cx, |editor, window, cx| {
30543        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30544    });
30545    editor.update(cx, |editor, cx| {
30546        assert_text_with_selections(
30547            editor,
30548            indoc! {r#"
30549                fn main() {
30550                    let x = foo(1, 2);
3055130552            "#},
30553            cx,
30554        );
30555    });
30556
30557    // Test case 2: Move to start of syntax nodes
30558    editor.update_in(cx, |editor, window, cx| {
30559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30560            s.select_display_ranges([
30561                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
30562            ]);
30563        });
30564    });
30565    editor.update(cx, |editor, cx| {
30566        assert_text_with_selections(
30567            editor,
30568            indoc! {r#"
30569                fn main() {
30570                    let x = foo(1, 2ˇ);
30571                }
30572            "#},
30573            cx,
30574        );
30575    });
30576    editor.update_in(cx, |editor, window, cx| {
30577        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30578    });
30579    editor.update(cx, |editor, cx| {
30580        assert_text_with_selections(
30581            editor,
30582            indoc! {r#"
30583                fn main() {
30584                    let x = fooˇ(1, 2);
30585                }
30586            "#},
30587            cx,
30588        );
30589    });
30590    editor.update_in(cx, |editor, window, cx| {
30591        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30592    });
30593    editor.update(cx, |editor, cx| {
30594        assert_text_with_selections(
30595            editor,
30596            indoc! {r#"
30597                fn main() {
30598                    let x = ˇfoo(1, 2);
30599                }
30600            "#},
30601            cx,
30602        );
30603    });
30604    editor.update_in(cx, |editor, window, cx| {
30605        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30606    });
30607    editor.update(cx, |editor, cx| {
30608        assert_text_with_selections(
30609            editor,
30610            indoc! {r#"
30611                fn main() {
30612                    ˇlet x = foo(1, 2);
30613                }
30614            "#},
30615            cx,
30616        );
30617    });
30618    editor.update_in(cx, |editor, window, cx| {
30619        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30620    });
30621    editor.update(cx, |editor, cx| {
30622        assert_text_with_selections(
30623            editor,
30624            indoc! {r#"
30625                fn main() ˇ{
30626                    let x = foo(1, 2);
30627                }
30628            "#},
30629            cx,
30630        );
30631    });
30632    editor.update_in(cx, |editor, window, cx| {
30633        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30634    });
30635    editor.update(cx, |editor, cx| {
30636        assert_text_with_selections(
30637            editor,
30638            indoc! {r#"
30639                ˇfn main() {
30640                    let x = foo(1, 2);
30641                }
30642            "#},
30643            cx,
30644        );
30645    });
30646}
30647
30648#[gpui::test]
30649async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
30650    init_test(cx, |_| {});
30651
30652    let language = Arc::new(Language::new(
30653        LanguageConfig::default(),
30654        Some(tree_sitter_rust::LANGUAGE.into()),
30655    ));
30656
30657    let text = r#"
30658        fn main() {
30659            let x = foo(1, 2);
30660            let y = bar(3, 4);
30661        }
30662    "#
30663    .unindent();
30664
30665    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
30666    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30667    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
30668
30669    editor
30670        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
30671        .await;
30672
30673    // Test case 1: Move to end of syntax nodes with two cursors
30674    editor.update_in(cx, |editor, window, cx| {
30675        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30676            s.select_display_ranges([
30677                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
30678                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
30679            ]);
30680        });
30681    });
30682    editor.update(cx, |editor, cx| {
30683        assert_text_with_selections(
30684            editor,
30685            indoc! {r#"
30686                fn main() {
30687                    let x = foo(1, 2ˇ);
30688                    let y = bar(3, 4ˇ);
30689                }
30690            "#},
30691            cx,
30692        );
30693    });
30694    editor.update_in(cx, |editor, window, cx| {
30695        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30696    });
30697    editor.update(cx, |editor, cx| {
30698        assert_text_with_selections(
30699            editor,
30700            indoc! {r#"
30701                fn main() {
30702                    let x = foo(1, 2)ˇ;
30703                    let y = bar(3, 4)ˇ;
30704                }
30705            "#},
30706            cx,
30707        );
30708    });
30709    editor.update_in(cx, |editor, window, cx| {
30710        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30711    });
30712    editor.update(cx, |editor, cx| {
30713        assert_text_with_selections(
30714            editor,
30715            indoc! {r#"
30716                fn main() {
30717                    let x = foo(1, 2);ˇ
30718                    let y = bar(3, 4);ˇ
30719                }
30720            "#},
30721            cx,
30722        );
30723    });
30724
30725    // Test case 2: Move to start of syntax nodes with two cursors
30726    editor.update_in(cx, |editor, window, cx| {
30727        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30728            s.select_display_ranges([
30729                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
30730                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
30731            ]);
30732        });
30733    });
30734    editor.update(cx, |editor, cx| {
30735        assert_text_with_selections(
30736            editor,
30737            indoc! {r#"
30738                fn main() {
30739                    let x = foo(1, ˇ2);
30740                    let y = bar(3, ˇ4);
30741                }
30742            "#},
30743            cx,
30744        );
30745    });
30746    editor.update_in(cx, |editor, window, cx| {
30747        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30748    });
30749    editor.update(cx, |editor, cx| {
30750        assert_text_with_selections(
30751            editor,
30752            indoc! {r#"
30753                fn main() {
30754                    let x = fooˇ(1, 2);
30755                    let y = barˇ(3, 4);
30756                }
30757            "#},
30758            cx,
30759        );
30760    });
30761    editor.update_in(cx, |editor, window, cx| {
30762        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30763    });
30764    editor.update(cx, |editor, cx| {
30765        assert_text_with_selections(
30766            editor,
30767            indoc! {r#"
30768                fn main() {
30769                    let x = ˇfoo(1, 2);
30770                    let y = ˇbar(3, 4);
30771                }
30772            "#},
30773            cx,
30774        );
30775    });
30776    editor.update_in(cx, |editor, window, cx| {
30777        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30778    });
30779    editor.update(cx, |editor, cx| {
30780        assert_text_with_selections(
30781            editor,
30782            indoc! {r#"
30783                fn main() {
30784                    ˇlet x = foo(1, 2);
30785                    ˇlet y = bar(3, 4);
30786                }
30787            "#},
30788            cx,
30789        );
30790    });
30791}
30792
30793#[gpui::test]
30794async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
30795    cx: &mut TestAppContext,
30796) {
30797    init_test(cx, |_| {});
30798
30799    let language = Arc::new(Language::new(
30800        LanguageConfig::default(),
30801        Some(tree_sitter_rust::LANGUAGE.into()),
30802    ));
30803
30804    let text = r#"
30805        fn main() {
30806            let x = foo(1, 2);
30807            let msg = "hello world";
30808        }
30809    "#
30810    .unindent();
30811
30812    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
30813    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30814    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
30815
30816    editor
30817        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
30818        .await;
30819
30820    // Test case 1: With existing selection, move_to_end keeps selection
30821    editor.update_in(cx, |editor, window, cx| {
30822        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30823            s.select_display_ranges([
30824                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
30825            ]);
30826        });
30827    });
30828    editor.update(cx, |editor, cx| {
30829        assert_text_with_selections(
30830            editor,
30831            indoc! {r#"
30832                fn main() {
30833                    let x = «foo(1, 2)ˇ»;
30834                    let msg = "hello world";
30835                }
30836            "#},
30837            cx,
30838        );
30839    });
30840    editor.update_in(cx, |editor, window, cx| {
30841        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30842    });
30843    editor.update(cx, |editor, cx| {
30844        assert_text_with_selections(
30845            editor,
30846            indoc! {r#"
30847                fn main() {
30848                    let x = «foo(1, 2)ˇ»;
30849                    let msg = "hello world";
30850                }
30851            "#},
30852            cx,
30853        );
30854    });
30855
30856    // Test case 2: Move to end within a string
30857    editor.update_in(cx, |editor, window, cx| {
30858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30859            s.select_display_ranges([
30860                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
30861            ]);
30862        });
30863    });
30864    editor.update(cx, |editor, cx| {
30865        assert_text_with_selections(
30866            editor,
30867            indoc! {r#"
30868                fn main() {
30869                    let x = foo(1, 2);
30870                    let msg = "ˇhello world";
30871                }
30872            "#},
30873            cx,
30874        );
30875    });
30876    editor.update_in(cx, |editor, window, cx| {
30877        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
30878    });
30879    editor.update(cx, |editor, cx| {
30880        assert_text_with_selections(
30881            editor,
30882            indoc! {r#"
30883                fn main() {
30884                    let x = foo(1, 2);
30885                    let msg = "hello worldˇ";
30886                }
30887            "#},
30888            cx,
30889        );
30890    });
30891
30892    // Test case 3: Move to start within a string
30893    editor.update_in(cx, |editor, window, cx| {
30894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30895            s.select_display_ranges([
30896                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
30897            ]);
30898        });
30899    });
30900    editor.update(cx, |editor, cx| {
30901        assert_text_with_selections(
30902            editor,
30903            indoc! {r#"
30904                fn main() {
30905                    let x = foo(1, 2);
30906                    let msg = "hello ˇworld";
30907                }
30908            "#},
30909            cx,
30910        );
30911    });
30912    editor.update_in(cx, |editor, window, cx| {
30913        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
30914    });
30915    editor.update(cx, |editor, cx| {
30916        assert_text_with_selections(
30917            editor,
30918            indoc! {r#"
30919                fn main() {
30920                    let x = foo(1, 2);
30921                    let msg = "ˇhello world";
30922                }
30923            "#},
30924            cx,
30925        );
30926    });
30927}